jabberd2  2.7.0
compress.c
Go to the documentation of this file.
1 /*
2  * jabberd - Jabber Open Source Server
3  * Copyright (c) 2007 Tomasz Sterna
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; version 2 of the License.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
17  */
18 
23 #include "sx.h"
24 
25 static void _sx_compress_notify_compress(sx_t s, void *arg) {
26 
27  _sx_debug(ZONE, "preparing for compress");
28 
29  _sx_reset(s);
30 
31  /* start listening */
33 }
34 
35 static int _sx_compress_process(sx_t s, sx_plugin_t p, nad_t nad) {
36  int flags;
37  char *ns = NULL, *to = NULL, *from = NULL, *version = NULL;
38  sx_error_t sxe;
39 
40  /* not interested if we're a server and we never offered it */
41  if(s->type == type_SERVER && !(s->flags & SX_COMPRESS_OFFER))
42  return 1;
43 
44  /* only want compress packets */
45  if(NAD_ENS(nad, 0) < 0 || NAD_NURI_L(nad, NAD_ENS(nad, 0)) != sizeof(uri_COMPRESS)-1 || strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_COMPRESS, sizeof(uri_COMPRESS)-1) != 0)
46  return 1;
47 
48  /* compress from client */
49  if(s->type == type_SERVER) {
50  if(NAD_ENAME_L(nad, 0) == 8 && strncmp(NAD_ENAME(nad, 0), "compress", 8) == 0) {
51  nad_free(nad);
52 
53  /* can't go on if we've been here before */
54  if(s->flags & SX_COMPRESS_WRAPPER) {
55  _sx_debug(ZONE, "compress requested on already compressed channel, dropping packet");
56  return 0;
57  }
58 
59  _sx_debug(ZONE, "compress requested, setting up");
60 
61  /* go ahead */
62  jqueue_push(s->wbufq, _sx_buffer_new("<compressed xmlns='" uri_COMPRESS "'/>", sizeof(uri_COMPRESS)-1 + 22, _sx_compress_notify_compress, NULL), 0);
63  s->want_write = 1;
64 
65  /* handled the packet */
66  return 0;
67  }
68  }
69 
70  else if(s->type == type_CLIENT) {
71  /* kick off the handshake */
72  if(NAD_ENAME_L(nad, 0) == 7 && strncmp(NAD_ENAME(nad, 0), "compressed", 7) == 0) {
73  nad_free(nad);
74 
75  /* save interesting bits */
76  flags = s->flags;
77 
78  if(s->ns != NULL) ns = strdup(s->ns);
79 
80  if(s->req_to != NULL) to = strdup(s->req_to);
81  if(s->req_from != NULL) from = strdup(s->req_from);
82  if(s->req_version != NULL) version = strdup(s->req_version);
83 
84  /* reset state */
85  _sx_reset(s);
86 
87  _sx_debug(ZONE, "server ready for compression, starting");
88 
89  /* second time round */
90  sx_client_init(s, flags | SX_COMPRESS_WRAPPER, ns, to, from, version);
91 
92  /* free bits */
93  if(ns != NULL) free(ns);
94  if(to != NULL) free(to);
95  if(from != NULL) free(from);
96  if(version != NULL) free(version);
97 
98  return 0;
99  }
100 
101  /* busted server */
102  if(NAD_ENAME_L(nad, 0) == 7 && strncmp(NAD_ENAME(nad, 0), "failure", 7) == 0) {
103  nad_free(nad);
104 
105  _sx_debug(ZONE, "server can't handle compression, business as usual");
106 
107  _sx_gen_error(sxe, SX_ERR_COMPRESS_FAILURE, "compress failure", "Server was unable to establish compression");
108  _sx_event(s, event_ERROR, (void *) &sxe);
109 
110  return 0;
111  }
112  }
113 
114  _sx_debug(ZONE, "unknown compress namespace element '%.*s', dropping packet", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0));
115  nad_free(nad);
116  return 0;
117 }
118 
119 static void _sx_compress_features(sx_t s, sx_plugin_t p, nad_t nad) {
120  int ns;
121 
122  /* if the session is already compressed, or the app told us not to, or we are on WebSocket framing,
123  * or STARTTLS is required and stream is not encrypted yet, then we don't offer anything */
124  if((s->flags & SX_COMPRESS_WRAPPER) || !(s->flags & SX_COMPRESS_OFFER) || ((s->flags & SX_SSL_STARTTLS_REQUIRE) && s->ssf == 0) || (s->flags & SX_WEBSOCKET_WRAPPER))
125  return;
126 
127  _sx_debug(ZONE, "offering compression");
128 
129  ns = nad_add_namespace(nad, uri_COMPRESS_FEATURE, NULL);
130  nad_append_elem(nad, ns, "compression", 1);
131  nad_append_elem(nad, ns, "method", 2);
132  nad_append_cdata(nad, "zlib", 4, 3);
133 }
134 
135 static int _sx_compress_wio(sx_t s, sx_plugin_t p, sx_buf_t buf) {
136  _sx_compress_conn_t sc = (_sx_compress_conn_t) s->plugin_data[p->index];
137  int ret;
138  sx_error_t sxe;
139 
140  /* only bothering if they asked for wrappermode */
141  if(!(s->flags & SX_COMPRESS_WRAPPER))
142  return 1;
143 
144  _sx_debug(ZONE, "in _sx_compress_wio");
145 
146  /* move the data into the zlib write buffer */
147  if(buf->len > 0) {
148  _sx_debug(ZONE, "loading %d bytes into zlib write buffer", buf->len);
149 
150  _sx_buffer_alloc_margin(sc->wbuf, 0, buf->len);
151  memcpy(sc->wbuf->data + sc->wbuf->len, buf->data, buf->len);
152  sc->wbuf->len += buf->len;
153 
154  _sx_buffer_clear(buf);
155  }
156 
157  /* compress the data */
158  if(sc->wbuf->len > 0) {
159  sc->wstrm.avail_in = sc->wbuf->len;
160  sc->wstrm.next_in = (Bytef*)sc->wbuf->data;
161  /* deflate() on write buffer until there is data to compress */
162  do {
163  /* make place for deflated data */
164  _sx_buffer_alloc_margin(buf, 0, sc->wbuf->len + SX_COMPRESS_CHUNK);
165 
166  sc->wstrm.avail_out = sc->wbuf->len + SX_COMPRESS_CHUNK;
167  sc->wstrm.next_out = (Bytef*)(buf->data + buf->len);
168 
169  ret = deflate(&(sc->wstrm), Z_SYNC_FLUSH);
170  assert(ret != Z_STREAM_ERROR);
171 
172  buf->len += sc->wbuf->len + SX_COMPRESS_CHUNK - sc->wstrm.avail_out;
173 
174  } while (sc->wstrm.avail_out == 0);
175 
176  if(ret != Z_OK || sc->wstrm.avail_in != 0) {
177  /* throw an error */
178  _sx_gen_error(sxe, SX_ERR_COMPRESS, "compression error", "Error during compression");
179  _sx_event(s, event_ERROR, (void *) &sxe);
180 
181  sx_error(s, stream_err_INTERNAL_SERVER_ERROR, "Error during compression");
182  sx_close(s);
183 
184  return -2; /* fatal */
185  }
186 
187  sc->wbuf->len = sc->wstrm.avail_in;
188  sc->wbuf->data = (char*)sc->wstrm.next_in;
189  }
190 
191  _sx_debug(ZONE, "passing %d bytes from zlib write buffer", buf->len);
192 
193  return 1;
194 }
195 
196 static int _sx_compress_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) {
197  _sx_compress_conn_t sc = (_sx_compress_conn_t) s->plugin_data[p->index];
198  int ret;
199  sx_error_t sxe;
200 
201  /* only bothering if they asked for wrappermode */
202  if(!(s->flags & SX_COMPRESS_WRAPPER))
203  return 1;
204 
205  _sx_debug(ZONE, "in _sx_compress_rio");
206 
207  /* move the data into the zlib read buffer */
208  if(buf->len > 0) {
209  _sx_debug(ZONE, "loading %d bytes into zlib read buffer", buf->len);
210 
211  _sx_buffer_alloc_margin(sc->rbuf, 0, buf->len);
212  memcpy(sc->rbuf->data + sc->rbuf->len, buf->data, buf->len);
213  sc->rbuf->len += buf->len;
214 
215  _sx_buffer_clear(buf);
216  }
217 
218  /* decompress the data */
219  if(sc->rbuf->len > 0) {
220  sc->rstrm.avail_in = sc->rbuf->len;
221  sc->rstrm.next_in = (Bytef*)sc->rbuf->data;
222  /* run inflate() on read buffer while able to fill the output buffer */
223  do {
224  /* make place for inflated data */
225  _sx_buffer_alloc_margin(buf, 0, SX_COMPRESS_CHUNK);
226 
227  sc->rstrm.avail_out = SX_COMPRESS_CHUNK;
228  sc->rstrm.next_out = (Bytef*)(buf->data + buf->len);
229 
230  ret = inflate(&(sc->rstrm), Z_SYNC_FLUSH);
231  assert(ret != Z_STREAM_ERROR);
232  switch (ret) {
233  case Z_NEED_DICT:
234  case Z_DATA_ERROR:
235  case Z_MEM_ERROR:
236  /* throw an error */
237  _sx_gen_error(sxe, SX_ERR_COMPRESS, "compression error", "Error during decompression");
238  _sx_event(s, event_ERROR, (void *) &sxe);
239 
240  sx_error(s, stream_err_INVALID_XML, "Error during decompression");
241  sx_close(s);
242 
243  return -2;
244  }
245 
246  buf->len += SX_COMPRESS_CHUNK - sc->rstrm.avail_out;
247 
248  } while (sc->rstrm.avail_out == 0);
249 
250  sc->rbuf->len = sc->rstrm.avail_in;
251  sc->rbuf->data = (char*)sc->rstrm.next_in;
252  }
253 
254  _sx_debug(ZONE, "passing %d bytes from zlib read buffer", buf->len);
255 
256  /* flag if we want to read */
257  if(sc->rbuf->len > 0)
258  s->want_read = 1;
259 
260  if(buf->len == 0)
261  return 0;
262 
263  return 1;
264 }
265 
266 static void _sx_compress_new(sx_t s, sx_plugin_t p) {
267  _sx_compress_conn_t sc = (_sx_compress_conn_t) s->plugin_data[p->index];
268 
269  /* only bothering if they asked for wrappermode and not already active */
270  if(!(s->flags & SX_COMPRESS_WRAPPER) || sc)
271  return;
272 
273  _sx_debug(ZONE, "preparing for compressed connect for %d", s->tag);
274 
275  sc = (_sx_compress_conn_t) calloc(1, sizeof(struct _sx_compress_conn_st));
276 
277  /* initialize streams */
278  sc->rstrm.zalloc = Z_NULL;
279  sc->rstrm.zfree = Z_NULL;
280  sc->rstrm.opaque = Z_NULL;
281  sc->rstrm.avail_in = 0;
282  sc->rstrm.next_in = Z_NULL;
283  inflateInit(&(sc->rstrm));
284 
285  sc->wstrm.zalloc = Z_NULL;
286  sc->wstrm.zfree = Z_NULL;
287  sc->wstrm.opaque = Z_NULL;
288  deflateInit(&(sc->wstrm), Z_DEFAULT_COMPRESSION);
289 
290  /* read and write buffers */
291  sc->rbuf = _sx_buffer_new(NULL, 0, NULL, NULL);
292  sc->wbuf = _sx_buffer_new(NULL, 0, NULL, NULL);
293 
294  s->plugin_data[p->index] = (void *) sc;
295 
296  /* bring the plugin online */
297  _sx_chain_io_plugin(s, p);
298 }
299 
301 static void _sx_compress_free(sx_t s, sx_plugin_t p) {
302  _sx_compress_conn_t sc = (_sx_compress_conn_t) s->plugin_data[p->index];
303 
304  if(sc == NULL)
305  return;
306 
307  log_debug(ZONE, "cleaning up compression state");
308 
309  if(s->type == type_NONE) {
310  free(sc);
311  return;
312  }
313 
314  /* end streams */
315  inflateEnd(&(sc->rstrm));
316  deflateEnd(&(sc->wstrm));
317 
318  /* free buffers */
319  _sx_buffer_free(sc->rbuf);
320  _sx_buffer_free(sc->wbuf);
321 
322  free(sc);
323 
324  s->plugin_data[p->index] = NULL;
325 }
326 
328 int sx_compress_init(sx_env_t env, sx_plugin_t p, va_list args) {
329 
330  _sx_debug(ZONE, "initialising compression plugin");
331 
334  p->rio = _sx_compress_rio;
335  p->wio = _sx_compress_wio;
338  p->free = _sx_compress_free;
339 
340  return 0;
341 }
342 
343 int sx_compress_client_compress(sx_plugin_t p, sx_t s, const char *pemfile) {
344  assert((int) (p != NULL));
345  assert((int) (s != NULL));
346 
347  /* sanity */
348  if(s->type != type_CLIENT || s->state != state_STREAM) {
349  _sx_debug(ZONE, "wrong conn type or state for client compress");
350  return 1;
351  }
352 
353  /* check if we're already compressed */
354  if((s->flags & SX_COMPRESS_WRAPPER)) {
355  _sx_debug(ZONE, "channel already compressed");
356  return 1;
357  }
358 
359  _sx_debug(ZONE, "initiating compress sequence");
360 
361  /* go */
362  jqueue_push(s->wbufq, _sx_buffer_new("<compress xmlns='" uri_COMPRESS "'><method>zlib</method></compress>", sizeof(uri_COMPRESS)-1 + 51, NULL, NULL), 0);
363  s->want_write = 1;
364  _sx_event(s, event_WANT_WRITE, NULL);
365 
366  return 0;
367 }
Definition: nad.h:93
void(* free)(sx_t s, sx_plugin_t p)
Definition: sx.h:357
Definition: sx.h:113
_sx_state_t state
Definition: sx.h:319
#define _sx_event(s, e, data)
Definition: sx.h:395
void(* server)(sx_t s, sx_plugin_t p)
Definition: sx.h:360
unsigned int flags
Definition: sx.h:276
const char * req_to
Definition: sx.h:282
jqueue_t wbufq
Definition: sx.h:301
static int _sx_compress_process(sx_t s, sx_plugin_t p, nad_t nad)
Definition: compress.c:35
static void _sx_compress_new(sx_t s, sx_plugin_t p)
Definition: compress.c:266
Definition: sx.h:65
void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth)
append new cdata to the last elem
Definition: nad.c:753
#define stream_err_INTERNAL_SERVER_ERROR
Definition: sx.h:131
void(* features)(sx_t s, sx_plugin_t p, nad_t nad)
Definition: sx.h:373
an environment
Definition: sx.h:382
error info for event_ERROR
Definition: sx.h:99
static void _sx_compress_free(sx_t s, sx_plugin_t p)
cleanup
Definition: compress.c:301
int tag
Definition: sx.h:258
a plugin
Definition: sx.h:347
int nad_add_namespace(nad_t nad, const char *uri, const char *prefix)
bring a new namespace into scope
Definition: nad.c:778
void sx_server_init(sx_t s, unsigned int flags)
Definition: server.c:248
#define SX_ERR_COMPRESS
Definition: plugins.h:45
#define NAD_ENAME(N, E)
Definition: nad.h:183
void _sx_chain_io_plugin(sx_t s, sx_plugin_t p)
Definition: chain.c:25
static int _sx_compress_rio(sx_t s, sx_plugin_t p, sx_buf_t buf)
Definition: compress.c:196
int nad_append_elem(nad_t nad, int ns, const char *name, int depth)
create a new elem on the list
Definition: nad.c:711
const char * ns
Definition: sx.h:279
void nad_free(nad_t nad)
free that nad
Definition: nad.c:180
#define SX_COMPRESS_OFFER
Definition: plugins.h:32
void _sx_buffer_alloc_margin(sx_buf_t buf, int before, int after)
utility: ensure a certain amount of allocated space adjacent to buf->data
Definition: sx.c:262
holds the state for a single stream
Definition: sx.h:253
char * data
Definition: sx.h:114
void sx_client_init(sx_t s, unsigned int flags, const char *ns, const char *to, const char *from, const char *version)
Definition: client.c:111
#define NAD_ENAME_L(N, E)
Definition: nad.h:184
void jqueue_push(jqueue_t q, void *data, int priority)
Definition: jqueue.c:44
#define NAD_NURI_L(N, NS)
Definition: nad.h:192
Definition: sx.h:82
void _sx_buffer_free(sx_buf_t buf)
utility: kill a buffer
Definition: sx.c:244
sx_buf_t _sx_buffer_new(const char *data, int len, _sx_notify_t notify, void *notify_arg)
utility: make a new buffer if len>0 but data is NULL, the buffer will contain that many bytes of garb...
Definition: sx.c:220
#define uri_COMPRESS
Definition: uri.h:45
#define uri_COMPRESS_FEATURE
Definition: uri.h:46
void sx_close(sx_t s)
Definition: io.c:512
Definition: sx.h:81
#define log_debug(...)
Definition: log.h:65
#define SX_WEBSOCKET_WRAPPER
Definition: plugins.h:34
#define _sx_debug
Definition: sx.h:408
#define SX_SSL_STARTTLS_REQUIRE
Definition: plugins.h:27
void sx_error(sx_t s, int err, const char *text)
Definition: error.c:94
_sx_type_t type
Definition: sx.h:273
#define stream_err_INVALID_XML
Definition: sx.h:135
void _sx_reset(sx_t s)
utility; reset stream state
Definition: sx.c:154
const char * req_version
Definition: sx.h:284
const char * req_from
Definition: sx.h:283
Definition: sx.h:83
int ssf
Definition: sx.h:343
static void _sx_compress_features(sx_t s, sx_plugin_t p, nad_t nad)
Definition: compress.c:119
unsigned int len
Definition: sx.h:115
int(* wio)(sx_t s, sx_plugin_t p, sx_buf_t buf)
Definition: sx.h:363
void _sx_buffer_clear(sx_buf_t buf)
utility: clear out a buffer, but don&#39;t deallocate it
Definition: sx.c:252
int sx_compress_init(sx_env_t env, sx_plugin_t p, va_list args)
args: none
Definition: compress.c:328
static void _sx_compress_notify_compress(sx_t s, void *arg)
this plugin implements the XEP-0138: Stream Compression
Definition: compress.c:25
#define _sx_gen_error(e, c, g, s)
helper macro to populate this struct
Definition: sx.h:106
#define ZONE
Definition: mio_impl.h:76
#define NAD_NURI(N, NS)
Definition: nad.h:191
int(* process)(sx_t s, sx_plugin_t p, nad_t nad)
Definition: sx.h:376
int(* rio)(sx_t s, sx_plugin_t p, sx_buf_t buf)
Definition: sx.h:364
int index
Definition: sx.h:352
#define SX_ERR_COMPRESS_FAILURE
Definition: plugins.h:46
static int _sx_compress_wio(sx_t s, sx_plugin_t p, sx_buf_t buf)
Definition: compress.c:135
int want_write
Definition: sx.h:306
void ** plugin_data
Definition: sx.h:330
void(* client)(sx_t s, sx_plugin_t p)
Definition: sx.h:359
int sx_compress_client_compress(sx_plugin_t p, sx_t s, const char *pemfile)
Definition: compress.c:343
#define NAD_ENS(N, E)
Definition: nad.h:196
#define SX_COMPRESS_WRAPPER
Definition: plugins.h:31