summaryrefslogtreecommitdiff
path: root/src/common
blob: f93793e65958768ab7e7dc3d3848339803d41ba3 (plain)
  1. # -*-shell-script-*-
  2. # Shared sh functions for the monkeysphere
  3. #
  4. # Written by
  5. # Jameson Rollins <jrollins@fifthhorseman.net>
  6. #
  7. # Copyright 2008, released under the GPL, version 3 or later
  8. # all-caps variables are meant to be user supplied (ie. from config
  9. # file) and are considered global
  10. ########################################################################
  11. ### COMMON VARIABLES
  12. # managed directories
  13. ETC="/etc/monkeysphere"
  14. export ETC
  15. ########################################################################
  16. ### UTILITY FUNCTIONS
  17. # failure function. exits with code 255, unless specified otherwise.
  18. failure() {
  19. echo "$1" >&2
  20. exit ${2:-'255'}
  21. }
  22. # write output to stderr based on specified LOG_LEVEL the first
  23. # parameter is the priority of the output, and everything else is what
  24. # is echoed to stderr
  25. log() {
  26. local priority
  27. local level
  28. local output
  29. # translate lowers to uppers in global log level
  30. LOG_LEVEL=$(echo "$LOG_LEVEL" | tr "[:lower:]" "[:upper:]")
  31. # just go ahead and return if the log level is silent
  32. if [ "$LOG_LEVEL" = 'SILENT' ] ; then
  33. return
  34. fi
  35. # get priority from first parameter, translating all lower to
  36. # uppers
  37. priority=$(echo "$1" | tr "[:lower:]" "[:upper:]")
  38. shift
  39. # scan over available levels
  40. # list in decreasing verbosity (all caps)
  41. for level in DEBUG INFO ERROR ; do
  42. # output if the log level matches, set output to true
  43. # this will output for all subsequenty loops as well.
  44. if [ "$LOG_LEVEL" = "$level" ] ; then
  45. output=true
  46. fi
  47. if [ "$priority" = "$level" -a "$output" = 'true' ] ; then
  48. echo -n "ms: " >&2
  49. echo "$@" >&2
  50. fi
  51. done
  52. }
  53. # cut out all comments(#) and blank lines from standard input
  54. meat() {
  55. grep -v -e "^[[:space:]]*#" -e '^$' "$1"
  56. }
  57. # cut a specified line from standard input
  58. cutline() {
  59. head --line="$1" "$2" | tail -1
  60. }
  61. # check that characters are in a string (in an AND fashion).
  62. # used for checking key capability
  63. # check_capability capability a [b...]
  64. check_capability() {
  65. local usage
  66. local capcheck
  67. usage="$1"
  68. shift 1
  69. for capcheck ; do
  70. if echo "$usage" | grep -q -v "$capcheck" ; then
  71. return 1
  72. fi
  73. done
  74. return 0
  75. }
  76. # hash of a file
  77. file_hash() {
  78. md5sum "$1" 2> /dev/null
  79. }
  80. # convert escaped characters in pipeline from gpg output back into
  81. # original character
  82. # FIXME: undo all escape character translation in with-colons gpg
  83. # output
  84. gpg_unescape() {
  85. sed 's/\\x3a/:/g'
  86. }
  87. # convert nasty chars into gpg-friendly form in pipeline
  88. # FIXME: escape everything, not just colons!
  89. gpg_escape() {
  90. sed 's/:/\\x3a/g'
  91. }
  92. # prompt for GPG-formatted expiration, and emit result on stdout
  93. get_gpg_expiration() {
  94. local keyExpire
  95. keyExpire="$1"
  96. if [ -z "$keyExpire" ]; then
  97. cat >&2 <<EOF
  98. Please specify how long the key should be valid.
  99. 0 = key does not expire
  100. <n> = key expires in n days
  101. <n>w = key expires in n weeks
  102. <n>m = key expires in n months
  103. <n>y = key expires in n years
  104. EOF
  105. while [ -z "$keyExpire" ] ; do
  106. read -p "Key is valid for? (0) " keyExpire
  107. if ! test_gpg_expire ${keyExpire:=0} ; then
  108. echo "invalid value" >&2
  109. unset keyExpire
  110. fi
  111. done
  112. elif ! test_gpg_expire "$keyExpire" ; then
  113. failure "invalid key expiration value '$keyExpire'."
  114. fi
  115. echo "$keyExpire"
  116. }
  117. passphrase_prompt() {
  118. local prompt="$1"
  119. local fifo="$2"
  120. local PASS
  121. if [ "$DISPLAY" ] && which "${SSH_ASKPASS:-ssh-askpass}" >/dev/null; then
  122. "${SSH_ASKPASS:-ssh-askpass}" "$prompt" > "$fifo"
  123. else
  124. read -s -p "$prompt" PASS
  125. # Uses the builtin echo, so should not put the passphrase into
  126. # the process table. I think. --dkg
  127. echo "$PASS" > "$fifo"
  128. fi
  129. }
  130. test_gnu_dummy_s2k_extension() {
  131. # this block contains a demonstration private key that has had the
  132. # primary key stripped out using the GNU S2K extension known as
  133. # "gnu-dummy" (see /usr/share/doc/gnupg/DETAILS.gz). The subkey is
  134. # present in cleartext, however.
  135. # openpgp2ssh will be able to deal with this based on whether the
  136. # local copy of GnuTLS contains read_s2k support that can handle it.
  137. # read up on that here:
  138. # http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html
  139. echo "
  140. -----BEGIN PGP PRIVATE KEY BLOCK-----
  141. Version: GnuPG v1.4.9 (GNU/Linux)
  142. lQCVBEO3YdABBACRqqEnucag4+vyZny2M67Pai5+5suIRRvY+Ly8Ms5MvgCi3EVV
  143. xT05O/+0ShiRaf+QicCOFrhbU9PZzzU+seEvkeW2UCu4dQfILkmj+HBEIltGnHr3
  144. G0yegHj5pnqrcezERURf2e17gGFWX91cXB9Cm721FPXczuKraphKwCA9PwARAQAB
  145. /gNlAkdOVQG0OURlbW9uc3RyYXRpb24gS2V5IGZvciBTMksgR05VIGV4dGVuc2lv
  146. biAxMDAxIC0tIGdudS1kdW1teYi8BBMBAgAmBQJDt2HQAhsDBQkB4TOABgsJCAcD
  147. AgQVAggDBBYCAwECHgECF4AACgkQQZUwSa4UDezTOQP/TMQXUVrWzHYZGopoPZ2+
  148. ZS3qddiznBHsgb7MGYg1KlTiVJSroDUBCHIUJvdQKZV9zrzrFl47D07x6hGyUPHV
  149. aZXvuITW8t1o5MMHkCy3pmJ2KgfDvdUxrBvLfgPMICA4c6zA0mWquee43syEW9NY
  150. g3q61iPlQwD1J1kX1wlimLCdAdgEQ7dh0AEEANAwa63zlQbuy1Meliy8otwiOa+a
  151. mH6pxxUgUNggjyjO5qx+rl25mMjvGIRX4/L1QwIBXJBVi3SgvJW1COZxZqBYqj9U
  152. 8HVT07mWKFEDf0rZLeUE2jTm16cF9fcW4DQhW+sfYm+hi2sY3HeMuwlUBK9KHfW2
  153. +bGeDzVZ4pqfUEudABEBAAEAA/0bemib+wxub9IyVFUp7nPobjQC83qxLSNzrGI/
  154. RHzgu/5CQi4tfLOnwbcQsLELfker2hYnjsLrT9PURqK4F7udrWEoZ1I1LymOtLG/
  155. 4tNZ7Mnul3wRC2tCn7FKx8sGJwGh/3li8vZ6ALVJAyOia5TZ/buX0+QZzt6+hPKk
  156. 7MU1WQIA4bUBjtrsqDwro94DvPj3/jBnMZbXr6WZIItLNeVDUcM8oHL807Am97K1
  157. ueO/f6v1sGAHG6lVPTmtekqPSTWBfwIA7CGFvEyvSALfB8NUa6jtk27NCiw0csql
  158. kuhCmwXGMVOiryKEfegkIahf2bAd/gnWHPrpWp7bUE20v8YoW22I4wIAhnm5Wr5Q
  159. Sy7EHDUxmJm5TzadFp9gq08qNzHBpXSYXXJ3JuWcL1/awUqp3tE1I6zZ0hZ38Ia6
  160. SdBMN88idnhDPqPoiKUEGAECAA8FAkO3YdACGyAFCQHhM4AACgkQQZUwSa4UDezm
  161. vQP/ZhK+2ly9oI2z7ZcNC/BJRch0/ybQ3haahII8pXXmOThpZohr/LUgoWgCZdXg
  162. vP6yiszNk2tIs8KphCAw7Lw/qzDC2hEORjWO4f46qk73RAgSqG/GyzI4ltWiDhqn
  163. vnQCFl3+QFSe4zinqykHnLwGPMXv428d/ZjkIc2ju8dRsn4=
  164. =CR5w
  165. -----END PGP PRIVATE KEY BLOCK-----
  166. " | openpgp2ssh 4129E89D17C1D591 >/dev/null 2>/dev/null
  167. }
  168. # remove all lines with specified string from specified file
  169. remove_line() {
  170. local file
  171. local string
  172. file="$1"
  173. string="$2"
  174. if [ -z "$file" -o -z "$string" ] ; then
  175. return 1
  176. fi
  177. if [ ! -e "$file" ] ; then
  178. return 1
  179. fi
  180. # if the string is in the file...
  181. if grep -q -F "$string" "$file" 2> /dev/null ; then
  182. # remove the line with the string, and return 0
  183. grep -v -F "$string" "$file" | sponge "$file"
  184. return 0
  185. # otherwise return 1
  186. else
  187. return 1
  188. fi
  189. }
  190. # remove all lines with MonkeySphere strings in file
  191. remove_monkeysphere_lines() {
  192. local file
  193. file="$1"
  194. if [ -z "$file" ] ; then
  195. return 1
  196. fi
  197. if [ ! -e "$file" ] ; then
  198. return 1
  199. fi
  200. egrep -v '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' \
  201. "$file" | sponge "$file"
  202. }
  203. # translate ssh-style path variables %h and %u
  204. translate_ssh_variables() {
  205. local uname
  206. local home
  207. uname="$1"
  208. path="$2"
  209. # get the user's home directory
  210. userHome=$(getent passwd "$uname" | cut -d: -f6)
  211. # translate '%u' to user name
  212. path=${path/\%u/"$uname"}
  213. # translate '%h' to user home directory
  214. path=${path/\%h/"$userHome"}
  215. echo "$path"
  216. }
  217. # test that a string to conforms to GPG's expiration format
  218. test_gpg_expire() {
  219. echo "$1" | egrep -q "^[0-9]+[mwy]?$"
  220. }
  221. # check that a file is properly owned, and that all it's parent
  222. # directories are not group/other writable
  223. check_key_file_permissions() {
  224. local user
  225. local path
  226. local access
  227. local gAccess
  228. local oAccess
  229. # function to check that an octal corresponds to writability
  230. is_write() {
  231. [ "$1" -eq 2 -o "$1" -eq 3 -o "$1" -eq 6 -o "$1" -eq 7 ]
  232. }
  233. user="$1"
  234. path="$2"
  235. # return 0 is path does not exist
  236. [ -e "$path" ] || return 0
  237. owner=$(stat --format '%U' "$path")
  238. access=$(stat --format '%a' "$path")
  239. gAccess=$(echo "$access" | cut -c2)
  240. oAccess=$(echo "$access" | cut -c3)
  241. # check owner
  242. if [ "$owner" != "$user" -a "$owner" != 'root' ] ; then
  243. return 1
  244. fi
  245. # check group/other writability
  246. if is_write "$gAccess" || is_write "$oAccess" ; then
  247. return 2
  248. fi
  249. if [ "$path" = '/' ] ; then
  250. return 0
  251. else
  252. check_key_file_permissions $(dirname "$path")
  253. fi
  254. }
  255. ### CONVERSION UTILITIES
  256. # output the ssh key for a given key ID
  257. gpg2ssh() {
  258. local keyID
  259. keyID="$1"
  260. gpg --export "$keyID" | openpgp2ssh "$keyID" 2> /dev/null
  261. }
  262. # output known_hosts line from ssh key
  263. ssh2known_hosts() {
  264. local host
  265. local key
  266. host="$1"
  267. key="$2"
  268. echo -n "$host "
  269. echo -n "$key" | tr -d '\n'
  270. echo " MonkeySphere${DATE}"
  271. }
  272. # output authorized_keys line from ssh key
  273. ssh2authorized_keys() {
  274. local userID
  275. local key
  276. userID="$1"
  277. key="$2"
  278. echo -n "$key" | tr -d '\n'
  279. echo " MonkeySphere${DATE} ${userID}"
  280. }
  281. # convert key from gpg to ssh known_hosts format
  282. gpg2known_hosts() {
  283. local host
  284. local keyID
  285. host="$1"
  286. keyID="$2"
  287. # NOTE: it seems that ssh-keygen -R removes all comment fields from
  288. # all lines in the known_hosts file. why?
  289. # NOTE: just in case, the COMMENT can be matched with the
  290. # following regexp:
  291. # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  292. echo -n "$host "
  293. gpg2ssh "$keyID" | tr -d '\n'
  294. echo " MonkeySphere${DATE}"
  295. }
  296. # convert key from gpg to ssh authorized_keys format
  297. gpg2authorized_keys() {
  298. local userID
  299. local keyID
  300. userID="$1"
  301. keyID="$2"
  302. # NOTE: just in case, the COMMENT can be matched with the
  303. # following regexp:
  304. # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  305. gpg2ssh "$keyID" | tr -d '\n'
  306. echo " MonkeySphere${DATE} ${userID}"
  307. }
  308. ### GPG UTILITIES
  309. # retrieve all keys with given user id from keyserver
  310. # FIXME: need to figure out how to retrieve all matching keys
  311. # (not just first N (5 in this case))
  312. gpg_fetch_userid() {
  313. local userID
  314. local returnCode
  315. if [ "$CHECK_KEYSERVER" != 'true' ] ; then
  316. return 0
  317. fi
  318. userID="$1"
  319. log info " checking keyserver $KEYSERVER... "
  320. echo 1,2,3,4,5 | \
  321. gpg --quiet --batch --with-colons \
  322. --command-fd 0 --keyserver "$KEYSERVER" \
  323. --search ="$userID" > /dev/null 2>&1
  324. returnCode="$?"
  325. # if the user is the monkeysphere user, then update the
  326. # monkeysphere user's trustdb
  327. if [ $(id -un) = "$MONKEYSPHERE_USER" ] ; then
  328. gpg_authentication "--check-trustdb" > /dev/null 2>&1
  329. fi
  330. return "$returnCode"
  331. }
  332. ########################################################################
  333. ### PROCESSING FUNCTIONS
  334. # userid and key policy checking
  335. # the following checks policy on the returned keys
  336. # - checks that full key has appropriate valididy (u|f)
  337. # - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY)
  338. # - checks that requested user ID has appropriate validity
  339. # (see /usr/share/doc/gnupg/DETAILS.gz)
  340. # output is one line for every found key, in the following format:
  341. #
  342. # flag:sshKey
  343. #
  344. # "flag" is an acceptability flag, 0 = ok, 1 = bad
  345. # "sshKey" is the translated gpg key
  346. #
  347. # all log output must go to stderr, as stdout is used to pass the
  348. # flag:sshKey to the calling function.
  349. #
  350. # expects global variable: "MODE"
  351. process_user_id() {
  352. local userID
  353. local requiredCapability
  354. local requiredPubCapability
  355. local gpgOut
  356. local type
  357. local validity
  358. local keyid
  359. local uidfpr
  360. local usage
  361. local keyOK
  362. local uidOK
  363. local lastKey
  364. local lastKeyOK
  365. local fingerprint
  366. userID="$1"
  367. # set the required key capability based on the mode
  368. if [ "$MODE" = 'known_hosts' ] ; then
  369. requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY"
  370. elif [ "$MODE" = 'authorized_keys' ] ; then
  371. requiredCapability="$REQUIRED_USER_KEY_CAPABILITY"
  372. fi
  373. requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]")
  374. # fetch the user ID if necessary/requested
  375. gpg_fetch_userid "$userID"
  376. # output gpg info for (exact) userid and store
  377. gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
  378. --with-fingerprint --with-fingerprint \
  379. ="$userID" 2>/dev/null)
  380. # if the gpg query return code is not 0, return 1
  381. if [ "$?" -ne 0 ] ; then
  382. log error " no primary keys found."
  383. return 1
  384. fi
  385. # loop over all lines in the gpg output and process.
  386. echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
  387. while IFS=: read -r type validity keyid uidfpr usage ; do
  388. # process based on record type
  389. case $type in
  390. 'pub') # primary keys
  391. # new key, wipe the slate
  392. keyOK=
  393. uidOK=
  394. lastKey=pub
  395. lastKeyOK=
  396. fingerprint=
  397. log error " primary key found: $keyid"
  398. # if overall key is not valid, skip
  399. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  400. log error " - unacceptable primary key validity ($validity)."
  401. continue
  402. fi
  403. # if overall key is disabled, skip
  404. if check_capability "$usage" 'D' ; then
  405. log error " - key disabled."
  406. continue
  407. fi
  408. # if overall key capability is not ok, skip
  409. if ! check_capability "$usage" $requiredPubCapability ; then
  410. log error " - unacceptable primary key capability ($usage)."
  411. continue
  412. fi
  413. # mark overall key as ok
  414. keyOK=true
  415. # mark primary key as ok if capability is ok
  416. if check_capability "$usage" $requiredCapability ; then
  417. lastKeyOK=true
  418. fi
  419. ;;
  420. 'uid') # user ids
  421. if [ "$lastKey" != pub ] ; then
  422. log error " - got a user ID after a sub key?! user IDs should only follow primary keys!"
  423. continue
  424. fi
  425. # if an acceptable user ID was already found, skip
  426. if [ "$uidOK" = 'true' ] ; then
  427. continue
  428. fi
  429. # if the user ID does matches...
  430. if [ "$(echo "$uidfpr" | gpg_unescape)" = "$userID" ] ; then
  431. # and the user ID validity is ok
  432. if [ "$validity" = 'u' -o "$validity" = 'f' ] ; then
  433. # mark user ID acceptable
  434. uidOK=true
  435. fi
  436. else
  437. continue
  438. fi
  439. # output a line for the primary key
  440. # 0 = ok, 1 = bad
  441. if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
  442. log error " * acceptable primary key."
  443. if [ -z "$sshKey" ] ; then
  444. log error " ! primary key could not be translated (not RSA or DSA?)."
  445. else
  446. echo "0:${sshKey}"
  447. fi
  448. else
  449. log error " - unacceptable primary key."
  450. if [ -z "$sshKey" ] ; then
  451. log error " ! primary key could not be translated (not RSA or DSA?)."
  452. else
  453. echo "1:${sshKey}"
  454. fi
  455. fi
  456. ;;
  457. 'sub') # sub keys
  458. # unset acceptability of last key
  459. lastKey=sub
  460. lastKeyOK=
  461. fingerprint=
  462. # don't bother with sub keys if the primary key is not valid
  463. if [ "$keyOK" != true ] ; then
  464. continue
  465. fi
  466. # don't bother with sub keys if no user ID is acceptable:
  467. if [ "$uidOK" != true ] ; then
  468. continue
  469. fi
  470. # if sub key validity is not ok, skip
  471. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  472. continue
  473. fi
  474. # if sub key capability is not ok, skip
  475. if ! check_capability "$usage" $requiredCapability ; then
  476. continue
  477. fi
  478. # mark sub key as ok
  479. lastKeyOK=true
  480. ;;
  481. 'fpr') # key fingerprint
  482. fingerprint="$uidfpr"
  483. sshKey=$(gpg2ssh "$fingerprint")
  484. # if the last key was the pub key, skip
  485. if [ "$lastKey" = pub ] ; then
  486. continue
  487. fi
  488. # output a line for the sub key
  489. # 0 = ok, 1 = bad
  490. if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
  491. log error " * acceptable sub key."
  492. if [ -z "$sshKey" ] ; then
  493. log error " ! sub key could not be translated (not RSA or DSA?)."
  494. else
  495. echo "0:${sshKey}"
  496. fi
  497. else
  498. log error " - unacceptable sub key."
  499. if [ -z "$sshKey" ] ; then
  500. log error " ! sub key could not be translated (not RSA or DSA?)."
  501. else
  502. echo "1:${sshKey}"
  503. fi
  504. fi
  505. ;;
  506. esac
  507. done | sort -t: -k1 -n -r
  508. # NOTE: this last sort is important so that the "good" keys (key
  509. # flag '0') come last. This is so that they take precedence when
  510. # being processed in the key files over "bad" keys (key flag '1')
  511. }
  512. # process a single host in the known_host file
  513. process_host_known_hosts() {
  514. local host
  515. local userID
  516. local nKeys
  517. local nKeysOK
  518. local ok
  519. local sshKey
  520. local tmpfile
  521. host="$1"
  522. userID="ssh://${host}"
  523. log info "processing: $host"
  524. nKeys=0
  525. nKeysOK=0
  526. IFS=$'\n'
  527. for line in $(process_user_id "${userID}") ; do
  528. # note that key was found
  529. nKeys=$((nKeys+1))
  530. ok=$(echo "$line" | cut -d: -f1)
  531. sshKey=$(echo "$line" | cut -d: -f2)
  532. if [ -z "$sshKey" ] ; then
  533. continue
  534. fi
  535. # remove the old host key line, and note if removed
  536. remove_line "$KNOWN_HOSTS" "$sshKey"
  537. # if key OK, add new host line
  538. if [ "$ok" -eq '0' ] ; then
  539. # note that key was found ok
  540. nKeysOK=$((nKeysOK+1))
  541. # hash if specified
  542. if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then
  543. # FIXME: this is really hackish cause ssh-keygen won't
  544. # hash from stdin to stdout
  545. tmpfile=$(mktemp)
  546. ssh2known_hosts "$host" "$sshKey" > "$tmpfile"
  547. ssh-keygen -H -f "$tmpfile" 2> /dev/null
  548. cat "$tmpfile" >> "$KNOWN_HOSTS"
  549. rm -f "$tmpfile" "${tmpfile}.old"
  550. else
  551. ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS"
  552. fi
  553. fi
  554. done
  555. # if at least one key was found...
  556. if [ "$nKeys" -gt 0 ] ; then
  557. # if ok keys were found, return 0
  558. if [ "$nKeysOK" -gt 0 ] ; then
  559. return 0
  560. # else return 2
  561. else
  562. return 2
  563. fi
  564. # if no keys were found, return 1
  565. else
  566. return 1
  567. fi
  568. }
  569. # update the known_hosts file for a set of hosts listed on command
  570. # line
  571. update_known_hosts() {
  572. local nHosts
  573. local nHostsOK
  574. local nHostsBAD
  575. local fileCheck
  576. local host
  577. # the number of hosts specified on command line
  578. nHosts="$#"
  579. nHostsOK=0
  580. nHostsBAD=0
  581. # set the trap to remove any lockfiles on exit
  582. trap "lockfile-remove $KNOWN_HOSTS" EXIT
  583. # create a lockfile on known_hosts
  584. lockfile-create "$KNOWN_HOSTS"
  585. # note pre update file checksum
  586. fileCheck="$(file_hash "$KNOWN_HOSTS")"
  587. for host ; do
  588. # process the host
  589. process_host_known_hosts "$host"
  590. # note the result
  591. case "$?" in
  592. 0)
  593. nHostsOK=$((nHostsOK+1))
  594. ;;
  595. 2)
  596. nHostsBAD=$((nHostsBAD+1))
  597. ;;
  598. esac
  599. # touch the lockfile, for good measure.
  600. lockfile-touch --oneshot "$KNOWN_HOSTS"
  601. done
  602. # remove the lockfile
  603. lockfile-remove "$KNOWN_HOSTS"
  604. # note if the known_hosts file was updated
  605. if [ "$(file_hash "$KNOWN_HOSTS")" != "$fileCheck" ] ; then
  606. log info "known_hosts file updated."
  607. fi
  608. # if an acceptable host was found, return 0
  609. if [ "$nHostsOK" -gt 0 ] ; then
  610. return 0
  611. # else if no ok hosts were found...
  612. else
  613. # if no bad host were found then no hosts were found at all,
  614. # and return 1
  615. if [ "$nHostsBAD" -eq 0 ] ; then
  616. return 1
  617. # else if at least one bad host was found, return 2
  618. else
  619. return 2
  620. fi
  621. fi
  622. }
  623. # process hosts from a known_hosts file
  624. process_known_hosts() {
  625. local hosts
  626. log info "processing known_hosts file..."
  627. hosts=$(meat "$KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | tr , ' ' | tr '\n' ' ')
  628. if [ -z "$hosts" ] ; then
  629. log error "no hosts to process."
  630. return
  631. fi
  632. # take all the hosts from the known_hosts file (first
  633. # field), grep out all the hashed hosts (lines starting
  634. # with '|')...
  635. update_known_hosts $hosts
  636. }
  637. # process uids for the authorized_keys file
  638. process_uid_authorized_keys() {
  639. local userID
  640. local nKeys
  641. local nKeysOK
  642. local ok
  643. local sshKey
  644. userID="$1"
  645. log info "processing: $userID"
  646. nKeys=0
  647. nKeysOK=0
  648. IFS=$'\n'
  649. for line in $(process_user_id "$userID") ; do
  650. # note that key was found
  651. nKeys=$((nKeys+1))
  652. ok=$(echo "$line" | cut -d: -f1)
  653. sshKey=$(echo "$line" | cut -d: -f2)
  654. if [ -z "$sshKey" ] ; then
  655. continue
  656. fi
  657. # remove the old host key line
  658. remove_line "$AUTHORIZED_KEYS" "$sshKey"
  659. # if key OK, add new host line
  660. if [ "$ok" -eq '0' ] ; then
  661. # note that key was found ok
  662. nKeysOK=$((nKeysOK+1))
  663. ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS"
  664. fi
  665. done
  666. # if at least one key was found...
  667. if [ "$nKeys" -gt 0 ] ; then
  668. # if ok keys were found, return 0
  669. if [ "$nKeysOK" -gt 0 ] ; then
  670. return 0
  671. # else return 2
  672. else
  673. return 2
  674. fi
  675. # if no keys were found, return 1
  676. else
  677. return 1
  678. fi
  679. }
  680. # update the authorized_keys files from a list of user IDs on command
  681. # line
  682. update_authorized_keys() {
  683. local userID
  684. local nIDs
  685. local nIDsOK
  686. local nIDsBAD
  687. local fileCheck
  688. # the number of ids specified on command line
  689. nIDs="$#"
  690. nIDsOK=0
  691. nIDsBAD=0
  692. # set the trap to remove any lockfiles on exit
  693. trap "lockfile-remove $AUTHORIZED_KEYS" EXIT
  694. # create a lockfile on authorized_keys
  695. lockfile-create "$AUTHORIZED_KEYS"
  696. # note pre update file checksum
  697. fileCheck="$(file_hash "$AUTHORIZED_KEYS")"
  698. # remove any monkeysphere lines from authorized_keys file
  699. remove_monkeysphere_lines "$AUTHORIZED_KEYS"
  700. for userID ; do
  701. # process the user ID, change return code if key not found for
  702. # user ID
  703. process_uid_authorized_keys "$userID"
  704. # note the result
  705. case "$?" in
  706. 0)
  707. nIDsOK=$((nIDsOK+1))
  708. ;;
  709. 2)
  710. nIDsBAD=$((nIDsBAD+1))
  711. ;;
  712. esac
  713. # touch the lockfile, for good measure.
  714. lockfile-touch --oneshot "$AUTHORIZED_KEYS"
  715. done
  716. # remove the lockfile
  717. lockfile-remove "$AUTHORIZED_KEYS"
  718. # note if the authorized_keys file was updated
  719. if [ "$(file_hash "$AUTHORIZED_KEYS")" != "$fileCheck" ] ; then
  720. log info "authorized_keys file updated."
  721. fi
  722. # if an acceptable id was found, return 0
  723. if [ "$nIDsOK" -gt 0 ] ; then
  724. return 0
  725. # else if no ok ids were found...
  726. else
  727. # if no bad ids were found then no ids were found at all, and
  728. # return 1
  729. if [ "$nIDsBAD" -eq 0 ] ; then
  730. return 1
  731. # else if at least one bad id was found, return 2
  732. else
  733. return 2
  734. fi
  735. fi
  736. }
  737. # process an authorized_user_ids file for authorized_keys
  738. process_authorized_user_ids() {
  739. local line
  740. local nline
  741. local userIDs
  742. authorizedUserIDs="$1"
  743. log info "processing authorized_user_ids file..."
  744. if ! meat "$authorizedUserIDs" > /dev/null ; then
  745. log error "no user IDs to process."
  746. return
  747. fi
  748. nline=0
  749. # extract user IDs from authorized_user_ids file
  750. IFS=$'\n'
  751. for line in $(meat "$authorizedUserIDs") ; do
  752. userIDs["$nline"]="$line"
  753. nline=$((nline+1))
  754. done
  755. update_authorized_keys "${userIDs[@]}"
  756. }