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