1 /* Copyright (c) 2004-2007, 2019, Sara Golemon <sarag@libssh2.org>
2 * Copyright (c) 2010-2014, Daniel Stenberg <daniel@haxx.se>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms,
6 * with or without modification, are permitted provided
7 * that the following conditions are met:
8 *
9 * Redistributions of source code must retain the above
10 * copyright notice, this list of conditions and the
11 * following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials
16 * provided with the distribution.
17 *
18 * Neither the name of the copyright holder nor the names
19 * of any other contributors may be used to endorse or
20 * promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
35 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
36 * OF SUCH DAMAGE.
37 */
38
39 #include "libssh2_priv.h"
40 #ifdef LIBSSH2_HAVE_ZLIB
41 # include <zlib.h>
42 #endif
43
44 #include "comp.h"
45
46 /* ********
47 * none *
48 ******** */
49
50 /*
51 * comp_method_none_comp
52 *
53 * Minimalist compression: Absolutely none
54 */
55 static int
comp_method_none_comp(LIBSSH2_SESSION * session,unsigned char * dest,size_t * dest_len,const unsigned char * src,size_t src_len,void ** abstract)56 comp_method_none_comp(LIBSSH2_SESSION *session,
57 unsigned char *dest,
58 size_t *dest_len,
59 const unsigned char *src,
60 size_t src_len,
61 void **abstract)
62 {
63 (void) session;
64 (void) abstract;
65 (void) dest;
66 (void) dest_len;
67 (void) src;
68 (void) src_len;
69
70 return 0;
71 }
72
73 /*
74 * comp_method_none_decomp
75 *
76 * Minimalist decompression: Absolutely none
77 */
78 static int
comp_method_none_decomp(LIBSSH2_SESSION * session,unsigned char ** dest,size_t * dest_len,size_t payload_limit,const unsigned char * src,size_t src_len,void ** abstract)79 comp_method_none_decomp(LIBSSH2_SESSION * session,
80 unsigned char **dest,
81 size_t *dest_len,
82 size_t payload_limit,
83 const unsigned char *src,
84 size_t src_len, void **abstract)
85 {
86 (void) session;
87 (void) payload_limit;
88 (void) abstract;
89 *dest = (unsigned char *) src;
90 *dest_len = src_len;
91 return 0;
92 }
93
94
95
96 static const LIBSSH2_COMP_METHOD comp_method_none = {
97 "none",
98 0, /* not really compressing */
99 0, /* isn't used in userauth, go figure */
100 NULL,
101 comp_method_none_comp,
102 comp_method_none_decomp,
103 NULL
104 };
105
106 #ifdef LIBSSH2_HAVE_ZLIB
107 /* ********
108 * zlib *
109 ******** */
110
111 /* Memory management wrappers
112 * Yes, I realize we're doing a callback to a callback,
113 * Deal...
114 */
115
116 static voidpf
comp_method_zlib_alloc(voidpf opaque,uInt items,uInt size)117 comp_method_zlib_alloc(voidpf opaque, uInt items, uInt size)
118 {
119 LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque;
120
121 return (voidpf) LIBSSH2_ALLOC(session, items * size);
122 }
123
124 static void
comp_method_zlib_free(voidpf opaque,voidpf address)125 comp_method_zlib_free(voidpf opaque, voidpf address)
126 {
127 LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque;
128
129 LIBSSH2_FREE(session, address);
130 }
131
132
133
134 /* libssh2_comp_method_zlib_init
135 * All your bandwidth are belong to us (so save some)
136 */
137 static int
comp_method_zlib_init(LIBSSH2_SESSION * session,int compr,void ** abstract)138 comp_method_zlib_init(LIBSSH2_SESSION * session, int compr,
139 void **abstract)
140 {
141 z_stream *strm;
142 int status;
143
144 strm = LIBSSH2_CALLOC(session, sizeof(z_stream));
145 if(!strm) {
146 return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
147 "Unable to allocate memory for "
148 "zlib compression/decompression");
149 }
150
151 strm->opaque = (voidpf) session;
152 strm->zalloc = (alloc_func) comp_method_zlib_alloc;
153 strm->zfree = (free_func) comp_method_zlib_free;
154 if(compr) {
155 /* deflate */
156 status = deflateInit(strm, Z_DEFAULT_COMPRESSION);
157 }
158 else {
159 /* inflate */
160 status = inflateInit(strm);
161 }
162
163 if(status != Z_OK) {
164 LIBSSH2_FREE(session, strm);
165 _libssh2_debug(session, LIBSSH2_TRACE_TRANS,
166 "unhandled zlib error %d", status);
167 return LIBSSH2_ERROR_COMPRESS;
168 }
169 *abstract = strm;
170
171 return LIBSSH2_ERROR_NONE;
172 }
173
174 /*
175 * libssh2_comp_method_zlib_comp
176 *
177 * Compresses source to destination. Without allocation.
178 */
179 static int
comp_method_zlib_comp(LIBSSH2_SESSION * session,unsigned char * dest,size_t * dest_len,const unsigned char * src,size_t src_len,void ** abstract)180 comp_method_zlib_comp(LIBSSH2_SESSION *session,
181 unsigned char *dest,
182
183 /* dest_len is a pointer to allow this function to
184 update it with the final actual size used */
185 size_t *dest_len,
186 const unsigned char *src,
187 size_t src_len,
188 void **abstract)
189 {
190 z_stream *strm = *abstract;
191 int out_maxlen = *dest_len;
192 int status;
193
194 strm->next_in = (unsigned char *) src;
195 strm->avail_in = src_len;
196 strm->next_out = dest;
197 strm->avail_out = out_maxlen;
198
199 status = deflate(strm, Z_PARTIAL_FLUSH);
200
201 if((status == Z_OK) && (strm->avail_out > 0)) {
202 *dest_len = out_maxlen - strm->avail_out;
203 return 0;
204 }
205
206 _libssh2_debug(session, LIBSSH2_TRACE_TRANS,
207 "unhandled zlib compression error %d, avail_out",
208 status, strm->avail_out);
209 return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, "compression failure");
210 }
211
212 /*
213 * libssh2_comp_method_zlib_decomp
214 *
215 * Decompresses source to destination. Allocates the output memory.
216 */
217 static int
comp_method_zlib_decomp(LIBSSH2_SESSION * session,unsigned char ** dest,size_t * dest_len,size_t payload_limit,const unsigned char * src,size_t src_len,void ** abstract)218 comp_method_zlib_decomp(LIBSSH2_SESSION * session,
219 unsigned char **dest,
220 size_t *dest_len,
221 size_t payload_limit,
222 const unsigned char *src,
223 size_t src_len, void **abstract)
224 {
225 z_stream *strm = *abstract;
226 /* A short-term alloc of a full data chunk is better than a series of
227 reallocs */
228 char *out;
229 size_t out_maxlen = src_len;
230
231 if(src_len <= SIZE_MAX / 4)
232 out_maxlen = src_len * 4;
233 else
234 out_maxlen = payload_limit;
235
236 /* If strm is null, then we have not yet been initialized. */
237 if(strm == NULL)
238 return _libssh2_error(session, LIBSSH2_ERROR_COMPRESS,
239 "decompression uninitialized");;
240
241 /* In practice they never come smaller than this */
242 if(out_maxlen < 25)
243 out_maxlen = 25;
244
245 if(out_maxlen > payload_limit)
246 out_maxlen = payload_limit;
247
248 strm->next_in = (unsigned char *) src;
249 strm->avail_in = src_len;
250 strm->next_out = (unsigned char *) LIBSSH2_ALLOC(session, out_maxlen);
251 out = (char *) strm->next_out;
252 strm->avail_out = out_maxlen;
253 if(!strm->next_out)
254 return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
255 "Unable to allocate decompression buffer");
256
257 /* Loop until it's all inflated or hit error */
258 for(;;) {
259 int status;
260 size_t out_ofs;
261 char *newout;
262
263 status = inflate(strm, Z_PARTIAL_FLUSH);
264
265 if(status == Z_OK) {
266 if(strm->avail_out > 0)
267 /* status is OK and the output buffer has not been exhausted
268 so we're done */
269 break;
270 }
271 else if(status == Z_BUF_ERROR) {
272 /* the input data has been exhausted so we are done */
273 break;
274 }
275 else {
276 /* error state */
277 LIBSSH2_FREE(session, out);
278 _libssh2_debug(session, LIBSSH2_TRACE_TRANS,
279 "unhandled zlib error %d", status);
280 return _libssh2_error(session, LIBSSH2_ERROR_ZLIB,
281 "decompression failure");
282 }
283
284 if(out_maxlen > payload_limit || out_maxlen > SIZE_MAX / 2) {
285 LIBSSH2_FREE(session, out);
286 return _libssh2_error(session, LIBSSH2_ERROR_ZLIB,
287 "Excessive growth in decompression phase");
288 }
289
290 /* If we get here we need to grow the output buffer and try again */
291 out_ofs = out_maxlen - strm->avail_out;
292 out_maxlen *= 2;
293 newout = LIBSSH2_REALLOC(session, out, out_maxlen);
294 if(!newout) {
295 LIBSSH2_FREE(session, out);
296 return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
297 "Unable to expand decompression buffer");
298 }
299 out = newout;
300 strm->next_out = (unsigned char *) out + out_ofs;
301 strm->avail_out = out_maxlen - out_ofs;
302 }
303
304 *dest = (unsigned char *) out;
305 *dest_len = out_maxlen - strm->avail_out;
306
307 return 0;
308 }
309
310
311 /* libssh2_comp_method_zlib_dtor
312 * All done, no more compression for you
313 */
314 static int
comp_method_zlib_dtor(LIBSSH2_SESSION * session,int compr,void ** abstract)315 comp_method_zlib_dtor(LIBSSH2_SESSION *session, int compr, void **abstract)
316 {
317 z_stream *strm = *abstract;
318
319 if(strm) {
320 if(compr)
321 deflateEnd(strm);
322 else
323 inflateEnd(strm);
324 LIBSSH2_FREE(session, strm);
325 }
326
327 *abstract = NULL;
328 return 0;
329 }
330
331 static const LIBSSH2_COMP_METHOD comp_method_zlib = {
332 "zlib",
333 1, /* yes, this compresses */
334 1, /* do compression during userauth */
335 comp_method_zlib_init,
336 comp_method_zlib_comp,
337 comp_method_zlib_decomp,
338 comp_method_zlib_dtor,
339 };
340
341 static const LIBSSH2_COMP_METHOD comp_method_zlib_openssh = {
342 "zlib@openssh.com",
343 1, /* yes, this compresses */
344 0, /* don't use compression during userauth */
345 comp_method_zlib_init,
346 comp_method_zlib_comp,
347 comp_method_zlib_decomp,
348 comp_method_zlib_dtor,
349 };
350 #endif /* LIBSSH2_HAVE_ZLIB */
351
352 /* If compression is enabled by the API, then this array is used which then
353 may allow compression if zlib is available at build time */
354 static const LIBSSH2_COMP_METHOD *comp_methods[] = {
355 #ifdef LIBSSH2_HAVE_ZLIB
356 &comp_method_zlib,
357 &comp_method_zlib_openssh,
358 #endif /* LIBSSH2_HAVE_ZLIB */
359 &comp_method_none,
360 NULL
361 };
362
363 /* If compression is disabled by the API, then this array is used */
364 static const LIBSSH2_COMP_METHOD *no_comp_methods[] = {
365 &comp_method_none,
366 NULL
367 };
368
369 const LIBSSH2_COMP_METHOD **
_libssh2_comp_methods(LIBSSH2_SESSION * session)370 _libssh2_comp_methods(LIBSSH2_SESSION *session)
371 {
372 if(session->flag.compress)
373 return comp_methods;
374 else
375 return no_comp_methods;
376 }
377