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