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