move src/subcommands to srv/share, and add common file to src/share (update Makefile...
[monkeysphere.git] / src / share / m / ssh_proxycommand
1 # -*-shell-script-*-
2 # This should be sourced by bash (though we welcome changes to make it POSIX sh compliant)
3
4 # monkeysphere-ssh-proxycommand: MonkeySphere ssh ProxyCommand hook
5 #
6 # The monkeysphere scripts are written by:
7 # Jameson Rollins <jrollins@finestructure.net>
8 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
9 #
10 # They are Copyright 2008-2009, and are all released under the GPL,
11 # version 3 or later.
12
13 # This is meant to be run as an ssh ProxyCommand to initiate a
14 # monkeysphere known_hosts update before an ssh connection to host is
15 # established.  Can be added to ~/.ssh/config as follows:
16 #  ProxyCommand monkeysphere-ssh-proxycommand %h %p
17
18 ssh_proxycommand() {
19
20 # "marginal case" ouput in the case that there is not a full
21 # validation path to the host
22 output_no_valid_key() {
23     local sshKeyOffered
24     local userID
25     local type
26     local validity
27     local keyid
28     local uidfpr
29     local usage
30     local sshKeyGPG
31     local tmpkey
32     local sshFingerprint
33     local gpgSigOut
34
35     userID="ssh://${HOSTP}"
36
37     log "-------------------- Monkeysphere warning -------------------"
38     log "Monkeysphere found OpenPGP keys for this hostname, but none had full validity."
39
40     # retrieve the actual ssh key
41     sshKeyOffered=$(ssh-keyscan -t rsa -p "$PORT" "$HOST" 2>/dev/null | awk '{ print $2, $3 }')
42     # FIXME: should we do any checks for failed keyscans, eg. host not
43     # found?
44
45     # get the gpg info for userid
46     gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
47         --with-fingerprint --with-fingerprint \
48         ="$userID" 2>/dev/null)
49
50     # find all 'pub' and 'sub' lines in the gpg output, which each
51     # represent a retrieved key for the user ID
52     echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
53     while IFS=: read -r type validity keyid uidfpr usage ; do
54         case $type in
55             'pub'|'sub')
56                 # get the ssh key of the gpg key
57                 sshKeyGPG=$(gpg2ssh "$keyid")
58
59                 # if one of keys found matches the one offered by the
60                 # host, then output info
61                 if [ "$sshKeyGPG" = "$sshKeyOffered" ] ; then
62                     log "An OpenPGP key matching the ssh key offered by the host was found:"
63                     log
64
65                     # do some crazy "Here Strings" redirection to get the key to
66                     # ssh-keygen, since it doesn't read from stdin cleanly
67                     sshFingerprint=$(ssh-keygen -l -f /dev/stdin \
68                         <<<$(echo "$sshKeyGPG") | \
69                         awk '{ print $2 }')
70
71                     # get the sigs for the matching key
72                     gpgSigOut=$(gpg --check-sigs \
73                         --list-options show-uid-validity \
74                         "$keyid")
75
76                     # output the sigs, but only those on the user ID
77                     # we are looking for
78                     echo "$gpgSigOut" | awk '
79 {
80 if (match($0,"^pub")) { print; }
81 if (match($0,"^uid")) { ok=0; }
82 if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
83 if (ok) { if (match($0,"^sig")) { print; } }
84 }
85 ' >&2
86                     log
87
88                     # output the other user IDs for reference
89                     if (echo "$gpgSigOut" | grep "^uid" | grep -v -q "$userID") ; then
90                         log "Other user IDs on this key:"
91                         echo "$gpgSigOut" | grep "^uid" | grep -v "$userID" >&2
92                         log
93                     fi
94
95                     # output ssh fingerprint
96                     log "RSA key fingerprint is ${sshFingerprint}."
97
98                     # this whole process is in a "while read"
99                     # subshell.  the only way to get information out
100                     # of the subshell is to change the return code.
101                     # therefore we return 1 here to indicate that a
102                     # matching gpg key was found for the ssh key
103                     # offered by the host
104                     return 1
105                 fi
106                 ;;
107         esac
108     done
109
110     # if no key match was made (and the "while read" subshell returned
111     # 1) output how many keys were found
112     if (($? != 1)) ; then
113         log "None of the found keys matched the key offered by the host."
114         log "Run the following command for more info about the found keys:"
115         log "gpg --check-sigs --list-options show-uid-validity =${userID}"
116         # FIXME: should we do anything extra here if the retrieved
117         # host key is actually in the known_hosts file and the ssh
118         # connection will succeed?  Should the user be warned?
119         # prompted?
120     fi
121
122     log "-------------------- ssh continues below --------------------"
123 }
124
125 ########################################################################
126
127 # export the monkeysphere log level
128 export MONKEYSPHERE_LOG_LEVEL
129
130 if [ "$1" = '--no-connect' ] ; then
131     NO_CONNECT='true'
132     shift 1
133 fi
134
135 HOST="$1"
136 PORT="$2"
137
138 if [ -z "$HOST" ] ; then
139     log "Host not specified."
140     usage
141     exit 255
142 fi
143 if [ -z "$PORT" ] ; then
144     PORT=22
145 fi
146
147 # set the host URI
148 if [ "$PORT" != '22' ] ; then
149     HOSTP="${HOST}:${PORT}"
150 else
151     HOSTP="${HOST}"
152 fi
153 URI="ssh://${HOSTP}"
154
155 # specify keyserver checking.  the behavior of this proxy command is
156 # intentionally different than that of running monkeyesphere normally,
157 # and keyserver checking is intentionally done under certain
158 # circumstances.  This can be overridden by setting the
159 # MONKEYSPHERE_CHECK_KEYSERVER environment variable.
160
161 # if the host is in the gpg keyring...
162 if gpg --list-key ="${URI}" 2>&1 >/dev/null ; then
163     # do not check the keyserver
164     CHECK_KEYSERVER="false"
165
166 # if the host is NOT in the keyring...
167 else
168     # if the host key is found in the known_hosts file...
169     # FIXME: this only works for default known_hosts location
170     hostKey=$(ssh-keygen -F "$HOST" 2>/dev/null)
171
172     if [ "$hostKey" ] ; then
173         # do not check the keyserver
174         # FIXME: more nuanced checking should be done here to properly
175         # take into consideration hosts that join monkeysphere by
176         # converting an existing and known ssh key
177         CHECK_KEYSERVER="false"
178
179     # if the host key is not found in the known_hosts file...
180     else
181         # check the keyserver
182         CHECK_KEYSERVER="true"
183     fi
184 fi
185 # set and export the variable for use by monkeysphere
186 MONKEYSPHERE_CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="$CHECK_KEYSERVER"}
187 export MONKEYSPHERE_CHECK_KEYSERVER
188
189 # update the known_hosts file for the host
190 monkeysphere update-known_hosts "$HOSTP"
191
192 # output on depending on the return of the update-known_hosts
193 # subcommand, which is (ultimately) the return code of the
194 # update_known_hosts function in common
195 case $? in
196     0)
197         # acceptable host key found so continue to ssh
198         true
199         ;;
200     1)
201         # no hosts at all found so also continue (drop through to
202         # regular ssh host verification)
203         true
204         ;;
205     2)
206         # at least one *bad* host key (and no good host keys) was
207         # found, so output some usefull information
208         output_no_valid_key
209         ;;
210     *)
211         # anything else drop through
212         true
213         ;;
214 esac
215
216 # FIXME: what about the case where monkeysphere successfully finds a
217 # valid key for the host and adds it to the known_hosts file, but a
218 # different non-monkeysphere key for the host already exists in the
219 # known_hosts, and it is this non-ms key that is offered by the host?
220 # monkeysphere will succeed, and the ssh connection will succeed, and
221 # the user will be left with the impression that they are dealing with
222 # a OpenPGP/PKI host key when in fact they are not.  should we use
223 # ssh-keyscan to compare the keys first?
224
225 # exec a netcat passthrough to host for the ssh connection
226 if [ -z "$NO_CONNECT" ] ; then
227     if (which nc 2>/dev/null >/dev/null); then
228         exec nc "$HOST" "$PORT"
229     elif (which socat 2>/dev/null >/dev/null); then
230         exec socat STDIO "TCP:$HOST:$PORT"
231     else
232         echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2
233         exit 255
234     fi
235 fi
236
237 }