1 /*
2 * ProFTPD - mod_sftp compression
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
27 #include "msg.h"
28 #include "packet.h"
29 #include "crypto.h"
30 #include "compress.h"
31
32 #ifdef HAVE_ZLIB_H
33 #include <zlib.h>
34
35 static const char *trace_channel = "ssh2";
36
37 struct sftp_compress {
38 int use_zlib;
39 int stream_ready;
40 };
41
42 /* We need to keep the old compression contexts around, so that we can handle
43 * N arbitrary packets to/from the client using the old contexts, as during
44 * rekeying. Thus we have two read compression contexts, two write compression
45 * contexts. The compression idx variable indicates which of the contexts is
46 * currently in use.
47 */
48
49 static struct sftp_compress read_compresses[] = {
50 { FALSE, FALSE },
51 { FALSE, FALSE }
52 };
53 static z_stream read_streams[2];
54
55 static struct sftp_compress write_compresses[] = {
56 { FALSE, FALSE },
57 { FALSE, FALSE }
58 };
59 static z_stream write_streams[2];
60
61 static unsigned int read_comp_idx = 0;
62 static unsigned int write_comp_idx = 0;
63
get_next_read_index(void)64 static unsigned int get_next_read_index(void) {
65 if (read_comp_idx == 1) {
66 return 0;
67 }
68
69 return 1;
70 }
71
get_next_write_index(void)72 static unsigned int get_next_write_index(void) {
73 if (write_comp_idx == 1) {
74 return 0;
75 }
76
77 return 1;
78 }
79
switch_read_compress(int flags)80 static void switch_read_compress(int flags) {
81 struct sftp_compress *comp;
82 z_stream *stream;
83
84 comp = &(read_compresses[read_comp_idx]);
85 stream = &(read_streams[read_comp_idx]);
86
87 /* First we can free up the read stream, kept from rekeying. */
88 if (comp->use_zlib == flags &&
89 comp->stream_ready) {
90
91 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
92 "done decompressing data: decompressed %" PR_LU " bytes to %" PR_LU
93 " bytes of data (%.2f)", (pr_off_t) stream->total_in,
94 (pr_off_t) stream->total_out,
95 stream->total_in == 0 ? 0.0 :
96 (float) stream->total_out / stream->total_in);
97
98 inflateEnd(stream);
99 comp->use_zlib = FALSE;
100 comp->stream_ready = FALSE;
101
102 /* Now we can switch the index. */
103 if (read_comp_idx == 1) {
104 read_comp_idx = 0;
105 return;
106 }
107
108 read_comp_idx = 1;
109 }
110 }
111
switch_write_compress(int flags)112 static void switch_write_compress(int flags) {
113 struct sftp_compress *comp;
114 z_stream *stream;
115
116 comp = &(write_compresses[write_comp_idx]);
117 stream = &(write_streams[write_comp_idx]);
118
119 /* First we can free up the write stream, kept from rekeying. */
120 if (comp->use_zlib == flags &&
121 comp->stream_ready) {
122
123 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
124 "done compressing data: compressed %" PR_LU " bytes to %" PR_LU
125 " bytes of data (%.2f)", (pr_off_t) stream->total_in,
126 (pr_off_t) stream->total_out,
127 stream->total_in == 0 ? 0.0 :
128 (float) stream->total_out / stream->total_in);
129
130 deflateEnd(stream);
131 comp->use_zlib = FALSE;
132 comp->stream_ready = FALSE;
133
134 /* Now we can switch the index. */
135 if (write_comp_idx == 1) {
136 write_comp_idx = 0;
137 return;
138 }
139
140 write_comp_idx = 1;
141 }
142 }
143
sftp_compress_get_read_algo(void)144 const char *sftp_compress_get_read_algo(void) {
145 struct sftp_compress *comp;
146
147 comp = &(read_compresses[read_comp_idx]);
148
149 if (comp->use_zlib) {
150 if (comp->use_zlib == SFTP_COMPRESS_FL_NEW_KEY) {
151 return "zlib";
152 }
153
154 if (comp->use_zlib == SFTP_COMPRESS_FL_AUTHENTICATED) {
155 return "zlib@openssh.com";
156 }
157 }
158
159 return "none";
160 }
161
sftp_compress_set_read_algo(const char * algo)162 int sftp_compress_set_read_algo(const char *algo) {
163 unsigned int idx = read_comp_idx;
164
165 if (read_compresses[idx].stream_ready) {
166 /* If we have an existing stream, it means that we are currently
167 * rekeying.
168 */
169 idx = get_next_read_index();
170 }
171
172 if (strncmp(algo, "zlib@openssh.com", 17) == 0) {
173 read_compresses[idx].use_zlib = SFTP_COMPRESS_FL_AUTHENTICATED;
174 return 0;
175 }
176
177 if (strncmp(algo, "zlib", 5) == 0) {
178 read_compresses[idx].use_zlib = SFTP_COMPRESS_FL_NEW_KEY;
179 return 0;
180 }
181
182 if (strncmp(algo, "none", 5) == 0) {
183 return 0;
184 }
185
186 errno = EINVAL;
187 return -1;
188 }
189
sftp_compress_init_read(int flags)190 int sftp_compress_init_read(int flags) {
191 struct sftp_compress *comp;
192 z_stream *stream;
193
194 switch_read_compress(flags);
195
196 comp = &(read_compresses[read_comp_idx]);
197 stream = &(read_streams[read_comp_idx]);
198
199 if (comp->use_zlib == flags &&
200 !comp->stream_ready) {
201 int zres;
202
203 zres = inflateInit(stream);
204 if (zres != Z_OK) {
205 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
206 "error preparing decompression stream (%d)", zres);
207 }
208
209 pr_event_generate("mod_sftp.ssh.client-compression", NULL);
210 comp->stream_ready = TRUE;
211 }
212
213 return 0;
214 }
215
sftp_compress_read_data(struct ssh2_packet * pkt)216 int sftp_compress_read_data(struct ssh2_packet *pkt) {
217 struct sftp_compress *comp;
218 z_stream *stream;
219
220 comp = &(read_compresses[read_comp_idx]);
221 stream = &(read_streams[read_comp_idx]);
222
223 if (comp->use_zlib &&
224 comp->stream_ready) {
225 unsigned char buf[16384], *input;
226 char *payload;
227 uint32_t input_len, payload_len = 0, payload_sz;
228 pool *sub_pool;
229 int zres;
230
231 if (pkt->payload_len == 0) {
232 return 0;
233 }
234
235 sub_pool = make_sub_pool(pkt->pool);
236
237 /* Use a copy of the payload, rather than the actual payload itself,
238 * as zlib may alter the payload contents and then encounter an error.
239 */
240 input_len = pkt->payload_len;
241 input = palloc(sub_pool, input_len);
242 memcpy(input, pkt->payload, input_len);
243
244 /* Try to guess at how big the uncompressed data will be. Optimistic
245 * estimate, for now, will be a factor of 8.
246 */
247 payload_sz = input_len * 8;
248 payload = palloc(sub_pool, payload_sz);
249
250 stream->next_in = input;
251 stream->avail_in = input_len;
252
253 while (1) {
254 size_t copy_len = 0;
255
256 pr_signals_handle();
257
258 stream->next_out = buf;
259 stream->avail_out = sizeof(buf);
260
261 zres = inflate(stream, Z_SYNC_FLUSH);
262 switch (zres) {
263 case Z_OK:
264 copy_len = sizeof(buf) - stream->avail_out;
265
266 /* Allocate more space for the data if necessary. */
267 if ((payload_len + copy_len) > payload_sz) {
268 uint32_t new_sz;
269 char *tmp;
270
271 pr_signals_handle();
272
273 new_sz = payload_sz;
274 while ((payload_len + copy_len) > new_sz) {
275 pr_signals_handle();
276
277 /* Keep doubling the size until it is large enough. */
278 new_sz *= 2;
279 }
280
281 pr_trace_msg(trace_channel, 20,
282 "allocating larger payload size (%lu bytes) for "
283 "inflated data (%lu bytes) plus existing payload %lu bytes",
284 (unsigned long) new_sz, (unsigned long) copy_len,
285 (unsigned long) payload_len);
286
287 tmp = palloc(sub_pool, new_sz);
288 memcpy(tmp, payload, payload_len);
289 payload = tmp;
290 payload_sz = new_sz;
291 }
292
293 if (copy_len > 0) {
294 memcpy(payload + payload_len, buf, copy_len);
295 payload_len += copy_len;
296
297 pr_trace_msg(trace_channel, 20,
298 "inflated %lu bytes to %lu bytes",
299 (unsigned long) input_len, (unsigned long) copy_len);
300 }
301
302 continue;
303
304 case Z_BUF_ERROR:
305 break;
306
307 default:
308 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
309 "unhandled zlib error (%d) while decompressing", zres);
310 destroy_pool(sub_pool);
311 return -1;
312 }
313
314 break;
315 }
316
317 /* Make sure that pkt->payload has enough room for the uncompressed data.
318 * If not, allocate a larger buffer.
319 */
320 if (pkt->payload_len < payload_len) {
321 pkt->payload = palloc(pkt->pool, payload_len);
322 }
323
324 memcpy(pkt->payload, payload, payload_len);
325 pkt->payload_len = payload_len;
326
327 pr_trace_msg(trace_channel, 20,
328 "finished inflating (payload len = %lu bytes)",
329 (unsigned long) payload_len);
330
331 destroy_pool(sub_pool);
332 }
333
334 return 0;
335 }
336
sftp_compress_get_write_algo(void)337 const char *sftp_compress_get_write_algo(void) {
338 struct sftp_compress *comp;
339
340 comp = &(write_compresses[write_comp_idx]);
341
342 if (comp->use_zlib) {
343 if (comp->use_zlib == SFTP_COMPRESS_FL_NEW_KEY) {
344 return "zlib";
345 }
346
347 if (comp->use_zlib == SFTP_COMPRESS_FL_AUTHENTICATED) {
348 return "zlib@openssh.com";
349 }
350 }
351
352 return "none";
353 }
354
sftp_compress_set_write_algo(const char * algo)355 int sftp_compress_set_write_algo(const char *algo) {
356 unsigned int idx = write_comp_idx;
357
358 if (write_compresses[idx].stream_ready) {
359 /* If we have an existing stream, it means that we are currently
360 * rekeying.
361 */
362 idx = get_next_write_index();
363 }
364
365 if (strncmp(algo, "zlib@openssh.com", 17) == 0) {
366 write_compresses[idx].use_zlib = SFTP_COMPRESS_FL_AUTHENTICATED;
367 return 0;
368 }
369
370 if (strncmp(algo, "zlib", 5) == 0) {
371 write_compresses[idx].use_zlib = SFTP_COMPRESS_FL_NEW_KEY;
372 return 0;
373 }
374
375 if (strncmp(algo, "none", 5) == 0) {
376 return 0;
377 }
378
379 errno = EINVAL;
380 return -1;
381 }
382
sftp_compress_init_write(int flags)383 int sftp_compress_init_write(int flags) {
384 struct sftp_compress *comp;
385 z_stream *stream;
386
387 switch_write_compress(flags);
388
389 comp = &(write_compresses[write_comp_idx]);
390 stream = &(write_streams[write_comp_idx]);
391
392 if (comp->use_zlib == flags &&
393 !comp->stream_ready) {
394 int zres;
395
396 zres = deflateInit(stream, Z_DEFAULT_COMPRESSION);
397 if (zres != Z_OK) {
398 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
399 "error preparing compression stream (%d)", zres);
400 }
401
402 pr_event_generate("mod_sftp.ssh.server-compression", NULL);
403 comp->stream_ready = TRUE;
404 }
405
406 return 0;
407 }
408
sftp_compress_write_data(struct ssh2_packet * pkt)409 int sftp_compress_write_data(struct ssh2_packet *pkt) {
410 struct sftp_compress *comp;
411 z_stream *stream;
412
413 comp = &(write_compresses[write_comp_idx]);
414 stream = &(write_streams[write_comp_idx]);
415
416 if (comp->use_zlib &&
417 comp->stream_ready) {
418 unsigned char buf[16384], *input;
419 char *payload;
420 uint32_t input_len, payload_len = 0, payload_sz;
421 pool *sub_pool;
422 int zres;
423
424 if (pkt->payload_len == 0) {
425 return 0;
426 }
427
428 sub_pool = make_sub_pool(pkt->pool);
429
430 /* Use a copy of the payload, rather than the actual payload itself,
431 * as zlib may alter the payload contents and then encounter an error.
432 */
433 input_len = pkt->payload_len;
434 input = palloc(sub_pool, input_len);
435 memcpy(input, pkt->payload, input_len);
436
437 /* Try to guess at how small the compressed data will be. Optimistic
438 * estimate, for now, will be a factor of 2, with a minimum of 1K.
439 */
440 payload_sz = 1024;
441 if ((input_len * 2) > payload_sz) {
442 payload_sz = input_len * 2;
443 }
444 payload = palloc(sub_pool, payload_sz);
445
446 stream->next_in = input;
447 stream->avail_in = input_len;
448 stream->avail_out = 0;
449
450 while (stream->avail_out == 0) {
451 size_t copy_len = 0;
452
453 pr_signals_handle();
454
455 stream->next_out = buf;
456 stream->avail_out = sizeof(buf);
457
458 zres = deflate(stream, Z_SYNC_FLUSH);
459
460 switch (zres) {
461 case Z_OK:
462 copy_len = sizeof(buf) - stream->avail_out;
463
464 /* Allocate more space for the data if necessary. */
465 if ((payload_len + copy_len) > payload_sz) {
466 uint32_t new_sz;
467 char *tmp;
468
469 new_sz = payload_sz;
470 while ((payload_len + copy_len) > new_sz) {
471 pr_signals_handle();
472
473 /* Keep doubling the size until it is large enough. */
474 new_sz *= 2;
475 }
476
477 pr_trace_msg(trace_channel, 20,
478 "allocating larger payload size (%lu bytes) for "
479 "deflated data (%lu bytes) plus existing payload %lu bytes",
480 (unsigned long) new_sz, (unsigned long) copy_len,
481 (unsigned long) payload_len);
482
483 tmp = palloc(sub_pool, new_sz);
484 memcpy(tmp, payload, payload_len);
485 payload = tmp;
486 payload_sz = new_sz;
487 }
488
489 memcpy(payload + payload_len, buf, copy_len);
490 payload_len += copy_len;
491
492 pr_trace_msg(trace_channel, 20,
493 "deflated %lu bytes to %lu bytes",
494 (unsigned long) input_len, (unsigned long) copy_len);
495
496 break;
497
498 default:
499 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
500 "unhandled zlib error (%d) while compressing", zres);
501 destroy_pool(sub_pool);
502 errno = EIO;
503 return -1;
504 }
505 }
506
507 if (payload_len > 0) {
508 if (pkt->payload_len < payload_len) {
509 pkt->payload = palloc(pkt->pool, payload_len);
510 }
511
512 memcpy(pkt->payload, payload, payload_len);
513 pkt->payload_len = payload_len;
514
515 pr_trace_msg(trace_channel, 20,
516 "finished deflating (payload len = %lu bytes)",
517 (unsigned long) payload_len);
518 }
519
520 destroy_pool(sub_pool);
521 }
522
523 return 0;
524 }
525
526 #else
527
sftp_compress_init_read(int flags)528 int sftp_compress_init_read(int flags) {
529 return 0;
530 }
531
sftp_compress_get_read_algo(void)532 const char *sftp_compress_get_read_algo(void) {
533 return "none";
534 }
535
sftp_compress_set_read_algo(const char * algo)536 int sftp_compress_set_read_algo(const char *algo) {
537 if (strncmp(algo, "none", 5) == 0) {
538 return 0;
539 }
540
541 errno = EINVAL;
542 return -1;
543 }
544
sftp_compress_read_data(struct ssh2_packet * pkt)545 int sftp_compress_read_data(struct ssh2_packet *pkt) {
546 return 0;
547 }
548
sftp_compress_init_write(int flags)549 int sftp_compress_init_write(int flags) {
550 return 0;
551 }
552
sftp_compress_get_write_algo(void)553 const char *sftp_compress_get_write_algo(void) {
554 return "none";
555 }
556
sftp_compress_set_write_algo(const char * algo)557 int sftp_compress_set_write_algo(const char *algo) {
558 if (strncmp(algo, "none", 5) == 0) {
559 return 0;
560 }
561
562 errno = EINVAL;
563 return -1;
564 }
565
sftp_compress_write_data(struct ssh2_packet * pkt)566 int sftp_compress_write_data(struct ssh2_packet *pkt) {
567 return 0;
568 }
569
570 #endif /* !HAVE_ZLIB_H */
571