summaryrefslogtreecommitdiff
path: root/src/common
blob: ff6ba5943ae9e27842d159513836ac6b57d5a5d3 (plain)
  1. # -*-shell-script-*-
  2. # Shared bash 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. # managed directories
  12. ETC="/etc/monkeysphere"
  13. export ETC
  14. LIB="/var/lib/monkeysphere"
  15. export LIB
  16. ########################################################################
  17. failure() {
  18. echo "$1" >&2
  19. exit ${2:-'1'}
  20. }
  21. # write output to stdout
  22. log() {
  23. echo -n "ms: "
  24. echo "$@"
  25. }
  26. # write output to stderr
  27. loge() {
  28. echo -n "ms: " 1>&2
  29. echo "$@" 1>&2
  30. }
  31. # cut out all comments(#) and blank lines from standard input
  32. meat() {
  33. grep -v -e "^[[:space:]]*#" -e '^$'
  34. }
  35. # cut a specified line from standard input
  36. cutline() {
  37. head --line="$1" | tail -1
  38. }
  39. # retrieve all keys with given user id from keyserver
  40. # FIXME: need to figure out how to retrieve all matching keys
  41. # (not just first 5)
  42. gpg_fetch_keys() {
  43. local id
  44. id="$1"
  45. echo 1,2,3,4,5 | \
  46. gpg --quiet --batch --command-fd 0 --with-colons \
  47. --keyserver "$KEYSERVER" \
  48. --search ="$id" >/dev/null 2>&1
  49. }
  50. # check that characters are in a string (in an AND fashion).
  51. # used for checking key capability
  52. # check_capability capability a [b...]
  53. check_capability() {
  54. local capability
  55. local capcheck
  56. capability="$1"
  57. shift 1
  58. for capcheck ; do
  59. if echo "$capability" | grep -q -v "$capcheck" ; then
  60. return 1
  61. fi
  62. done
  63. return 0
  64. }
  65. # convert escaped characters from gpg output back into original
  66. # character
  67. # FIXME: undo all escape character translation in with-colons gpg output
  68. unescape() {
  69. echo "$1" | sed 's/\\x3a/:/'
  70. }
  71. # stand in until we get dkg's gpg2ssh program
  72. gpg2ssh_tmp() {
  73. local keyID
  74. local userID
  75. local host
  76. keyID="$1"
  77. userID="$2"
  78. if [ "$MODE" = 'authorized_keys' ] ; then
  79. gpgkey2ssh "$keyID" | sed -e "s/COMMENT/MonkeySphere userID: ${userID}/"
  80. # NOTE: it seems that ssh-keygen -R removes all comment fields from
  81. # all lines in the known_hosts file. why?
  82. # NOTE: just in case, the COMMENT can be matched with the
  83. # following regexp:
  84. # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
  85. elif [ "$MODE" = 'known_hosts' ] ; then
  86. host=$(echo "$userID" | sed -e "s|ssh://||")
  87. echo -n "$host "; gpgkey2ssh "$keyID" | sed -e "s/COMMENT/MonkeySphere${DATE}/"
  88. fi
  89. }
  90. # userid and key policy checking
  91. # the following checks policy on the returned keys
  92. # - checks that full key has appropriate valididy (u|f)
  93. # - checks key has specified capability (REQUIRED_KEY_CAPABILITY)
  94. # - checks that particular desired user id has appropriate validity
  95. # see /usr/share/doc/gnupg/DETAILS.gz
  96. # expects global variable: "MODE"
  97. process_user_id() {
  98. local userID
  99. local cacheDir
  100. local requiredPubCapability
  101. local gpgOut
  102. local line
  103. local type
  104. local validity
  105. local keyid
  106. local uidfpr
  107. local capability
  108. local keyOK
  109. local pubKeyID
  110. local uidOK
  111. local keyIDs
  112. local userIDHash
  113. local keyID
  114. userID="$1"
  115. cacheDir="$2"
  116. requiredPubCapability=$(echo "$REQUIRED_KEY_CAPABILITY" | tr "[:lower:]" "[:upper:]")
  117. # fetch keys from keyserver, return 1 if none found
  118. gpg_fetch_keys "$userID" || return 1
  119. # output gpg info for (exact) userid and store
  120. gpgOut=$(gpg --fixed-list-mode --list-key --with-colons \
  121. ="$userID" 2> /dev/null)
  122. # return 1 if there only "tru" lines are output from gpg
  123. if [ -z "$(echo "$gpgOut" | grep -v '^tru:')" ] ; then
  124. loge " key not found."
  125. return 1
  126. fi
  127. # loop over all lines in the gpg output and process.
  128. # need to do it this way (as opposed to "while read...") so that
  129. # variables set in loop will be visible outside of loop
  130. for line in $(seq 1 $(echo "$gpgOut" | wc -l)) ; do
  131. # read the contents of the line
  132. type=$(echo "$gpgOut" | cutline "$line" | cut -d: -f1)
  133. validity=$(echo "$gpgOut" | cutline "$line" | cut -d: -f2)
  134. keyid=$(echo "$gpgOut" | cutline "$line" | cut -d: -f5)
  135. uidfpr=$(echo "$gpgOut" | cutline "$line" | cut -d: -f10)
  136. capability=$(echo "$gpgOut" | cutline "$line" | cut -d: -f12)
  137. # process based on record type
  138. case $type in
  139. 'pub') # primary keys
  140. # new key, wipe the slate
  141. keyOK=
  142. pubKeyID=
  143. uidOK=
  144. keyIDs=
  145. pubKeyID="$keyid"
  146. # check primary key validity
  147. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  148. loge " unacceptable primary key validity ($validity)."
  149. continue
  150. fi
  151. # check capability is not Disabled...
  152. if check_capability "$capability" 'D' ; then
  153. loge " key disabled."
  154. continue
  155. fi
  156. # check overall key capability
  157. # must be Encryption and Authentication
  158. if ! check_capability "$capability" $requiredPubCapability ; then
  159. loge " unacceptable primary key capability ($capability)."
  160. continue
  161. fi
  162. # mark if primary key is acceptable
  163. keyOK=true
  164. # add primary key ID to key list if it has required capability
  165. if check_capability "$capability" $REQUIRED_KEY_CAPABILITY ; then
  166. keyIDs[${#keyIDs[*]}]="$keyid"
  167. fi
  168. ;;
  169. 'uid') # user ids
  170. # check key ok and we have key fingerprint
  171. if [ -z "$keyOK" ] ; then
  172. continue
  173. fi
  174. # check key validity
  175. if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
  176. continue
  177. fi
  178. # check the uid matches
  179. if [ "$(unescape "$uidfpr")" != "$userID" ] ; then
  180. continue
  181. fi
  182. # mark if uid acceptable
  183. uidOK=true
  184. ;;
  185. 'sub') # sub keys
  186. # add sub key ID to key list if it has required capability
  187. if check_capability "$capability" $REQUIRED_KEY_CAPABILITY ; then
  188. keyIDs[${#keyIDs[*]}]="$keyid"
  189. fi
  190. ;;
  191. esac
  192. done
  193. # hash userid for cache file name
  194. userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }')
  195. # touch/clear key cache file
  196. # (will be left empty if there are noacceptable keys)
  197. > "$cacheDir"/"$userIDHash"."$pubKeyID"
  198. # for each acceptable key, write an ssh key line to the
  199. # key cache file
  200. if [ "$keyOK" -a "$uidOK" -a "${keyIDs[*]}" ] ; then
  201. for keyID in ${keyIDs[@]} ; do
  202. loge " acceptable key/uid found."
  203. # export the key with gpg2ssh
  204. # FIXME: needs to apply extra options for authorized_keys
  205. # lines if specified
  206. gpg2ssh_tmp "$keyID" "$userID" >> "$cacheDir"/"$userIDHash"."$pubKeyID"
  207. # hash the cache file if specified
  208. if [ "$MODE" = 'known_hosts' -a "$HASH_KNOWN_HOSTS" ] ; then
  209. ssh-keygen -H -f "$cacheDir"/"$userIDHash"."$pubKeyID" > /dev/null 2>&1
  210. rm "$cacheDir"/"$userIDHash"."$pubKeyID".old
  211. fi
  212. done
  213. fi
  214. # echo the path to the key cache file
  215. echo "$cacheDir"/"$userIDHash"."$pubKeyID"
  216. }
  217. # process a host for addition to a known_host file
  218. process_host() {
  219. local host
  220. local cacheDir
  221. local hostKeyCachePath
  222. host="$1"
  223. cacheDir="$2"
  224. log "processing host: '$host'"
  225. hostKeyCachePath=$(process_user_id "ssh://${host}" "$cacheDir")
  226. if [ $? = 0 ] ; then
  227. ssh-keygen -R "$host" -f "$USER_KNOWN_HOSTS"
  228. cat "$hostKeyCachePath" >> "$USER_KNOWN_HOSTS"
  229. fi
  230. }
  231. # process known_hosts file
  232. # go through line-by-line, extract each host, and process with the
  233. # host processing function
  234. process_known_hosts() {
  235. local knownHosts
  236. local cacheDir
  237. local hosts
  238. local host
  239. knownHosts="$1"
  240. cacheDir="$2"
  241. # take all the hosts from the known_hosts file (first field),
  242. # grep out all the hashed hosts (lines starting with '|')
  243. cut -d ' ' -f 1 "$knownHosts" | \
  244. grep -v '^|.*$' | \
  245. while IFS=, read -r -a hosts ; do
  246. # process each host
  247. for host in ${hosts[*]} ; do
  248. process_host "$host" "$cacheDir"
  249. done
  250. done
  251. }
  252. # update an authorized_keys file after first processing the
  253. # authorized_user_ids file
  254. update_authorized_keys() {
  255. local cacheDir
  256. local msAuthorizedKeys
  257. local userAuthorizedKeys
  258. cacheDir="$1"
  259. msAuthorizedKeys="$2"
  260. userAuthorizedKeys="$3"
  261. process_authorized_ids "$AUTHORIZED_USER_IDS" "$cacheDir"
  262. # write output key file
  263. log "writing monkeysphere authorized_keys file... "
  264. touch "$msAuthorizedKeys"
  265. if [ "$(ls "$cacheDir")" ] ; then
  266. log -n "adding gpg keys... "
  267. cat "$cacheDir"/* > "$msAuthorizedKeys"
  268. echo "done."
  269. else
  270. log "no gpg keys to add."
  271. fi
  272. if [ "$userAuthorizedKeys" -a -s "$userAuthorizedKeys" ] ; then
  273. log -n "adding user authorized_keys file... "
  274. cat "$userAuthorizedKeys" >> "$msAuthorizedKeys"
  275. echo "done."
  276. fi
  277. log "monkeysphere authorized_keys file generated: $msAuthorizedKeys"
  278. }
  279. # process an authorized_*_ids file
  280. # go through line-by-line, extract each userid, and process
  281. process_authorized_ids() {
  282. local authorizedIDs
  283. local cacheDir
  284. local userID
  285. authorizedIDs="$1"
  286. cacheDir="$2"
  287. # clean out keys file and remake keys directory
  288. rm -rf "$cacheDir"
  289. mkdir -p "$cacheDir"
  290. # loop through all user ids in file
  291. # FIXME: needs to handle authorized_keys options
  292. cat "$authorizedIDs" | meat | \
  293. while read -r userID ; do
  294. # process the userid
  295. log "processing userid: '$userID'"
  296. process_user_id "$userID" "$cacheDir" > /dev/null
  297. done
  298. }
  299. # EXPERIMENTAL (unused) process userids found in authorized_keys file
  300. # go through line-by-line, extract monkeysphere userids from comment
  301. # fields, and process each userid
  302. process_userids_from_authorized_keys() {
  303. local authorizedKeys
  304. local cacheDir
  305. local userID
  306. authorizedKeys="$1"
  307. cacheDir="$2"
  308. # take all the monkeysphere userids from the authorized_keys file
  309. # comment field (third field) that starts with "MonkeySphere uid:"
  310. # FIXME: needs to handle authorized_keys options (field 0)
  311. cat "$authorizedKeys" | \
  312. while read -r options keytype key comment ; do
  313. # if the comment field is empty, assume the third field was
  314. # the comment
  315. if [ -z "$comment" ] ; then
  316. comment="$key"
  317. fi
  318. if ! echo "$comment" | grep '^MonkeySphere userID:.*$' ; then
  319. continue
  320. fi
  321. userID=$(echo "$comment" | sed -e "/^MonkeySphere userID://")
  322. if [ -z "$userID" ] ; then
  323. continue
  324. fi
  325. # process the userid
  326. log "processing userid: '$userID'"
  327. process_user_id "$userID" "$cacheDir" > /dev/null
  328. done
  329. }
  330. # update the cache for userid, and prompt to add file to
  331. # authorized_user_ids file if the userid is found in gpg
  332. # and not already in file.
  333. update_userid() {
  334. local userID
  335. local cacheDir
  336. local userIDKeyCache
  337. userID="$1"
  338. cacheDir="$2"
  339. log "processing userid: '$userID'"
  340. userIDKeyCache=$(process_user_id "$userID" "$cacheDir")
  341. if [ -z "$userIDKeyCache" ] ; then
  342. return 1
  343. fi
  344. if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
  345. echo "the following userid is not in the authorized_user_ids file:"
  346. echo " $userID"
  347. read -p "would you like to add? [Y|n]: " OK; OK=${OK:=Y}
  348. if [ ${OK/y/Y} = 'Y' ] ; then
  349. log -n " adding userid to authorized_user_ids file... "
  350. echo "$userID" >> "$AUTHORIZED_USER_IDS"
  351. echo "done."
  352. fi
  353. fi
  354. }
  355. # retrieve key from web of trust, and set owner trust to "full"
  356. # if key is found.
  357. trust_key() {
  358. # get the key from the key server
  359. gpg --keyserver "$KEYSERVER" --recv-key "$keyID" || failure "could not retrieve key '$keyID'"
  360. # edit the key to change trust
  361. # FIXME: need to figure out how to automate this,
  362. # in a batch mode or something.
  363. gpg --edit-key "$keyID"
  364. }