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