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