stropheruby 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/conn.c ADDED
@@ -0,0 +1,611 @@
1
+ /* conn.c
2
+ ** strophe XMPP client library -- connection object functions
3
+ **
4
+ ** Copyright (C) 2005-2008 OGG, LLC. All rights reserved.
5
+ **
6
+ ** This software is provided AS-IS with no warranty, either express
7
+ ** or implied.
8
+ **
9
+ ** This software is distributed under license and may not be copied,
10
+ ** modified or distributed except as expressly authorized under the
11
+ ** terms of the license contained in the file LICENSE.txt in this
12
+ ** distribution.
13
+ */
14
+
15
+ /** @file
16
+ * Connection management.
17
+ */
18
+
19
+ /** @defgroup Connections Connection management
20
+ */
21
+
22
+ #include <stdio.h>
23
+ #include <stdlib.h>
24
+ #include <string.h>
25
+
26
+ #include "strophe.h"
27
+ #include "common.h"
28
+ #include "util.h"
29
+
30
+ #ifndef DEFAULT_SEND_QUEUE_MAX
31
+ /** @def DEFAULT_SEND_QUEUE_MAX
32
+ * The default maximum send queue size. This is currently unused.
33
+ */
34
+ #define DEFAULT_SEND_QUEUE_MAX 64
35
+ #endif
36
+ #ifndef DISCONNECT_TIMEOUT
37
+ /** @def DISCONNECT_TIMEOUT
38
+ * The time to wait (in milliseconds) for graceful disconnection to
39
+ * complete before the connection is reset. The default is 2 seconds.
40
+ */
41
+ #define DISCONNECT_TIMEOUT 2000 /* 2 seconds */
42
+ #endif
43
+ #ifndef CONNECT_TIMEOUT
44
+ /** @def CONNECT_TIMEOUT
45
+ * The time to wait (in milliseconds) for a connection attempt to succeed
46
+ * or error. The default is 5 seconds.
47
+ */
48
+ #define CONNECT_TIMEOUT 5000 /* 5 seconds */
49
+ #endif
50
+
51
+ static int _disconnect_cleanup(xmpp_conn_t * const conn,
52
+ void * const userdata);
53
+
54
+ /** Create a new Strophe connection object.
55
+ *
56
+ * @param ctx a Strophe context object
57
+ *
58
+ * @return a Strophe connection object or NULL on an error
59
+ *
60
+ * @ingroup Connections
61
+ */
62
+ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx)
63
+ {
64
+ xmpp_conn_t *conn = NULL;
65
+ xmpp_connlist_t *tail, *item;
66
+
67
+ if (ctx == NULL) return NULL;
68
+ conn = xmpp_alloc(ctx, sizeof(xmpp_conn_t));
69
+
70
+ if (conn != NULL) {
71
+ conn->ctx = ctx;
72
+
73
+ conn->type = XMPP_UNKNOWN;
74
+ conn->sock = -1;
75
+ conn->tls = NULL;
76
+ conn->timeout_stamp = 0;
77
+ conn->error = 0;
78
+ conn->stream_error = NULL;
79
+
80
+ /* default send parameters */
81
+ conn->blocking_send = 0;
82
+ conn->send_queue_max = DEFAULT_SEND_QUEUE_MAX;
83
+ conn->send_queue_len = 0;
84
+ conn->send_queue_head = NULL;
85
+ conn->send_queue_tail = NULL;
86
+
87
+ /* default timeouts */
88
+ conn->connect_timeout = CONNECT_TIMEOUT;
89
+
90
+ conn->lang = xmpp_strdup(conn->ctx, "en");
91
+ if (!conn->lang) {
92
+ xmpp_free(conn->ctx, conn);
93
+ return NULL;
94
+ }
95
+ conn->domain = NULL;
96
+ conn->jid = NULL;
97
+ conn->pass = NULL;
98
+ conn->stream_id = NULL;
99
+
100
+ conn->tls_support = 0;
101
+ conn->tls_failed = 0;
102
+ conn->sasl_support = 0;
103
+
104
+ conn->bind_required = 0;
105
+ conn->session_required = 0;
106
+
107
+ conn->parser = NULL;
108
+ conn->stanza = NULL;
109
+ parser_prepare_reset(conn, auth_handle_open);
110
+
111
+ conn->authenticated = 0;
112
+ conn->conn_handler = NULL;
113
+ conn->userdata = NULL;
114
+ conn->timed_handlers = NULL;
115
+ /* we own (and will free) the hash values */
116
+ conn->id_handlers = hash_new(conn->ctx, 32, NULL);
117
+ conn->handlers = NULL;
118
+
119
+ /* give the caller a reference to connection */
120
+ conn->ref = 1;
121
+
122
+ /* add connection to ctx->connlist */
123
+ tail = conn->ctx->connlist;
124
+ while (tail && tail->next) tail = tail->next;
125
+
126
+ item = xmpp_alloc(conn->ctx, sizeof(xmpp_connlist_t));
127
+ if (!item) {
128
+ xmpp_error(conn->ctx, "xmpp", "failed to allocate memory");
129
+ xmpp_free(conn->ctx, conn->lang);
130
+ XML_ParserFree(conn->parser);
131
+ xmpp_free(conn->ctx, conn);
132
+ conn = NULL;
133
+ } else {
134
+ item->conn = conn;
135
+ item->next = NULL;
136
+
137
+ if (tail) tail->next = item;
138
+ else conn->ctx->connlist = item;
139
+ }
140
+ }
141
+
142
+ return conn;
143
+ }
144
+
145
+ /** Clone a Strophe connection object.
146
+ *
147
+ * @param conn a Strophe connection object
148
+ *
149
+ * @return the same conn object passed in with its reference count
150
+ * incremented by 1
151
+ *
152
+ * @ingroup Connections
153
+ */
154
+ xmpp_conn_t * xmpp_conn_clone(xmpp_conn_t * const conn)
155
+ {
156
+ conn->ref++;
157
+ return conn;
158
+ }
159
+
160
+ /** Release a Strophe connection object.
161
+ * Decrement the reference count by one for a connection, freeing the
162
+ * connection object if the count reaches 0.
163
+ *
164
+ * @param conn a Strophe connection object
165
+ *
166
+ * @return TRUE if the connection object was freed and FALSE otherwise
167
+ *
168
+ * @ingroup Connections
169
+ */
170
+ int xmpp_conn_release(xmpp_conn_t * const conn)
171
+ {
172
+ xmpp_ctx_t *ctx;
173
+ xmpp_connlist_t *item, *prev;
174
+ xmpp_handlist_t *hlitem, *thli;
175
+ hash_iterator_t *iter;
176
+ const char *key;
177
+ int released = 0;
178
+
179
+ if (conn->ref > 1)
180
+ conn->ref--;
181
+ else {
182
+ ctx = conn->ctx;
183
+
184
+ /* remove connection from context's connlist */
185
+ if (ctx->connlist->conn == conn) {
186
+ item = ctx->connlist;
187
+ ctx->connlist = item->next;
188
+ xmpp_free(ctx, item);
189
+ } else {
190
+ prev = NULL;
191
+ item = ctx->connlist;
192
+ while (item && item->conn != conn) {
193
+ prev = item;
194
+ item = item->next;
195
+ }
196
+
197
+ if (!item) {
198
+ xmpp_error(ctx, "xmpp", "Connection not in context's list\n");
199
+ } else {
200
+ prev->next = item->next;
201
+ xmpp_free(ctx, item);
202
+ }
203
+ }
204
+
205
+ /* free handler stuff
206
+ * note that userdata is the responsibility of the client
207
+ * and the handler pointers don't need to be freed since they
208
+ * are pointers to functions */
209
+
210
+ hlitem = conn->timed_handlers;
211
+ while (hlitem) {
212
+ thli = hlitem;
213
+ hlitem = hlitem->next;
214
+
215
+ xmpp_free(ctx, thli);
216
+ }
217
+
218
+ /* id handlers
219
+ * we have to traverse the hash table freeing list elements
220
+ * then release the hash table */
221
+ iter = hash_iter_new(conn->id_handlers);
222
+ while ((key = hash_iter_next(iter))) {
223
+ hlitem = (xmpp_handlist_t *)hash_get(conn->id_handlers, key);
224
+ while (hlitem) {
225
+ thli = hlitem;
226
+ hlitem = hlitem->next;
227
+ xmpp_free(conn->ctx, thli->id);
228
+ xmpp_free(conn->ctx, thli);
229
+ }
230
+ }
231
+ hash_iter_release(iter);
232
+ hash_release(conn->id_handlers);
233
+
234
+ hlitem = conn->handlers;
235
+ while (hlitem) {
236
+ thli = hlitem;
237
+ hlitem = hlitem->next;
238
+
239
+ if (thli->ns) xmpp_free(ctx, thli->ns);
240
+ if (thli->name) xmpp_free(ctx, thli->name);
241
+ if (thli->type) xmpp_free(ctx, thli->type);
242
+ xmpp_free(ctx, thli);
243
+ }
244
+
245
+ if (conn->stream_error) {
246
+ xmpp_stanza_release(conn->stream_error->stanza);
247
+ if (conn->stream_error->text)
248
+ xmpp_free(ctx, conn->stream_error->text);
249
+ xmpp_free(ctx, conn->stream_error);
250
+ }
251
+
252
+ XML_ParserFree(conn->parser);
253
+
254
+ if (conn->domain) xmpp_free(ctx, conn->domain);
255
+ if (conn->jid) xmpp_free(ctx, conn->jid);
256
+ if (conn->pass) xmpp_free(ctx, conn->pass);
257
+ if (conn->stream_id) xmpp_free(ctx, conn->stream_id);
258
+ xmpp_free(ctx, conn);
259
+ released = 1;
260
+ }
261
+
262
+ return released;
263
+ }
264
+
265
+ /** Get the JID which is or will be bound to the connection.
266
+ *
267
+ * @param conn a Strophe connection object
268
+ *
269
+ * @return a string containing the full JID or NULL if it has not been set
270
+ *
271
+ * @ingroup Connections
272
+ */
273
+ const char *xmpp_conn_get_jid(const xmpp_conn_t * const conn)
274
+ {
275
+ return conn->jid;
276
+ }
277
+
278
+ /** Set the JID of the user that will be bound to the connection.
279
+ * If any JID was previously set, it will be discarded. This should not be
280
+ * be used after a connection is created. The function will make a copy of
281
+ * the JID string. If the supllied JID is missing the node, SASL
282
+ * ANONYMOUS authentication will be used.
283
+ *
284
+ * @param conn a Strophe connection object
285
+ * @param jid a full or bare JID
286
+ *
287
+ * @ingroup Connections
288
+ */
289
+ void xmpp_conn_set_jid(xmpp_conn_t * const conn, const char * const jid)
290
+ {
291
+ if (conn->jid) xmpp_free(conn->ctx, conn->jid);
292
+ conn->jid = xmpp_strdup(conn->ctx, jid);
293
+ }
294
+
295
+ /** Get the password used for authentication of a connection.
296
+ *
297
+ * @param conn a Strophe connection object
298
+ *
299
+ * @return a string containing the password or NULL if it has not been set
300
+ *
301
+ * @ingroup Connections
302
+ */
303
+ const char *xmpp_conn_get_pass(const xmpp_conn_t * const conn)
304
+ {
305
+ return conn->pass;
306
+ }
307
+
308
+ /** Set the password used to authenticate the connection.
309
+ * If any password was previously set, it will be discarded. The function
310
+ * will make a copy of the password string.
311
+ *
312
+ * @param conn a Strophe connection object
313
+ * @param pass the password
314
+ *
315
+ * @ingroup Connections
316
+ */
317
+ void xmpp_conn_set_pass(xmpp_conn_t * const conn, const char * const pass)
318
+ {
319
+ if (conn->pass) xmpp_free(conn->ctx, conn->pass);
320
+ conn->pass = xmpp_strdup(conn->ctx, pass);
321
+ }
322
+
323
+ /** Get the strophe context that the connection is associated with.
324
+ * @param conn a Strophe connection object
325
+ *
326
+ * @return a Strophe context
327
+ *
328
+ * @ingroup Connections
329
+ */
330
+ xmpp_ctx_t* xmpp_conn_get_context(xmpp_conn_t * const conn)
331
+ {
332
+ return conn->ctx;
333
+ }
334
+
335
+ /** Initiate a connection to the XMPP server.
336
+ * This function returns immediately after starting the connection
337
+ * process to the XMPP server, and notifiations of connection state changes
338
+ * will be sent to the callback function. The domain and port to connect to
339
+ * are usually determined by an SRV lookup for the xmpp-client service at
340
+ * the domain specified in the JID. If SRV lookup fails, altdomain and
341
+ * altport will be used instead if specified.
342
+ *
343
+ * @param conn a Strophe connection object
344
+ * @param altdomain a string with domain to use if SRV lookup fails. If this
345
+ * is NULL, the domain from the JID will be used.
346
+ * @param altport an integer port number to use if SRV lookup fails. If this
347
+ * is 0, the default port (5222) will be assumed.
348
+ * @param callback a xmpp_conn_handler callback function that will receive
349
+ * notifications of connection status
350
+ * @param userdata an opaque data pointer that will be passed to the callback
351
+ *
352
+ * @return 0 on success and -1 on an error
353
+ *
354
+ * @ingroup Connections
355
+ */
356
+ int xmpp_connect_client(xmpp_conn_t * const conn,
357
+ const char * const altdomain,
358
+ unsigned short altport,
359
+ xmpp_conn_handler callback,
360
+ void * const userdata)
361
+ {
362
+ char connectdomain[2048];
363
+ int connectport;
364
+ char *domain;
365
+
366
+ conn->type = XMPP_CLIENT;
367
+
368
+ conn->domain = xmpp_jid_domain(conn->ctx, conn->jid);
369
+ if (!conn->domain) return -1;
370
+
371
+ if (!sock_srv_lookup("xmpp-client", "tcp", conn->domain, connectdomain, 2048, &connectport))
372
+ {
373
+ xmpp_debug(conn->ctx, "xmpp", "SRV lookup failed.");
374
+ if (!altdomain)
375
+ domain = conn->domain;
376
+ else
377
+ domain = altdomain;
378
+ xmpp_debug(conn->ctx, "xmpp", "Using alternate domain %s, port %d", altdomain, altport);
379
+ strcpy(connectdomain, domain);
380
+ connectport = altport ? altport : 5222;
381
+ }
382
+ conn->sock = sock_connect(connectdomain, connectport);
383
+ if (conn->sock == -1) return -1;
384
+
385
+ /* setup handler */
386
+ conn->conn_handler = callback;
387
+ conn->userdata = userdata;
388
+
389
+ /* FIXME: it could happen that the connect returns immediately as
390
+ * successful, though this is pretty unlikely. This would be a little
391
+ * hard to fix, since we'd have to detect and fire off the callback
392
+ * from within the event loop */
393
+
394
+ conn->state = XMPP_STATE_CONNECTING;
395
+ conn->timeout_stamp = time_stamp();
396
+ xmpp_debug(conn->ctx, "xmpp", "attempting to connect to %s", connectdomain);
397
+
398
+ return 0;
399
+ }
400
+
401
+ /** Cleanly disconnect the connection.
402
+ * This function is only called by the stream parser when </stream:stream>
403
+ * is received, and it not intended to be called by code outside of Strophe.
404
+ *
405
+ * @param conn a Strophe connection object
406
+ */
407
+ void conn_disconnect_clean(xmpp_conn_t * const conn)
408
+ {
409
+ /* remove the timed handler */
410
+ xmpp_timed_handler_delete(conn, _disconnect_cleanup);
411
+ conn->error = -4;
412
+ conn_disconnect(conn);
413
+ }
414
+
415
+ /** Disconnect from the XMPP server.
416
+ * This function immediately disconnects from the XMPP server, and should
417
+ * not be used outside of the Strophe library.
418
+ *
419
+ * @param conn a Strophe connection object
420
+ */
421
+ void conn_disconnect(xmpp_conn_t * const conn)
422
+ {
423
+ xmpp_debug(conn->ctx, "xmpp", "Closing socket.");
424
+ conn->state = XMPP_STATE_DISCONNECTED;
425
+ if (conn->tls) {
426
+ tls_stop(conn->tls);
427
+ tls_free(conn->tls);
428
+ conn->tls = NULL;
429
+ }
430
+
431
+ sock_close(conn->sock);
432
+ /* fire off connection handler */
433
+ if (NULL != conn->conn_handler) {
434
+ conn->conn_handler(conn, XMPP_CONN_DISCONNECT, conn->error,
435
+ conn->stream_error, conn->userdata);
436
+ }
437
+ }
438
+
439
+ /* timed handler for cleanup if normal disconnect procedure takes too long */
440
+ static int _disconnect_cleanup(xmpp_conn_t * const conn,
441
+ void * const userdata)
442
+ {
443
+ xmpp_debug(conn->ctx, "xmpp",
444
+ "disconnection forced by cleanup timeout");
445
+ conn->error = -5;
446
+ conn_disconnect(conn);
447
+
448
+ return 0;
449
+ }
450
+
451
+ /** Initiate termination of the connection to the XMPP server.
452
+ * This function starts the disconnection sequence by sending
453
+ * </stream:stream> to the XMPP server. This function will do nothing
454
+ * if the connection state is CONNECTING or CONNECTED.
455
+ *
456
+ * @param conn a Strophe connection object
457
+ *
458
+ * @ingroup Connections
459
+ */
460
+ void xmpp_disconnect(xmpp_conn_t * const conn)
461
+ {
462
+ if (conn->state != XMPP_STATE_CONNECTING &&
463
+ conn->state != XMPP_STATE_CONNECTED)
464
+ return;
465
+
466
+ /* close the stream */
467
+ xmpp_send_raw_string(conn, "</stream:stream>");
468
+
469
+ /* setup timed handler in case disconnect takes too long */
470
+ handler_add_timed(conn, _disconnect_cleanup,
471
+ DISCONNECT_TIMEOUT, NULL);
472
+ }
473
+
474
+ /** Send a raw string to the XMPP server.
475
+ * This function is a convenience function to send raw string data to the
476
+ * XMPP server. It is used by Strophe to send short messages instead of
477
+ * building up an XML stanza with DOM methods. This should be used with care
478
+ * as it does not validate the data; invalid data may result in immediate
479
+ * stream termination by the XMPP server.
480
+ *
481
+ * @param conn a Strophe connection object
482
+ * @param fmt a printf-style format string followed by a variable list of
483
+ * arguments to format
484
+ */
485
+ void xmpp_send_raw_string(xmpp_conn_t * const conn,
486
+ const char * const fmt, ...)
487
+ {
488
+ va_list ap;
489
+ size_t len;
490
+ char buf[1024]; /* small buffer for common case */
491
+ char *bigbuf;
492
+
493
+ va_start(ap, fmt);
494
+ len = xmpp_vsnprintf(buf, 1024, fmt, ap);
495
+ va_end(ap);
496
+
497
+ if (len >= 1024) {
498
+ /* we need more space for this data, so we allocate a big
499
+ * enough buffer and print to that */
500
+ len++; /* account for trailing \0 */
501
+ bigbuf = xmpp_alloc(conn->ctx, len);
502
+ if (!bigbuf) {
503
+ xmpp_debug(conn->ctx, "xmpp", "Could not allocate memory for send_raw_string");
504
+ return;
505
+ }
506
+ va_start(ap, fmt);
507
+ xmpp_vsnprintf(bigbuf, len, fmt, ap);
508
+ va_end(ap);
509
+
510
+ xmpp_debug(conn->ctx, "conn", "SENT: %s", bigbuf);
511
+
512
+ /* len - 1 so we don't send trailing \0 */
513
+ xmpp_send_raw(conn, bigbuf, len - 1);
514
+
515
+ xmpp_free(conn->ctx, bigbuf);
516
+ } else {
517
+ xmpp_debug(conn->ctx, "conn", "SENT: %s", buf);
518
+
519
+ xmpp_send_raw(conn, buf, len);
520
+ }
521
+ }
522
+
523
+ /** Send raw bytes to the XMPP server.
524
+ * This function is a convenience function to send raw bytes to the
525
+ * XMPP server. It is usedly primarly by xmpp_send_raw_string. This
526
+ * function should be used with care as it does not validate the bytes and
527
+ * invalid data may result in stream termination by the XMPP server.
528
+ *
529
+ * @param conn a Strophe connection object
530
+ * @param data a buffer of raw bytes
531
+ * @param len the length of the data in the buffer
532
+ */
533
+ void xmpp_send_raw(xmpp_conn_t * const conn,
534
+ const char * const data, const size_t len)
535
+ {
536
+ xmpp_send_queue_t *item;
537
+
538
+ if (conn->state != XMPP_STATE_CONNECTED) return;
539
+
540
+ /* create send queue item for queue */
541
+ item = xmpp_alloc(conn->ctx, sizeof(xmpp_send_queue_t));
542
+ if (!item) return;
543
+
544
+ item->data = xmpp_alloc(conn->ctx, len);
545
+ if (!item->data) {
546
+ xmpp_free(conn->ctx, item);
547
+ return;
548
+ }
549
+ memcpy(item->data, data, len);
550
+ item->len = len;
551
+ item->next = NULL;
552
+ item->written = 0;
553
+
554
+ /* add item to the send queue */
555
+ if (!conn->send_queue_tail) {
556
+ /* first item, set head and tail */
557
+ conn->send_queue_head = item;
558
+ conn->send_queue_tail = item;
559
+ } else {
560
+ /* add to the tail */
561
+ conn->send_queue_tail->next = item;
562
+ conn->send_queue_tail = item;
563
+ }
564
+ conn->send_queue_len++;
565
+ }
566
+
567
+ /** Send an XML stanza to the XMPP server.
568
+ * This is the main way to send data to the XMPP server. The function will
569
+ * terminate without action if the connection state is not CONNECTED.
570
+ *
571
+ * @param conn a Strophe connection object
572
+ * @param stanza a Strophe stanza object
573
+ *
574
+ * @ingroup Connections
575
+ */
576
+ void xmpp_send(xmpp_conn_t * const conn,
577
+ xmpp_stanza_t * const stanza)
578
+ {
579
+ char *buf;
580
+ size_t len;
581
+ int ret;
582
+
583
+ if (conn->state == XMPP_STATE_CONNECTED) {
584
+ if ((ret = xmpp_stanza_to_text(stanza, &buf, &len)) == 0) {
585
+ xmpp_send_raw(conn, buf, len);
586
+ xmpp_debug(conn->ctx, "conn", "SENT: %s", buf);
587
+ xmpp_free(conn->ctx, buf);
588
+ }
589
+ }
590
+ }
591
+
592
+ /** Send the opening &lt;stream:stream&gt; tag to the server.
593
+ * This function is used by Strophe to begin an XMPP stream. It should
594
+ * not be used outside of the library.
595
+ *
596
+ * @param conn a Strophe connection object
597
+ */
598
+ void conn_open_stream(xmpp_conn_t * const conn)
599
+ {
600
+ xmpp_send_raw_string(conn,
601
+ "<?xml version=\"1.0\"?>" \
602
+ "<stream:stream to=\"%s\" " \
603
+ "xml:lang=\"%s\" " \
604
+ "version=\"1.0\" " \
605
+ "xmlns=\"%s\" " \
606
+ "xmlns:stream=\"%s\">",
607
+ conn->domain,
608
+ conn->lang,
609
+ conn->type == XMPP_CLIENT ? XMPP_NS_CLIENT : XMPP_NS_COMPONENT,
610
+ XMPP_NS_STREAMS);
611
+ }