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