1 /*
2  * ProFTPD - mod_sftp interoperability
3  * Copyright (c) 2008-2016 TJ Saunders
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18  *
19  * As a special exemption, TJ Saunders and other respective copyright holders
20  * give permission to link this program with OpenSSL, and distribute the
21  * resulting executable, without including the source code for OpenSSL in the
22  * source distribution.
23  */
24 
25 #include "mod_sftp.h"
26 #include "ssh2.h"
27 #include "channel.h"
28 #include "disconnect.h"
29 #include "interop.h"
30 #include "fxp.h"
31 
32 extern module sftp_module;
33 
34 /* By default, each client is assumed to support all of the features in
35  * which we are interested.
36  */
37 static unsigned int default_flags =
38   SFTP_SSH2_FEAT_IGNORE_MSG |
39   SFTP_SSH2_FEAT_MAC_LEN |
40   SFTP_SSH2_FEAT_CIPHER_USE_K |
41   SFTP_SSH2_FEAT_REKEYING |
42   SFTP_SSH2_FEAT_USERAUTH_BANNER |
43   SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO |
44   SFTP_SSH2_FEAT_SERVICE_IN_HOST_SIG |
45   SFTP_SSH2_FEAT_SERVICE_IN_PUBKEY_SIG |
46   SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG |
47   SFTP_SSH2_FEAT_NO_DATA_WHILE_REKEYING;
48 
49 struct sftp_version_pattern {
50   const char *pattern;
51   int disabled_flags;
52   pr_regex_t *pre;
53 };
54 
55 static struct sftp_version_pattern known_versions[] = {
56 
57   { "^OpenSSH-2\\.0.*|"
58     "^OpenSSH-2\\.1.*|"
59     "^OpenSSH_2\\.1.*|"
60     "^OpenSSH_2\\.2.*|"
61     "^OpenSSH_2\\.3\\.0.*",	SFTP_SSH2_FEAT_USERAUTH_BANNER|
62 				SFTP_SSH2_FEAT_REKEYING,		NULL },
63 
64   { "^OpenSSH_2\\.3\\..*|"
65     "^OpenSSH_2\\.5\\.0p1.*|"
66     "^OpenSSH_2\\.5\\.1p1.*|"
67     "^OpenSSH_2\\.5\\.0.*|"
68     "^OpenSSH_2\\.5\\.1.*|"
69     "^OpenSSH_2\\.5\\.2.*|"
70     "^OpenSSH_2\\.5\\.3.*",	SFTP_SSH2_FEAT_REKEYING,		NULL },
71 
72   { "^OpenSSH.*",		0,					NULL },
73 
74   { ".*J2SSH_Maverick.*",	SFTP_SSH2_FEAT_REKEYING,		NULL },
75 
76   { ".*MindTerm.*",		0,					NULL },
77 
78   { "^Sun_SSH_1\\.0.*",		SFTP_SSH2_FEAT_REKEYING,		NULL },
79 
80   { "^2\\.1\\.0.*|"
81     "^2\\.1 .*",		SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
82 				SFTP_SSH2_FEAT_SERVICE_IN_HOST_SIG|
83 				SFTP_SSH2_FEAT_MAC_LEN,			NULL },
84 
85   { "^2\\.0\\.13.*|"
86     "^2\\.0\\.14.*|"
87     "^2\\.0\\.15.*|"
88     "^2\\.0\\.16.*|"
89     "^2\\.0\\.17.*|"
90     "^2\\.0\\.18.*|"
91     "^2\\.0\\.19.*",		SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
92 				SFTP_SSH2_FEAT_SERVICE_IN_HOST_SIG|
93 				SFTP_SSH2_FEAT_SERVICE_IN_PUBKEY_SIG|
94 				SFTP_SSH2_FEAT_MAC_LEN,			NULL },
95 
96   { "^2\\.0\\.11.*|"
97     "^2\\.0\\.12.*",		SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
98 				SFTP_SSH2_FEAT_SERVICE_IN_PUBKEY_SIG|
99     				SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO|
100 				SFTP_SSH2_FEAT_MAC_LEN,			NULL },
101 
102   { "^2\\.0\\..*",		SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
103 				SFTP_SSH2_FEAT_SERVICE_IN_PUBKEY_SIG|
104     				SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO|
105 				SFTP_SSH2_FEAT_CIPHER_USE_K|
106 				SFTP_SSH2_FEAT_MAC_LEN,			NULL },
107 
108   { "^2\\.2\\.0.*|"
109     "^2\\.3\\.0.*",		SFTP_SSH2_FEAT_MAC_LEN,			NULL },
110 
111 
112   { "^1\\.2\\.18.*|"
113     "^1\\.2\\.19.*|"
114     "^1\\.2\\.20.*|"
115     "^1\\.2\\.21.*|"
116     "^1\\.2\\.22.*|"
117     "^1\\.3\\.2.*|"
118     "^3\\.2\\.9.*",		SFTP_SSH2_FEAT_IGNORE_MSG,		NULL },
119 
120   { ".*PuTTY.*|"
121     ".*PUTTY.*|"
122     ".*WinSCP.*",		SFTP_SSH2_FEAT_NO_DATA_WHILE_REKEYING,	NULL },
123 
124   { ".*SSH_Version_Mapper.*",	SFTP_SSH2_FEAT_SCANNER,			NULL },
125 
126   { "^Probe-.*", 		SFTP_SSH2_FEAT_PROBE,			NULL },
127 
128   { NULL, 0, NULL },
129 };
130 
131 static const char *trace_channel = "ssh2";
132 
sftp_interop_handle_version(pool * p,const char * client_version)133 int sftp_interop_handle_version(pool *p, const char *client_version) {
134   register unsigned int i;
135   size_t version_len;
136   const char *version = NULL;
137   char *ptr = NULL;
138   int is_probe = FALSE, is_scan = FALSE;
139   config_rec *c;
140 
141   if (client_version == NULL) {
142     errno = EINVAL;
143     return -1;
144   }
145 
146   version_len = strlen(client_version);
147 
148   /* The version string MUST conform to the following, as per Section 4.2
149    * of RFC4253:
150    *
151    *  SSH-protoversion-softwareversion [SP comments]
152    *
153    * The 'comments' field is optional.  The 'protoversion' MUST be "2.0".
154    * The 'softwareversion' field MUST be printable ASCII characters and
155    * cannot contain SP or the '-' character.
156    */
157 
158   for (i = 0; i < version_len; i++) {
159     if (!PR_ISPRINT(client_version[i]) &&
160         client_version[i] != '-' &&
161         client_version[i] != ' ') {
162       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
163         "client-sent version contains non-printable or illegal characters, "
164         "disconnecting client");
165       SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
166         NULL);
167     }
168   }
169 
170   /* Skip past the leading "SSH-2.0-" (or "SSH-1.99-") to get the actual
171    * client info.
172    */
173   if (strncmp(client_version, "SSH-2.0-", 8) == 0) {
174     version = pstrdup(p, client_version + 8);
175 
176   } else if (strncmp(client_version, "SSH-1.99-", 9) == 0) {
177     version = pstrdup(p, client_version + 9);
178 
179   } else {
180     /* An illegally formatted client version.  How did it get here? */
181     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
182       "client-sent version (%s) is illegally formmated, disconnecting client",
183       client_version);
184     SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
185       NULL);
186   }
187 
188   /* Look for the optional comments field in the received client version; if
189    * present, trim it out, so that we do not try to match on it.
190    */
191   ptr = strchr(version, ' ');
192   if (ptr != NULL) {
193     pr_trace_msg(trace_channel, 11, "read client version with comments: '%s'",
194       version);
195     *ptr = '\0';
196   }
197 
198   (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
199     "handling connection from SSH2 client '%s'", version);
200   pr_trace_msg(trace_channel, 5, "handling connection from SSH2 client '%s'",
201     version);
202 
203   /* First matching pattern wins. */
204   for (i = 0; known_versions[i].pattern != NULL; i++) {
205     int res;
206 
207     pr_signals_handle();
208 
209     pr_trace_msg(trace_channel, 18,
210       "checking client version '%s' against regex '%s'", version,
211       known_versions[i].pattern);
212 
213     res = pr_regexp_exec(known_versions[i].pre, version, 0, NULL, 0, 0, 0);
214     if (res == 0) {
215       pr_trace_msg(trace_channel, 18,
216         "client version '%s' matched against regex '%s'", version,
217         known_versions[i].pattern);
218 
219       /* We have a match. */
220       default_flags &= ~(known_versions[i].disabled_flags);
221 
222       if (known_versions[i].disabled_flags == SFTP_SSH2_FEAT_PROBE) {
223         is_probe = TRUE;
224       }
225 
226       if (known_versions[i].disabled_flags == SFTP_SSH2_FEAT_SCANNER) {
227         is_scan = TRUE;
228       }
229 
230       break;
231 
232     } else {
233       pr_trace_msg(trace_channel, 18,
234         "client version '%s' did not match regex '%s'", version,
235         known_versions[i].pattern);
236     }
237   }
238 
239   /* Disconnect probes/scans here. */
240   if (is_probe) {
241     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
242       "SSH2 probe from '%s', disconnecting", version);
243 
244     /* We should use the PROTOCOL_VERSION_NOT_SUPPORTED disconnect code,
245      * but for probes/scans, simply hanging up on the client seems better.
246      */
247     pr_session_disconnect(&sftp_module, PR_SESS_DISCONNECT_BY_APPLICATION,
248       NULL);
249   }
250 
251   if (is_scan) {
252     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
253       "SSH2 scan from '%s', disconnecting", version);
254 
255     /* We should use the PROTOCOL_VERSION_NOT_SUPPORTED disconnect code,
256      * but for probes/scans, simply hanging up on the client seems better.
257      */
258     pr_session_disconnect(&sftp_module, PR_SESS_DISCONNECT_BY_APPLICATION,
259       NULL);
260   }
261 
262   /* Now iterate through any SFTPClientMatch rules. */
263 
264   c = find_config(main_server->conf, CONF_PARAM, "SFTPClientMatch", FALSE);
265   while (c) {
266     int res;
267     char *pattern;
268     pr_regex_t *pre;
269 
270     pr_signals_handle();
271 
272     pattern = c->argv[0];
273     pre = c->argv[1];
274 
275     pr_trace_msg(trace_channel, 18,
276       "checking client version '%s' against SFTPClientMatch regex '%s'",
277       version, pattern);
278 
279     res = pr_regexp_exec(pre, version, 0, NULL, 0, 0, 0);
280     if (res == 0) {
281       pr_table_t *tab;
282       const void *v, *v2;
283 
284       /* We have a match. */
285 
286       tab = c->argv[2];
287 
288       /* Look for the following keys:
289        *
290        *  channelWindowSize
291        *  channelPacketSize
292        *  pessimisticNewkeys
293        *  sftpMinProtocolVersion
294        *  sftpMaxProtocolVersion
295        *  sftpUTF8ProtocolVersion (only if NLS support is enabled)
296        */
297 
298       v = pr_table_get(tab, "channelWindowSize", NULL);
299       if (v != NULL) {
300         uint32_t window_size;
301 
302         window_size = *((uint32_t *) v);
303 
304         pr_trace_msg(trace_channel, 16, "setting max server channel window "
305           "size to %lu bytes, as per SFTPClientMatch",
306           (unsigned long) window_size);
307 
308         sftp_channel_set_max_windowsz(window_size);
309       }
310 
311       v = pr_table_get(tab, "channelPacketSize", NULL);
312       if (v != NULL) {
313         uint32_t packet_size;
314 
315         packet_size = *((uint32_t *) v);
316 
317         pr_trace_msg(trace_channel, 16, "setting max server channel packet "
318           "size to %lu bytes, as per SFTPClientMatch",
319           (unsigned long) packet_size);
320 
321         sftp_channel_set_max_packetsz(packet_size);
322       }
323 
324       v = pr_table_get(tab, "pessimisticNewkeys", NULL);
325       if (v != NULL) {
326         int pessimistic_newkeys;
327 
328         pessimistic_newkeys = *((int *) v);
329 
330         pr_trace_msg(trace_channel, 16,
331           "setting pessimistic NEWKEYS behavior to %s, as per SFTPClientMatch",
332           pessimistic_newkeys ? "true" : "false");
333 
334         if (pessimistic_newkeys) {
335           default_flags |= SFTP_SSH2_FEAT_PESSIMISTIC_NEWKEYS;
336         }
337       }
338 
339       v = pr_table_get(tab, "sftpMinProtocolVersion", NULL);
340       v2 = pr_table_get(tab, "sftpMaxProtocolVersion", NULL);
341       if (v != NULL &&
342           v2 != NULL) {
343         unsigned int min_version, max_version;
344 
345         min_version = *((unsigned int *) v);
346         max_version = *((unsigned int *) v2);
347 
348         if (min_version != max_version) {
349           pr_trace_msg(trace_channel, 16, "setting SFTP protocol version "
350             "range %u-%u, as per SFTPClientMatch", min_version,
351             max_version);
352 
353         } else {
354           pr_trace_msg(trace_channel, 16, "setting SFTP protocol version "
355             "%u, as per SFTPClientMatch", min_version);
356         }
357 
358         sftp_fxp_set_protocol_version(min_version, max_version);
359       }
360 
361 #ifdef PR_USE_NLS
362       v = pr_table_get(tab, "sftpUTF8ProtocolVersion", NULL);
363       if (v != NULL) {
364         unsigned int protocol_version;
365 
366         protocol_version = *((unsigned int *) v);
367         pr_trace_msg(trace_channel, 16, "setting SFTP UTF8 protocol version "
368           "%u, as per SFTPClientMatch", protocol_version);
369 
370         sftp_fxp_set_utf8_protocol_version(protocol_version);
371       }
372 #endif /* PR_USE_NLS */
373 
374       /* Once we're done, we can destroy the table. */
375       (void) pr_table_empty(tab);
376       (void) pr_table_free(tab);
377       c->argv[2] = NULL;
378 
379     } else {
380       pr_trace_msg(trace_channel, 18,
381         "client version '%s' did not match SFTPClientMatch regex '%s'", version,
382         pattern);
383     }
384 
385     c = find_config_next(c, c->next, CONF_PARAM, "SFTPClientMatch", FALSE);
386   }
387 
388   return 0;
389 }
390 
sftp_interop_supports_feature(int feat_flag)391 int sftp_interop_supports_feature(int feat_flag) {
392   switch (feat_flag) {
393     case SFTP_SSH2_FEAT_PROBE:
394     case SFTP_SSH2_FEAT_SCANNER:
395       /* Scanners and probes would have been disconnected by now. */
396       return FALSE;
397 
398     default:
399       if (!(default_flags & feat_flag)) {
400         return FALSE;
401       }
402   }
403 
404   return TRUE;
405 }
406 
sftp_interop_init(void)407 int sftp_interop_init(void) {
408   register unsigned int i;
409 
410   /* Compile the regexps for all of the known client versions, to save the
411    * time when a client connects.
412    */
413   for (i = 0; known_versions[i].pattern != NULL; i++) {
414     pr_regex_t *pre;
415     int res;
416 
417     pr_signals_handle();
418 
419     pre = pr_regexp_alloc(&sftp_module);
420 
421     res = pr_regexp_compile(pre, known_versions[i].pattern,
422       REG_EXTENDED|REG_NOSUB);
423     if (res != 0) {
424       char errmsg[256];
425 
426       memset(errmsg, '\0', sizeof(errmsg));
427       pr_regexp_error(res, pre, errmsg, sizeof(errmsg));
428       pr_regexp_free(NULL, pre);
429 
430       pr_log_debug(DEBUG0, MOD_SFTP_VERSION
431         ": error compiling regex pattern '%s' (known_versions[%u]): %s",
432         known_versions[i].pattern, i, errmsg);
433       continue;
434     }
435 
436     known_versions[i].pre = pre;
437   }
438 
439   return 0;
440 }
441 
sftp_interop_free(void)442 int sftp_interop_free(void) {
443   register unsigned int i;
444 
445   for (i = 0; known_versions[i].pattern != NULL; i++) {
446     if (known_versions[i].pre != NULL) {
447       pr_regexp_free(NULL, known_versions[i].pre);
448       known_versions[i].pre = NULL;
449     }
450   }
451 
452   return 0;
453 }
454