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