summaryrefslogtreecommitdiff
path: root/src/common
blob: 8643080f595a67aee3f6e46aa1e4922a8a9bc2e6 (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="$2"
  77. userID="$3"
  78. if [ "$mode" = 'authorized_keys' ] ; then
  79. gpgkey2ssh "$keyID" | sed -e "s/COMMENT/${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. # process authorized_keys file
  253. # go through line-by-line, extract monkeysphere userids from comment
  254. # fields, and process each userid
  255. process_authorized_keys() {
  256. local authorizedKeys
  257. local cacheDir
  258. local userID
  259. authorizedKeys="$1"
  260. cacheDir="$2"
  261. # take all the monkeysphere userids from the authorized_keys file
  262. # comment field (third field) that starts with "MonkeySphere uid:"
  263. # FIXME: needs to handle authorized_keys options (field 0)
  264. cat "$authorizedKeys" | \
  265. while read -r options keytype key comment ; do
  266. # if the comment field is empty, assume the third field was
  267. # the comment
  268. if [ -z "$comment" ] ; then
  269. comment="$key"
  270. fi
  271. if ! echo "$comment" | grep '^MonkeySphere userID:.*$' ; then
  272. continue
  273. fi
  274. userID=$(echo "$comment" | sed -e "/^MonkeySphere userID://")
  275. if [ -z "$userID" ] ; then
  276. continue
  277. fi
  278. # process the userid
  279. log "processing userid: '$userID'"
  280. process_user_id "$userID" "$cacheDir" > /dev/null
  281. done
  282. }
  283. # process an authorized_*_ids file
  284. # go through line-by-line, extract each userid, and process
  285. process_authorized_ids() {
  286. local authorizedIDs
  287. local cacheDir
  288. local userID
  289. authorizedIDs="$1"
  290. cacheDir="$2"
  291. # clean out keys file and remake keys directory
  292. rm -rf "$cacheDir"
  293. mkdir -p "$cacheDir"
  294. # loop through all user ids in file
  295. # FIXME: needs to handle authorized_keys options
  296. cat "$authorizedIDs" | meat | \
  297. while read -r userID ; do
  298. # process the userid
  299. log "processing userid: '$userID'"
  300. process_user_id "$userID" "$cacheDir" > /dev/null
  301. done
  302. }