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