some small compatibility changes:
[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 subcommand
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 # "marginal case" ouput in the case that there is not a full
19 # validation path to the host
20 output_no_valid_key() {
21     local userID
22     local sshKeyOffered
23     local gpgOut
24     local type
25     local validity
26     local keyid
27     local uidfpr
28     local usage
29     local sshKeyGPG
30     local tmpkey
31     local sshFingerprint
32     local gpgSigOut
33     local returnCode=0
34
35     userID="ssh://${HOSTP}"
36
37     LOG_PREFIX=
38
39     # retrieve the ssh key being offered by the host
40     sshKeyOffered=$(ssh-keyscan -t rsa -p "$PORT" "$HOST" 2>/dev/null \
41         | awk '{ print $2, $3 }')
42
43     # get the gpg info for userid
44     gpgOut=$(gpg_user --list-key --fixed-list-mode --with-colon \
45         --with-fingerprint --with-fingerprint \
46         ="$userID" 2>/dev/null)
47
48     # output header
49     log info <<EOF
50 -------------------- Monkeysphere warning -------------------
51 Monkeysphere found OpenPGP keys for this hostname, but none had full validity.
52 EOF
53
54     # if the host key is retrieved from the host, check against known
55     # OpenPGP keys
56     if [ "$sshKeyOffered" ] ; then
57         # find all 'pub' and 'sub' lines in the gpg output, which each
58         # represent a retrieved key for the user ID
59         echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
60         while IFS=: read -r type validity keyid uidfpr usage ; do
61             case $type in
62                 'pub'|'sub')
63                     # get the ssh key of the gpg key
64                     sshKeyGPG=$(gpg2ssh "$keyid")
65
66                     # if one of keys found matches the one offered by the
67                     # host, then output info
68                     if [ "$sshKeyGPG" = "$sshKeyOffered" ] ; then
69                         log info <<EOF
70 An OpenPGP key matching the ssh key offered by the host was found:
71
72 EOF
73
74                         sshKeyGPGFile=$(msmktempfile)
75                         printf "%s" "$sshKeyGPG" >"$sshKeyGPGFile"
76                         sshFingerprint=$(ssh-keygen -l -f "$sshKeyGPGFile" | \
77                             awk '{ print $2 }')
78                         rm -f "$sshKeyGPGFile"
79
80                         # get the sigs for the matching key
81                         gpgSigOut=$(gpg_user --check-sigs \
82                             --list-options show-uid-validity \
83                             "$keyid")
84
85                         # output the sigs, but only those on the user ID
86                         # we are looking for
87                         echo "$gpgSigOut" | awk '
88 {
89 if (match($0,"^pub")) { print; }
90 if (match($0,"^uid")) { ok=0; }
91 if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
92 if (ok) { if (match($0,"^sig")) { print; } }
93 }
94 ' | log info
95                         echo | log info
96
97                         # output the other user IDs for reference
98                         if (echo "$gpgSigOut" | grep "^uid" | grep -v -q "$userID") ; then
99                             log info <<EOF
100 Other user IDs on this key:
101
102 EOF
103                             echo "$gpgSigOut" | grep "^uid" | grep -v "$userID" | log info
104                             echo | log info
105                         fi
106
107                         # output ssh fingerprint
108                         log info <<EOF
109 RSA key fingerprint is ${sshFingerprint}.
110 EOF
111
112                         # this whole process is in a "while read"
113                         # subshell.  the only way to get information
114                         # out of the subshell is to change the return
115                         # code.  therefore we return 1 here to
116                         # indicate that a matching gpg key was found
117                         # for the ssh key offered by the host
118                         return 1
119                     fi
120                     ;;
121             esac
122         done || returnCode="$?"
123
124         # if no key match was made (and the "while read" subshell
125         # returned 1) output how many keys were found
126         if (( returnCode != 1 )) ; then
127             log info <<EOF
128 None of the found keys matched the key offered by the host.
129 Run the following command for more info about the found keys:
130 gpg --check-sigs --list-options show-uid-validity =${userID}
131 EOF
132
133             # FIXME: should we do anything extra here if the retrieved
134             # host key is actually in the known_hosts file and the ssh
135             # connection will succeed?  Should the user be warned?
136             # prompted?
137         fi
138
139     # if host key could not be retrieved from the host, output message
140     else
141         log info <<EOF
142 Could not retrieve RSA host key from $HOST.
143 EOF
144     fi
145
146     # output footer
147     log info <<EOF
148 -------------------- ssh continues below --------------------
149 EOF
150 }
151
152
153 # the ssh proxycommand function itself
154 ssh_proxycommand() {
155
156 if [ "$1" = '--no-connect' ] ; then
157     NO_CONNECT='true'
158     shift 1
159 fi
160
161 HOST="$1"
162 PORT="$2"
163
164 if [ -z "$HOST" ] ; then
165     log error "Host not specified."
166     usage
167     exit 255
168 fi
169 if [ -z "$PORT" ] ; then
170     PORT=22
171 fi
172
173 # set the host URI
174 if [ "$PORT" != '22' ] ; then
175     HOSTP="${HOST}:${PORT}"
176 else
177     HOSTP="${HOST}"
178 fi
179 URI="ssh://${HOSTP}"
180
181 # specify keyserver checking.  the behavior of this proxy command is
182 # intentionally different than that of running monkeyesphere normally,
183 # and keyserver checking is intentionally done under certain
184 # circumstances.  This can be overridden by setting the
185 # MONKEYSPHERE_CHECK_KEYSERVER environment variable, or by setting the
186 # CHECK_KEYSERVER variable in the monkeysphere.conf file.
187
188 # if the host is in the gpg keyring...
189 if gpg_user --list-key ="${URI}" &>/dev/null ; then
190     # do not check the keyserver
191     CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
192
193 # if the host is NOT in the keyring...
194 else
195     # if the host key is found in the known_hosts file...
196     # FIXME: this only works for default known_hosts location
197     hostKey=$(ssh-keygen -F "$HOST" 2>/dev/null)
198
199     if [ "$hostKey" ] ; then
200         # do not check the keyserver
201         # FIXME: more nuanced checking should be done here to properly
202         # take into consideration hosts that join monkeysphere by
203         # converting an existing and known ssh key
204         CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
205
206     # if the host key is not found in the known_hosts file...
207     else
208         # check the keyserver
209         CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
210     fi
211 fi
212
213 # finally look in the MONKEYSPHERE_ environment variable for a
214 # CHECK_KEYSERVER setting to override all else
215 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=$CHECK_KEYSERVER}
216
217 # update the known_hosts file for the host
218 local returnCode=0
219 update_known_hosts "$HOSTP" || returnCode="$?"
220
221 # output on depending on the return of the update-known_hosts
222 # subcommand, which is (ultimately) the return code of the
223 # update_known_hosts function in common
224 case "$returnCode" in
225     0)
226         # acceptable host key found so continue to ssh
227         true
228         ;;
229     1)
230         # no hosts at all found so also continue (drop through to
231         # regular ssh host verification)
232         true
233         ;;
234     2)
235         # at least one *bad* host key (and no good host keys) was
236         # found, so output some usefull information
237         output_no_valid_key
238         ;;
239     *)
240         # anything else drop through
241         true
242         ;;
243 esac
244
245 # FIXME: what about the case where monkeysphere successfully finds a
246 # valid key for the host and adds it to the known_hosts file, but a
247 # different non-monkeysphere key for the host already exists in the
248 # known_hosts, and it is this non-ms key that is offered by the host?
249 # monkeysphere will succeed, and the ssh connection will succeed, and
250 # the user will be left with the impression that they are dealing with
251 # a OpenPGP/PKI host key when in fact they are not.  should we use
252 # ssh-keyscan to compare the keys first?
253
254 # exec a netcat passthrough to host for the ssh connection
255 if [ -z "$NO_CONNECT" ] ; then
256     if (type nc &>/dev/null); then
257         exec nc "$HOST" "$PORT"
258     elif (type socat &>/dev/null); then
259         exec socat STDIO "TCP:$HOST:$PORT"
260     else
261         echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2
262         exit 255
263     fi
264 fi
265
266 }