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