summaryrefslogtreecommitdiff
path: root/src/common
blob: 34d9b5adfcace1cf8cf4858788a03d602ade4e4a (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. local level
  25. level="$1"
  26. shift
  27. echo -n "ms: " >&2
  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 info " 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. # if the user is the monkeysphere user, then update the
  303. # monkeysphere user's trustdb
  304. if [ $(id -un) = "$MONKEYSPHERE_USER" ] ; then
  305. gpg_authentication "--check-trustdb" > /dev/null 2>&1
  306. fi
  307. return "$returnCode"
  308. }
  309. ########################################################################
  310. ### PROCESSING FUNCTIONS
  311. # userid and key policy checking
  312. # the following checks policy on the returned keys
  313. # - checks that full key has appropriate valididy (u|f)
  314. # - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY)
  315. # - checks that requested user ID has appropriate validity
  316. # (see /usr/share/doc/gnupg/DETAILS.gz)
  317. # output is one line for every found key, in the following format:
  318. #
  319. # flag:sshKey
  320. #
  321. # "flag" is an acceptability flag, 0 = ok, 1 = bad
  322. # "sshKey" is the translated gpg key
  323. #
  324. # all log output must go to stderr, as stdout is used to pass the
  325. # flag:sshKey to the calling function.
  326. #
  327. # expects global variable: "MODE"
  328. process_user_id() {
  329. local userID
  330. local requiredCapability
  331. local requiredPubCapability
  332. local gpgOut
  333. local type
  334. local validity
  335. local keyid
  336. local uidfpr
  337. local usage
  338. local keyOK
  339. local uidOK
  340. local lastKey
  341. local lastKeyOK
  342. local fingerprint
  343. userID="$1"
  344. # set the required key capability based on the mode
  345. if [ "$MODE" = 'known_hosts' ] ; then
  346. requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY"
  347. elif [ "$MODE" = 'authorized_keys' ] ; then
  348. requiredCapability="$REQUIRED_USER_KEY_CAPABILITY"
  349. fi
  350. requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]")
  351. # fetch the user ID if necessary/requested
  352. gpg_fetch_userid "$userID"
  353. # output gpg info for (exact) userid and store
  354. gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
  355. --with-fingerprint --with-fingerprint \
  356. ="$userID" 2>/dev/null)
  357. # if the gpg query return code is not 0, return 1
  358. if [ "$?" -ne 0 ] ; then
  359. log error " no primary keys found."
  360. return 1
  361. fi
  362. # loop over all lines in the gpg output and process.
  363. echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
  364. while IFS=: read -r type validity keyid uidfpr usage ; do
  365. # process based on record type
  366. case $type in
  367. 'pub') # primary keys
  368. # new key, wipe the slate
  369. keyOK=
  370. uidOK=
  371. lastKey=pub
  372. lastKeyOK=
  373. fingerprint=
  374. log error " primary key found: $keyid"
  375. # if overall key is not valid, skip
  376. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  377. log error " - unacceptable primary key validity ($validity)."
  378. continue
  379. fi
  380. # if overall key is disabled, skip
  381. if check_capability "$usage" 'D' ; then
  382. log error " - key disabled."
  383. continue
  384. fi
  385. # if overall key capability is not ok, skip
  386. if ! check_capability "$usage" $requiredPubCapability ; then
  387. log error " - unacceptable primary key capability ($usage)."
  388. continue
  389. fi
  390. # mark overall key as ok
  391. keyOK=true
  392. # mark primary key as ok if capability is ok
  393. if check_capability "$usage" $requiredCapability ; then
  394. lastKeyOK=true
  395. fi
  396. ;;
  397. 'uid') # user ids
  398. if [ "$lastKey" != pub ] ; then
  399. log error " - got a user ID after a sub key?! user IDs should only follow primary keys!"
  400. continue
  401. fi
  402. # if an acceptable user ID was already found, skip
  403. if [ "$uidOK" = 'true' ] ; then
  404. continue
  405. fi
  406. # if the user ID does matches...
  407. if [ "$(echo "$uidfpr" | gpg_unescape)" = "$userID" ] ; then
  408. # and the user ID validity is ok
  409. if [ "$validity" = 'u' -o "$validity" = 'f' ] ; then
  410. # mark user ID acceptable
  411. uidOK=true
  412. fi
  413. else
  414. continue
  415. fi
  416. # output a line for the primary key
  417. # 0 = ok, 1 = bad
  418. if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
  419. log error " * acceptable primary key."
  420. if [ -z "$sshKey" ] ; then
  421. log error " ! primary key could not be translated (not RSA or DSA?)."
  422. else
  423. echo "0:${sshKey}"
  424. fi
  425. else
  426. log error " - unacceptable primary key."
  427. if [ -z "$sshKey" ] ; then
  428. log error " ! primary key could not be translated (not RSA or DSA?)."
  429. else
  430. echo "1:${sshKey}"
  431. fi
  432. fi
  433. ;;
  434. 'sub') # sub keys
  435. # unset acceptability of last key
  436. lastKey=sub
  437. lastKeyOK=
  438. fingerprint=
  439. # don't bother with sub keys if the primary key is not valid
  440. if [ "$keyOK" != true ] ; then
  441. continue
  442. fi
  443. # don't bother with sub keys if no user ID is acceptable:
  444. if [ "$uidOK" != true ] ; then
  445. continue
  446. fi
  447. # if sub key validity is not ok, skip
  448. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  449. continue
  450. fi
  451. # if sub key capability is not ok, skip
  452. if ! check_capability "$usage" $requiredCapability ; then
  453. continue
  454. fi
  455. # mark sub key as ok
  456. lastKeyOK=true
  457. ;;
  458. 'fpr') # key fingerprint
  459. fingerprint="$uidfpr"
  460. sshKey=$(gpg2ssh "$fingerprint")
  461. # if the last key was the pub key, skip
  462. if [ "$lastKey" = pub ] ; then
  463. continue
  464. fi
  465. # output a line for the sub key
  466. # 0 = ok, 1 = bad
  467. if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
  468. log error " * acceptable sub key."
  469. if [ -z "$sshKey" ] ; then
  470. log error " ! sub key could not be translated (not RSA or DSA?)."
  471. else
  472. echo "0:${sshKey}"
  473. fi
  474. else
  475. log error " - unacceptable sub key."
  476. if [ -z "$sshKey" ] ; then
  477. log error " ! sub key could not be translated (not RSA or DSA?)."
  478. else
  479. echo "1:${sshKey}"
  480. fi
  481. fi
  482. ;;
  483. esac
  484. done | sort -t: -k1 -n -r
  485. # NOTE: this last sort is important so that the "good" keys (key
  486. # flag '0') come last. This is so that they take precedence when
  487. # being processed in the key files over "bad" keys (key flag '1')
  488. }
  489. # process a single host in the known_host file
  490. process_host_known_hosts() {
  491. local host
  492. local userID
  493. local nKeys
  494. local nKeysOK
  495. local ok
  496. local sshKey
  497. local tmpfile
  498. host="$1"
  499. userID="ssh://${host}"
  500. log info "processing: $host"
  501. nKeys=0
  502. nKeysOK=0
  503. IFS=$'\n'
  504. for line in $(process_user_id "${userID}") ; do
  505. # note that key was found
  506. nKeys=$((nKeys+1))
  507. ok=$(echo "$line" | cut -d: -f1)
  508. sshKey=$(echo "$line" | cut -d: -f2)
  509. if [ -z "$sshKey" ] ; then
  510. continue
  511. fi
  512. # remove the old host key line, and note if removed
  513. remove_line "$KNOWN_HOSTS" "$sshKey"
  514. # if key OK, add new host line
  515. if [ "$ok" -eq '0' ] ; then
  516. # note that key was found ok
  517. nKeysOK=$((nKeysOK+1))
  518. # hash if specified
  519. if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then
  520. # FIXME: this is really hackish cause ssh-keygen won't
  521. # hash from stdin to stdout
  522. tmpfile=$(mktemp)
  523. ssh2known_hosts "$host" "$sshKey" > "$tmpfile"
  524. ssh-keygen -H -f "$tmpfile" 2> /dev/null
  525. cat "$tmpfile" >> "$KNOWN_HOSTS"
  526. rm -f "$tmpfile" "${tmpfile}.old"
  527. else
  528. ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS"
  529. fi
  530. fi
  531. done
  532. # if at least one key was found...
  533. if [ "$nKeys" -gt 0 ] ; then
  534. # if ok keys were found, return 0
  535. if [ "$nKeysOK" -gt 0 ] ; then
  536. return 0
  537. # else return 2
  538. else
  539. return 2
  540. fi
  541. # if no keys were found, return 1
  542. else
  543. return 1
  544. fi
  545. }
  546. # update the known_hosts file for a set of hosts listed on command
  547. # line
  548. update_known_hosts() {
  549. local nHosts
  550. local nHostsOK
  551. local nHostsBAD
  552. local fileCheck
  553. local host
  554. # the number of hosts specified on command line
  555. nHosts="$#"
  556. nHostsOK=0
  557. nHostsBAD=0
  558. # set the trap to remove any lockfiles on exit
  559. trap "lockfile-remove $KNOWN_HOSTS" EXIT
  560. # create a lockfile on known_hosts
  561. lockfile-create "$KNOWN_HOSTS"
  562. # note pre update file checksum
  563. fileCheck="$(file_hash "$KNOWN_HOSTS")"
  564. for host ; do
  565. # process the host
  566. process_host_known_hosts "$host"
  567. # note the result
  568. case "$?" in
  569. 0)
  570. nHostsOK=$((nHostsOK+1))
  571. ;;
  572. 2)
  573. nHostsBAD=$((nHostsBAD+1))
  574. ;;
  575. esac
  576. # touch the lockfile, for good measure.
  577. lockfile-touch --oneshot "$KNOWN_HOSTS"
  578. done
  579. # remove the lockfile
  580. lockfile-remove "$KNOWN_HOSTS"
  581. # note if the known_hosts file was updated
  582. if [ "$(file_hash "$KNOWN_HOSTS")" != "$fileCheck" ] ; then
  583. log info "known_hosts file updated."
  584. fi
  585. # if an acceptable host was found, return 0
  586. if [ "$nHostsOK" -gt 0 ] ; then
  587. return 0
  588. # else if no ok hosts were found...
  589. else
  590. # if no bad host were found then no hosts were found at all,
  591. # and return 1
  592. if [ "$nHostsBAD" -eq 0 ] ; then
  593. return 1
  594. # else if at least one bad host was found, return 2
  595. else
  596. return 2
  597. fi
  598. fi
  599. }
  600. # process hosts from a known_hosts file
  601. process_known_hosts() {
  602. local hosts
  603. log info "processing known_hosts file..."
  604. hosts=$(meat "$KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | tr , ' ' | tr '\n' ' ')
  605. if [ -z "$hosts" ] ; then
  606. log error "no hosts to process."
  607. return
  608. fi
  609. # take all the hosts from the known_hosts file (first
  610. # field), grep out all the hashed hosts (lines starting
  611. # with '|')...
  612. update_known_hosts $hosts
  613. }
  614. # process uids for the authorized_keys file
  615. process_uid_authorized_keys() {
  616. local userID
  617. local nKeys
  618. local nKeysOK
  619. local ok
  620. local sshKey
  621. userID="$1"
  622. log info "processing: $userID"
  623. nKeys=0
  624. nKeysOK=0
  625. IFS=$'\n'
  626. for line in $(process_user_id "$userID") ; do
  627. # note that key was found
  628. nKeys=$((nKeys+1))
  629. ok=$(echo "$line" | cut -d: -f1)
  630. sshKey=$(echo "$line" | cut -d: -f2)
  631. if [ -z "$sshKey" ] ; then
  632. continue
  633. fi
  634. # remove the old host key line
  635. remove_line "$AUTHORIZED_KEYS" "$sshKey"
  636. # if key OK, add new host line
  637. if [ "$ok" -eq '0' ] ; then
  638. # note that key was found ok
  639. nKeysOK=$((nKeysOK+1))
  640. ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS"
  641. fi
  642. done
  643. # if at least one key was found...
  644. if [ "$nKeys" -gt 0 ] ; then
  645. # if ok keys were found, return 0
  646. if [ "$nKeysOK" -gt 0 ] ; then
  647. return 0
  648. # else return 2
  649. else
  650. return 2
  651. fi
  652. # if no keys were found, return 1
  653. else
  654. return 1
  655. fi
  656. }
  657. # update the authorized_keys files from a list of user IDs on command
  658. # line
  659. update_authorized_keys() {
  660. local userID
  661. local nIDs
  662. local nIDsOK
  663. local nIDsBAD
  664. local fileCheck
  665. # the number of ids specified on command line
  666. nIDs="$#"
  667. nIDsOK=0
  668. nIDsBAD=0
  669. # set the trap to remove any lockfiles on exit
  670. trap "lockfile-remove $AUTHORIZED_KEYS" EXIT
  671. # create a lockfile on authorized_keys
  672. lockfile-create "$AUTHORIZED_KEYS"
  673. # note pre update file checksum
  674. fileCheck="$(file_hash "$AUTHORIZED_KEYS")"
  675. # remove any monkeysphere lines from authorized_keys file
  676. remove_monkeysphere_lines "$AUTHORIZED_KEYS"
  677. for userID ; do
  678. # process the user ID, change return code if key not found for
  679. # user ID
  680. process_uid_authorized_keys "$userID"
  681. # note the result
  682. case "$?" in
  683. 0)
  684. nIDsOK=$((nIDsOK+1))
  685. ;;
  686. 2)
  687. nIDsBAD=$((nIDsBAD+1))
  688. ;;
  689. esac
  690. # touch the lockfile, for good measure.
  691. lockfile-touch --oneshot "$AUTHORIZED_KEYS"
  692. done
  693. # remove the lockfile
  694. lockfile-remove "$AUTHORIZED_KEYS"
  695. # note if the authorized_keys file was updated
  696. if [ "$(file_hash "$AUTHORIZED_KEYS")" != "$fileCheck" ] ; then
  697. log info "authorized_keys file updated."
  698. fi
  699. # if an acceptable id was found, return 0
  700. if [ "$nIDsOK" -gt 0 ] ; then
  701. return 0
  702. # else if no ok ids were found...
  703. else
  704. # if no bad ids were found then no ids were found at all, and
  705. # return 1
  706. if [ "$nIDsBAD" -eq 0 ] ; then
  707. return 1
  708. # else if at least one bad id was found, return 2
  709. else
  710. return 2
  711. fi
  712. fi
  713. }
  714. # process an authorized_user_ids file for authorized_keys
  715. process_authorized_user_ids() {
  716. local line
  717. local nline
  718. local userIDs
  719. authorizedUserIDs="$1"
  720. log info "processing authorized_user_ids file..."
  721. if ! meat "$authorizedUserIDs" > /dev/null ; then
  722. log error "no user IDs to process."
  723. return
  724. fi
  725. nline=0
  726. # extract user IDs from authorized_user_ids file
  727. IFS=$'\n'
  728. for line in $(meat "$authorizedUserIDs") ; do
  729. userIDs["$nline"]="$line"
  730. nline=$((nline+1))
  731. done
  732. update_authorized_keys "${userIDs[@]}"
  733. }