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