static int _sx_compress_wio(sx_t s, sx_plugin_t p, sx_buf_t buf) { _sx_compress_conn_t sc = (_sx_compress_conn_t) s->plugin_data[p->index]; int ret; sx_error_t sxe; /* only bothering if they asked for wrappermode */ if(!(s->flags & SX_COMPRESS_WRAPPER) || !s->compressed) return 1; _sx_debug(ZONE, "in _sx_compress_wio"); /* move the data into the zlib write buffer */ if(buf->len > 0) { _sx_debug(ZONE, "loading %d bytes into zlib write buffer", buf->len); _sx_buffer_alloc_margin(sc->wbuf, 0, buf->len); memcpy(sc->wbuf->data + sc->wbuf->len, buf->data, buf->len); sc->wbuf->len += buf->len; _sx_buffer_clear(buf); } /* compress the data */ if(sc->wbuf->len > 0) { sc->wstrm.avail_in = sc->wbuf->len; sc->wstrm.next_in = sc->wbuf->data; /* deflate() on write buffer until there is data to compress */ do { /* make place for deflated data */ _sx_buffer_alloc_margin(buf, 0, sc->wbuf->len + SX_COMPRESS_CHUNK); sc->wstrm.avail_out = sc->wbuf->len + SX_COMPRESS_CHUNK; sc->wstrm.next_out = buf->data + buf->len; ret = deflate(&(sc->wstrm), Z_SYNC_FLUSH); assert(ret != Z_STREAM_ERROR); buf->len += sc->wbuf->len + SX_COMPRESS_CHUNK - sc->wstrm.avail_out; } while (sc->wstrm.avail_out == 0); if(ret != Z_OK || sc->wstrm.avail_in != 0) { /* throw an error */ _sx_gen_error(sxe, SX_ERR_COMPRESS, "compression error", "Error during compression"); _sx_event(s, event_ERROR, (void *) &sxe); sx_error(s, stream_err_INTERNAL_SERVER_ERROR, "Error during compression"); sx_close(s); return -2; /* fatal */ } sc->wbuf->len = sc->wstrm.avail_in; sc->wbuf->data = sc->wstrm.next_in; } _sx_debug(ZONE, "passing %d bytes from zlib write buffer", buf->len); return 1; }
static int _sx_sasl_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) { sasl_conn_t *sasl; sx_error_t sxe; int *x, len; char *out; sasl = ((_sx_sasl_data_t) s->plugin_data[p->index])->sasl; /* if there's no security layer, don't bother */ sasl_getprop(sasl, SASL_SSF, (const void **) &x); if(*x == 0) return 1; _sx_debug(ZONE, "doing sasl decode"); /* decode the input */ if (sasl_decode(sasl, buf->data, buf->len, (const char **) &out, &len) != SASL_OK) { /* Fatal error */ _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", "sasl_decode failed, closing stream"); _sx_event(s, event_ERROR, (void *) &sxe); _sx_state(s, state_CLOSING); return -1; } /* replace the buffer */ _sx_buffer_set(buf, out, len, NULL); _sx_debug(ZONE, "%d bytes decoded from sasl channel", len); return 1; }
int sx_compress_client_compress(sx_plugin_t p, sx_t s, const char *pemfile) { assert((int) (p != NULL)); assert((int) (s != NULL)); /* sanity */ if(s->type != type_CLIENT || s->state != state_STREAM) { _sx_debug(ZONE, "wrong conn type or state for client compress"); return 1; } /* check if we're already compressed */ if((s->flags & SX_COMPRESS_WRAPPER) || s->compressed) { _sx_debug(ZONE, "channel already compressed"); return 1; } _sx_debug(ZONE, "initiating compress sequence"); /* go */ jqueue_push(s->wbufq, _sx_buffer_new("<compress xmlns='" uri_COMPRESS "'><method>zlib</method></compress>", sizeof(uri_COMPRESS)-1 + 51, NULL, NULL), 0); s->want_write = 1; _sx_event(s, event_WANT_WRITE, NULL); return 0; }
static int _sx_sasl_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) { sx_error_t sxe; int len, ret; char *out; Gsasl_session *sd = (Gsasl_session *) s->plugin_data[p->index]; _sx_debug(ZONE, "doing sasl decode"); /* decode the input */ ret = gsasl_decode(sd, buf->data, buf->len, &out, &len); if (ret != GSASL_OK) { _sx_debug(ZONE, "gsasl_decode failed (%d): %s", ret, gsasl_strerror (ret)); /* Fatal error */ _sx_gen_error(sxe, SX_ERR_AUTH, "SASL Stream decoding failed", (char*) gsasl_strerror (ret)); _sx_event(s, event_ERROR, (void *) &sxe); return -1; } /* replace the buffer */ _sx_buffer_set(buf, out, len, NULL); free(out); _sx_debug(ZONE, "%d bytes decoded from sasl channel", len); return 1; }
static int _sx_sasl_wio(sx_t s, sx_plugin_t p, sx_buf_t buf) { sasl_conn_t *sasl; int *x, len, pos, reslen, maxbuf; char *out, *result; int sasl_ret; sx_error_t sxe; sasl = ((_sx_sasl_data_t) s->plugin_data[p->index])->sasl; /* if there's no security layer, don't bother */ sasl_getprop(sasl, SASL_SSF, (const void **) &x); if(*x == 0) return 1; _sx_debug(ZONE, "doing sasl encode"); /* can only encode x bytes at a time */ sasl_getprop(sasl, SASL_MAXOUTBUF, (const void **) &x); maxbuf = *x; /* encode the output */ pos = 0; result = NULL; reslen = 0; while(pos < buf->len) { if((buf->len - pos) < maxbuf) maxbuf = buf->len - pos; sasl_ret = sasl_encode(sasl, &buf->data[pos], maxbuf, (const char **) &out, &len); if (sasl_ret != SASL_OK) { _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", "sasl_encode failed, closing stream"); _sx_event(s, event_ERROR, (void *) &sxe); _sx_state(s, state_CLOSING); return 1; } result = (char *) realloc(result, sizeof(char) * (reslen + len)); memcpy(&result[reslen], out, len); reslen += len; pos += maxbuf; } /* replace the buffer */ _sx_buffer_set(buf, result, reslen, result); _sx_debug(ZONE, "%d bytes encoded for sasl channel", buf->len); return 1; }
/** send a new nad out */ int _sx_nad_write(sx_t s, nad_t nad, int elem) { const char *out; int len; /* silently drop it if we're closing or closed */ if(s->state >= state_CLOSING) { log_debug(ZONE, "stream closed, dropping outgoing packet"); nad_free(nad); return 1; } /* run it through the plugins */ if(_sx_chain_nad_write(s, nad, elem) == 0) return 1; /* serialise it */ nad_print(nad, elem, &out, &len); _sx_debug(ZONE, "queueing for write: %.*s", len, out); /* ready to go */ jqueue_push(s->wbufq, _sx_buffer_new(out, len, NULL, NULL), 0); nad_free(nad); /* things to write */ s->want_write = 1; return 0; }
sx_plugin_t sx_env_plugin(sx_env_t env, sx_plugin_init_t init, ...) { sx_plugin_t p; int ret; va_list args; assert((int) (env != NULL)); assert((int) (init != NULL)); va_start(args, init); p = (sx_plugin_t) calloc(1, sizeof(struct _sx_plugin_st)); p->env = env; p->index = env->nplugins; ret = (init)(env, p, args); va_end(args); if(ret != 0) { free(p); return NULL; } env->plugins = (sx_plugin_t *) realloc(env->plugins, sizeof(sx_plugin_t) * (env->nplugins + 1)); env->plugins[env->nplugins] = p; env->nplugins++; _sx_debug(ZONE, "plugin initialised (index %d)", p->index); return p; }
//** send an extended error with custom contents other than text */ // Ideally should be merged with sx_error. sx_error should permit additional content beneath the <stream:error> element, other than a <text> node. void _sx_error_extended(sx_t s, int err, const char *content) { int len = 0; sx_buf_t buf; /* build the string */ if(s->state < state_STREAM) len = strlen(uri_STREAMS) + 61; len += strlen(uri_STREAMS) + strlen(uri_STREAM_ERR) + strlen(_stream_errors[err]) + 58; if(content != NULL) len += strlen(content) + strlen(_stream_errors[err]) + 2; buf = _sx_buffer_new(NULL, len, NULL, NULL); len = 0; if(s->state < state_STREAM) len = sprintf(buf->data, "<stream:stream xmlns:stream='" uri_STREAMS "' version='1.0'>"); if(content == NULL) len += sprintf(&(buf->data[len]), "<stream:error xmlns:stream='" uri_STREAMS "'><%s xmlns='" uri_STREAM_ERR "'/></stream:error>", _stream_errors[err]); else len += sprintf(&(buf->data[len]), "<stream:error xmlns:stream='" uri_STREAMS "'><%s xmlns='" uri_STREAM_ERR "'>%s</%s></stream:error>", _stream_errors[err], content, _stream_errors[err]); if(s->state < state_STREAM) len += sprintf(&(buf->data[len]), "</stream:stream>"); buf->len--; assert(len == buf->len); _sx_debug(ZONE, "prepared error: %.*s", buf->len, buf->data); /* go */ jqueue_push(s->wbufq, buf, 0); /* stuff to write */ s->want_write = 1; }
/* code stolen from SSL_CTX_set_verify(3) */ static int _sx_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { char buf[256]; X509 *err_cert; int err, depth; err_cert = X509_STORE_CTX_get_current_cert(ctx); err = X509_STORE_CTX_get_error(ctx); depth = X509_STORE_CTX_get_error_depth(ctx); /* * Ignore errors when we can't get CRLs in the certificate */ if (!preverify_ok && err == X509_V_ERR_UNABLE_TO_GET_CRL) { _sx_debug(ZONE, "ignoring verify error:num=%d:%s:depth=%d:%s\n", err, X509_verify_cert_error_string(err), depth, buf); preverify_ok = 1; } /* * Retrieve the pointer to the SSL of the connection currently treated * and the application specific data stored into the SSL object. */ X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); if (!preverify_ok) { _sx_debug(ZONE, "verify error:num=%d:%s:depth=%d:%s\n", err, X509_verify_cert_error_string(err), depth, buf); } else { _sx_debug(ZONE, "OK! depth=%d:%s", depth, buf); } /* * At this point, err contains the last verification error. We can use * it for something special */ if (!preverify_ok && (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT)) { X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256); _sx_debug(ZONE, "issuer= %s\n", buf); } return preverify_ok; }
static void _sx_compress_notify_compress(sx_t s, void *arg) { _sx_debug(ZONE, "preparing for compress"); _sx_reset(s); /* start listening */ sx_server_init(s, s->flags | SX_COMPRESS_WRAPPER); }
/** we can write */ static int _sx_get_pending_write(sx_t s) { sx_buf_t in, out; int ret; assert(s != NULL); if (s->wbufpending != NULL) { /* there's already a pending buffer ready to write */ return 0; } /* get the first buffer off the queue */ in = jqueue_pull(s->wbufq); if(in == NULL) { /* if there was a write event, and something is interested, we still have to tell the plugins */ in = _sx_buffer_new(NULL, 0, NULL, NULL); } /* if there's more to write, we want to make sure we get it */ s->want_write = jqueue_size(s->wbufq); /* make a copy for processing */ out = _sx_buffer_new(in->data, in->len, in->notify, in->notify_arg); _sx_debug(ZONE, "encoding %d bytes for writing: %.*s", in->len, in->len, in->data); /* run it by the plugins */ ret = _sx_chain_io_write(s, out); if(ret <= 0) { /* TODO/!!!: Are we leaking the 'out' buffer here? How about the 'in' buffer? */ if(ret == -1) { /* temporary failure, push it back on the queue */ jqueue_push(s->wbufq, in, (s->wbufq->front != NULL) ? s->wbufq->front->priority : 0); s->want_write = 1; } else if(ret == -2) { /* permanent failure, its all over */ /* !!! shut down */ s->want_read = s->want_write = 0; return -1; } /* done */ return 0; } _sx_buffer_free(in); if (out->len == 0) /* if there's nothing to write, then we're done */ _sx_buffer_free(out); else s->wbufpending = out; return 0; }
/** process handshake packets from the client */ static int _sx_ack_process(sx_t s, sx_plugin_t p, nad_t nad) { int attr; /* not interested if we're not a server */ if(s->type != type_SERVER) return 1; /* only want ack packets */ if((NAD_ENS(nad, 0) < 0 || NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_ACK) || strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_ACK, strlen(uri_ACK)) != 0)) return 1; /* pings */ if(NAD_ENAME_L(nad, 0) == 4 && strncmp(NAD_ENAME(nad, 0), "ping", 4) == 0) { jqueue_push(s->wbufq, _sx_buffer_new("<ack:pong/>", 11, NULL, NULL), 0); s->want_write = 1; /* handled the packet */ nad_free(nad); return 0; } /* enable only when authenticated */ if(s->state == state_OPEN && NAD_ENAME_L(nad, 0) == 6 && strncmp(NAD_ENAME(nad, 0), "enable", 6) == 0) { jqueue_push(s->wbufq, _sx_buffer_new("<ack:enabled/>", 14, NULL, NULL), 254); s->want_write = 1; s->plugin_data[p->index] = (void *) 1; /* handled the packet */ nad_free(nad); return 0; } /* 'r' or 'a' when enabled */ if(s->plugin_data[p->index] != NULL && NAD_ENAME_L(nad, 0) == 1 && (strncmp(NAD_ENAME(nad, 0), "r", 1) == 0 || strncmp(NAD_ENAME(nad, 0), "a", 1) == 0) ) { attr = nad_find_attr(nad, 0, -1, "c", NULL); if(attr >= 0) { char *buf = (char *) malloc(sizeof(char) * (NAD_AVAL_L(nad, attr) + 13 + 1)); snprintf(buf, NAD_AVAL_L(nad, attr) + 13 + 1, "<ack:a b='%.*s'/>", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); jqueue_push(s->wbufq, _sx_buffer_new(buf, NAD_AVAL_L(nad, attr) + 13, NULL, NULL), 255); free(buf); s->want_write = 1; } /* handled the packet */ nad_free(nad); return 0; } _sx_debug(ZONE, "unhandled ack namespace element '%.*s', dropping packet", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0)); nad_free(nad); return 0; }
/** sx features callback */ static void _address_features(sx_t s, sx_plugin_t p, nad_t nad) { int ns; /* offer feature only when not authenticated yet */ if(s->state >= state_OPEN) return; _sx_debug(ZONE, "adding address feature"); ns = nad_add_namespace(nad, uri_ADDRESS_FEATURE, NULL); nad_append_elem(nad, ns, "address", 1); nad_append_cdata(nad, s->ip, strlen(s->ip), 2); }
/** args: none */ int sx_compress_init(sx_env_t env, sx_plugin_t p, va_list args) { _sx_debug(ZONE, "initialising compression plugin"); p->client = _sx_compress_new; p->server = _sx_compress_new; p->rio = _sx_compress_rio; p->wio = _sx_compress_wio; p->features = _sx_compress_features; p->process = _sx_compress_process; p->free = _sx_compress_free; return 0; }
static void _sx_compress_features(sx_t s, sx_plugin_t p, nad_t nad) { int ns; /* if the session is already compressed, or the app told us not to, * or STARTTLS is required and stream is not encrypted yet, then we don't offer anything */ if(s->compressed || !(s->flags & SX_COMPRESS_OFFER) || ((s->flags & SX_SSL_STARTTLS_REQUIRE) && s->ssf == 0)) return; _sx_debug(ZONE, "offering compression"); ns = nad_add_namespace(nad, uri_COMPRESS_FEATURE, NULL); nad_append_elem(nad, ns, "compression", 1); nad_append_elem(nad, ns, "method", 2); nad_append_cdata(nad, "zlib", 4, 3); }
/** make the stream authenticated second time round */ static void _sx_sasl_stream(sx_t s, sx_plugin_t p) { Gsasl_session *sd = (Gsasl_session *) s->plugin_data[p->index]; /* do nothing the first time */ if(sd == NULL) return; /* are we auth'd? */ if(NULL == gsasl_property_fast(sd, GSASL_AUTHID)) { _sx_debug(ZONE, "not auth'd, not advancing to auth'd state yet"); return; } /* otherwise, its auth time */ _sx_sasl_open(s, sd); }
/** send raw data out */ int _sx_raw_write(sx_t s, const char *buf, int len) { /* siltently drop it if we're closing or closed */ if(s->state >= state_CLOSING) { log_debug(ZONE, "stream closed, dropping outgoing raw data"); return 1; } _sx_debug(ZONE, "queuing for write: %.*s", len, buf); /* ready to go */ jqueue_push(s->wbufq, _sx_buffer_new(buf, len, NULL, NULL), 0); /* things to write */ s->want_write = 1; return 0; }
/** move the stream to the auth state */ void _sx_sasl_open(sx_t s, Gsasl_session *sd) { char *method, *authzid; const char *realm = NULL; struct sx_sasl_creds_st creds = {NULL, NULL, NULL, NULL}; _sx_sasl_t ctx = gsasl_session_hook_get(sd); const char *mechname = gsasl_mechanism_name (sd); /* get the method */ method = (char *) malloc(sizeof(char) * (strlen(mechname) + 6)); sprintf(method, "SASL/%s", mechname); /* and the authorization identifier */ creds.authzid = gsasl_property_fast(sd, GSASL_AUTHZID); creds.authnid = gsasl_property_fast(sd, GSASL_AUTHID); creds.realm = gsasl_property_fast(sd, GSASL_REALM); if(0 && ctx && ctx->cb) { /* not supported yet */ if((ctx->cb)(sx_sasl_cb_CHECK_AUTHZID, &creds, NULL, s, ctx->cbarg)!=sx_sasl_ret_OK) { _sx_debug(ZONE, "stream authzid: %s verification failed, not advancing to auth state", creds.authzid); free(method); return; } } else if (NULL != gsasl_property_fast(sd, GSASL_GSSAPI_DISPLAY_NAME)) { creds.authzid = strdup(gsasl_property_fast(sd, GSASL_GSSAPI_DISPLAY_NAME)); authzid = NULL; } else { /* override unchecked arbitrary authzid */ if(creds.realm && creds.realm[0] != '\0') { realm = creds.realm; } else { realm = s->req_to; } authzid = (char *) malloc(sizeof(char) * (strlen(creds.authnid) + strlen(realm) + 2)); sprintf(authzid, "%s@%s", creds.authnid, realm); creds.authzid = authzid; } /* proceed stream to authenticated state */ sx_auth(s, method, creds.authzid); free(method); if(authzid) free(authzid); }
/** send an error */ void _sx_error(sx_t s, int err, const char *text) { int len = 0; sx_buf_t buf; /* open stream if not already */ if(s->state < state_STREAM) { if (s->flags & SX_WEBSOCKET_WRAPPER) jqueue_push(s->wbufq, _sx_buffer_new("<open xmlns='" uri_XFRAMING "' version='1.0' />", sizeof(uri_XFRAMING) + 30, NULL, NULL), 0); else jqueue_push(s->wbufq, _sx_buffer_new("<stream:stream xmlns:stream='" uri_STREAMS "' version='1.0'>", sizeof(uri_STREAMS) + 44, NULL, NULL), 0); } /* build the error */ len = strlen(uri_STREAMS) + strlen(uri_STREAM_ERR) + strlen(_stream_errors[err]) + 58; if(text != NULL) len += strlen(uri_STREAM_ERR) + strlen(text) + 22; buf = _sx_buffer_new(NULL, len, NULL, NULL); if(text == NULL) len = sprintf(buf->data, "<stream:error xmlns:stream='" uri_STREAMS "'><%s xmlns='" uri_STREAM_ERR "'/></stream:error>", _stream_errors[err]); else len = sprintf(buf->data, "<stream:error xmlns:stream='" uri_STREAMS "'><%s xmlns='" uri_STREAM_ERR "'/><text xmlns='" uri_STREAM_ERR "'>%s</text></stream:error>", _stream_errors[err], text); buf->len--; assert(len == buf->len); _sx_debug(ZONE, "prepared error: %.*s", buf->len, buf->data); jqueue_push(s->wbufq, buf, 0); /* close the stream if needed */ if(s->state < state_STREAM) { if (s->flags & SX_WEBSOCKET_WRAPPER) jqueue_push(s->wbufq, _sx_buffer_new("<close xmlns='" uri_XFRAMING "' />", sizeof(uri_XFRAMING) + 17, NULL, NULL), 0); else jqueue_push(s->wbufq, _sx_buffer_new("</stream:stream>", 16, NULL, NULL), 0); } /* stuff to write */ s->want_write = 1; }
static void _sx_compress_new(sx_t s, sx_plugin_t p) { _sx_compress_conn_t sc; /* only bothering if they asked for wrappermode */ if(!(s->flags & SX_COMPRESS_WRAPPER) || s->compressed) return; _sx_debug(ZONE, "preparing for compressed connect for %d", s->tag); sc = (_sx_compress_conn_t) calloc(1, sizeof(struct _sx_compress_conn_st)); /* initialize streams */ sc->rstrm.zalloc = Z_NULL; sc->rstrm.zfree = Z_NULL; sc->rstrm.opaque = Z_NULL; sc->rstrm.avail_in = 0; sc->rstrm.next_in = Z_NULL; inflateInit(&(sc->rstrm)); sc->wstrm.zalloc = Z_NULL; sc->wstrm.zfree = Z_NULL; sc->wstrm.opaque = Z_NULL; deflateInit(&(sc->wstrm), Z_DEFAULT_COMPRESSION); /* read and write buffers */ sc->rbuf = _sx_buffer_new(NULL, 0, NULL, NULL); sc->wbuf = _sx_buffer_new(NULL, 0, NULL, NULL); s->plugin_data[p->index] = (void *) sc; /* bring the plugin online */ _sx_chain_io_plugin(s, p); /* mark stream compressed */ s->compressed = 1; }
static int _sx_compress_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) { _sx_compress_conn_t sc = (_sx_compress_conn_t) s->plugin_data[p->index]; int ret; sx_error_t sxe; /* only bothering if they asked for wrappermode */ if(!(s->flags & SX_COMPRESS_WRAPPER) || !s->compressed) return 1; _sx_debug(ZONE, "in _sx_compress_rio"); /* move the data into the zlib read buffer */ if(buf->len > 0) { _sx_debug(ZONE, "loading %d bytes into zlib read buffer", buf->len); _sx_buffer_alloc_margin(sc->rbuf, 0, buf->len); memcpy(sc->rbuf->data + sc->rbuf->len, buf->data, buf->len); sc->rbuf->len += buf->len; _sx_buffer_clear(buf); } /* decompress the data */ if(sc->rbuf->len > 0) { sc->rstrm.avail_in = sc->rbuf->len; sc->rstrm.next_in = sc->rbuf->data; /* run inflate() on read buffer while able to fill the output buffer */ do { /* make place for inflated data */ _sx_buffer_alloc_margin(buf, 0, SX_COMPRESS_CHUNK); sc->rstrm.avail_out = SX_COMPRESS_CHUNK; sc->rstrm.next_out = buf->data + buf->len; ret = inflate(&(sc->rstrm), Z_SYNC_FLUSH); assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: case Z_DATA_ERROR: case Z_MEM_ERROR: /* throw an error */ _sx_gen_error(sxe, SX_ERR_COMPRESS, "compression error", "Error during decompression"); _sx_event(s, event_ERROR, (void *) &sxe); sx_error(s, stream_err_INVALID_XML, "Error during decompression"); sx_close(s); return -2; } buf->len += SX_COMPRESS_CHUNK - sc->rstrm.avail_out; } while (sc->rstrm.avail_out == 0); sc->rbuf->len = sc->rstrm.avail_in; sc->rbuf->data = sc->rstrm.next_in; } _sx_debug(ZONE, "passing %d bytes from zlib read buffer", buf->len); /* flag if we want to read */ if(sc->rbuf->len > 0) s->want_read = 1; if(buf->len == 0) return 0; return 1; }
static int _sx_compress_process(sx_t s, sx_plugin_t p, nad_t nad) { int flags; char *ns = NULL, *to = NULL, *from = NULL, *version = NULL; sx_error_t sxe; /* not interested if we're a server and we never offered it */ if(s->type == type_SERVER && !(s->flags & SX_COMPRESS_OFFER)) return 1; /* only want compress packets */ 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) return 1; /* compress from client */ if(s->type == type_SERVER) { if(NAD_ENAME_L(nad, 0) == 8 && strncmp(NAD_ENAME(nad, 0), "compress", 8) == 0) { nad_free(nad); /* can't go on if we've been here before */ if(s->compressed) { _sx_debug(ZONE, "compress requested on already compressed channel, dropping packet"); return 0; } _sx_debug(ZONE, "compress requested, setting up"); /* go ahead */ jqueue_push(s->wbufq, _sx_buffer_new("<compressed xmlns='" uri_COMPRESS "'/>", sizeof(uri_COMPRESS)-1 + 22, _sx_compress_notify_compress, NULL), 0); s->want_write = 1; /* handled the packet */ return 0; } } else if(s->type == type_CLIENT) { /* kick off the handshake */ if(NAD_ENAME_L(nad, 0) == 7 && strncmp(NAD_ENAME(nad, 0), "compressed", 7) == 0) { nad_free(nad); /* save interesting bits */ flags = s->flags; if(s->ns != NULL) ns = strdup(s->ns); if(s->req_to != NULL) to = strdup(s->req_to); if(s->req_from != NULL) from = strdup(s->req_from); if(s->req_version != NULL) version = strdup(s->req_version); /* reset state */ _sx_reset(s); _sx_debug(ZONE, "server ready for compression, starting"); /* second time round */ sx_client_init(s, flags | SX_COMPRESS_WRAPPER, ns, to, from, version); /* free bits */ if(ns != NULL) free(ns); if(to != NULL) free(to); if(from != NULL) free(from); if(version != NULL) free(version); return 0; } /* busted server */ if(NAD_ENAME_L(nad, 0) == 7 && strncmp(NAD_ENAME(nad, 0), "failure", 7) == 0) { nad_free(nad); _sx_debug(ZONE, "server can't handle compression, business as usual"); _sx_gen_error(sxe, SX_ERR_COMPRESS_FAILURE, "compress failure", "Server was unable to establish compression"); _sx_event(s, event_ERROR, (void *) &sxe); return 0; } } _sx_debug(ZONE, "unknown compress namespace element '%.*s', dropping packet", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0)); nad_free(nad); return 0; }
/** we can read */ int sx_can_read(sx_t s) { sx_buf_t in, out; int read, ret; assert((int) (s != NULL)); /* do we care? */ if(!s->want_read && s->state < state_CLOSING) return 0; /* no more thanks */ _sx_debug(ZONE, "%d ready for reading", s->tag); /* new buffer */ in = _sx_buffer_new(NULL, 1024, NULL, NULL); /* get them to read stuff */ read = _sx_event(s, event_READ, (void *) in); /* bail if something went wrong */ if(read < 0) { _sx_buffer_free(in); s->want_read = 0; s->want_write = 0; return 0; } if(read == 0) { /* nothing to read * should never happen because we did get a read event, * thus there is something to read, or error handled * via (read < 0) block before (errors return -1) */ _sx_debug(ZONE, "decoded 0 bytes read data - this should not happen"); _sx_buffer_free(in); } else { _sx_debug(ZONE, "passed %d read bytes", in->len); /* count bytes read */ s->rbytes += in->len; /* make a copy for processing */ out = _sx_buffer_new(in->data, in->len, in->notify, in->notify_arg); /* run it by the plugins */ ret = _sx_chain_io_read(s, out); /* check if the stanza size limit is exceeded (it wasn't reset by parser) */ if(s->rbytesmax && s->rbytes > s->rbytesmax) { _sx_debug(ZONE, "maximum stanza size (%d) exceeded by reading %d bytes", s->rbytesmax, s->pbytes); /* make it fail */ ret = -1; } if(ret <= 0) { if(ret < 0) { /* permanent failure, its all over */ /* !!! shut down */ s->want_read = s->want_write = 0; } _sx_buffer_free(in); _sx_buffer_free(out); /* done */ if(s->want_write) _sx_event(s, event_WANT_WRITE, NULL); return s->want_read; } _sx_buffer_free(in); _sx_debug(ZONE, "decoded read data (%d bytes): %.*s", out->len, out->len, out->data); /* into the parser with you */ _sx_process_read(s, out); } /* if we've written everything, and we're closed, then inform the app it can kill us */ if(s->want_write == 0 && s->state == state_CLOSING) { _sx_state(s, state_CLOSED); _sx_event(s, event_CLOSED, NULL); return 0; } if(s->state == state_CLOSED) return 0; if(s->want_write) _sx_event(s, event_WANT_WRITE, NULL); return s->want_read; }
/** handler for read data */ void _sx_process_read(sx_t s, sx_buf_t buf) { sx_error_t sxe; nad_t nad; char *errstring; int i; int ns, elem; /* Note that buf->len can validly be 0 here, if we got data from the socket but the plugin didn't return anything to us (e.g. a SSL packet was split across a tcp segment boundary) */ /* count bytes parsed */ s->pbytes += buf->len; /* parse it */ if(XML_Parse(s->expat, buf->data, buf->len, 0) == 0) { /* only report error we haven't already */ if(!s->fail) { /* parse error */ errstring = (char *) XML_ErrorString(XML_GetErrorCode(s->expat)); _sx_debug(ZONE, "XML parse error: %s, character %d: %.*s", errstring, XML_GetCurrentByteIndex(s->expat) - s->tbytes, buf->len, buf->data); _sx_gen_error(sxe, SX_ERR_XML_PARSE, "XML parse error", errstring); _sx_event(s, event_ERROR, (void *) &sxe); _sx_error(s, stream_err_XML_NOT_WELL_FORMED, errstring); _sx_close(s); _sx_buffer_free(buf); return; } /* !!! is this the right thing to do? we should probably set * s->fail and let the code further down handle it. */ _sx_buffer_free(buf); return; } /* check if the stanza size limit is exceeded (it wasn't reset by parser) */ if(s->rbytesmax && s->pbytes > s->rbytesmax) { /* parse error */ _sx_debug(ZONE, "maximum stanza size (%d) exceeded by reading %d bytes", s->rbytesmax, s->pbytes); errstring = (char *) XML_ErrorString(XML_GetErrorCode(s->expat)); _sx_gen_error(sxe, SX_ERR_XML_PARSE, "stream read error", "Maximum stanza size exceeded"); _sx_event(s, event_ERROR, (void *) &sxe); _sx_error(s, stream_err_POLICY_VIOLATION, errstring); _sx_close(s); _sx_buffer_free(buf); return; } /* count bytes processed */ s->tbytes += buf->len; /* done with the buffer */ _sx_buffer_free(buf); /* process completed nads */ if(s->state >= state_STREAM) while((nad = jqueue_pull(s->rnadq)) != NULL) { int plugin_error; #ifdef SX_DEBUG const char *out; int len; nad_print(nad, 0, &out, &len); _sx_debug(ZONE, "completed nad: %.*s", len, out); #endif /* check for errors */ if(NAD_ENS(nad, 0) >= 0 && NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_STREAMS) && strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_STREAMS, strlen(uri_STREAMS)) == 0 && NAD_ENAME_L(nad, 0) == 5 && strncmp(NAD_ENAME(nad, 0), "error", 5) == 0) { errstring = NULL; /* get text error description if available - XMPP 4.7.2 */ if((ns = nad_find_scoped_namespace(nad, uri_STREAM_ERR, NULL)) >= 0) if((elem = nad_find_elem(nad, 0, ns, "text", 1)) >= 0) if(NAD_CDATA_L(nad, elem) > 0) { errstring = (char *) malloc(sizeof(char) * (NAD_CDATA_L(nad, elem) + 1)); sprintf(errstring, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); } /* if not available, look for legacy error text as in <stream:error>description</stream:error> */ if (errstring == NULL && NAD_CDATA_L(nad, 0) > 0) { errstring = (char *) malloc(sizeof(char) * (NAD_CDATA_L(nad, 0) + 1)); sprintf(errstring, "%.*s", NAD_CDATA_L(nad, 0), NAD_CDATA(nad, 0)); } /* if not available, log the whole packet for debugging */ if (errstring == NULL) { const char *xml; int xlen; nad_print(nad, 0, &xml, &xlen); errstring = (char *) malloc(sizeof(char) * (xlen + 1)); sprintf(errstring, "%.*s", xlen, xml); } if(s->state < state_CLOSING) { _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", errstring); _sx_event(s, event_ERROR, (void *) &sxe); _sx_state(s, state_CLOSING); } free(errstring); nad_free(nad); break; } /* check for close */ if ((s->flags & SX_WEBSOCKET_WRAPPER) && NAD_ENS(nad, 0) >= 0 && NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_XFRAMING) && strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_XFRAMING, strlen(uri_XFRAMING)) == 0 && NAD_ENAME_L(nad, 0) == 5 && strncmp(NAD_ENAME(nad, 0), "close", 5) == 0) { _sx_debug(ZONE, "<close/> frame @ depth %d", s->depth); s->fail = 1; break; } /* run it by the plugins */ if(_sx_chain_nad_read(s, nad) == 0) return; /* now let the plugins process the completed nad */ plugin_error = 0; if(s->env != NULL) for(i = 0; i < s->env->nplugins; i++) if(s->env->plugins[i]->process != NULL) { int plugin_ret; plugin_ret = (s->env->plugins[i]->process)(s, s->env->plugins[i], nad); if(plugin_ret == 0) { plugin_error ++; break; } } /* hand it to the app */ if ((plugin_error == 0) && (s->state < state_CLOSING)) _sx_event(s, event_PACKET, (void *) nad); } /* something went wrong, bail */ if(s->fail) { _sx_close(s); return; } /* stream was closed */ if(s->depth < 0 && s->state < state_CLOSING) { /* close the stream if necessary */ if(s->state >= state_STREAM_SENT) { if (s->flags & SX_WEBSOCKET_WRAPPER) jqueue_push(s->wbufq, _sx_buffer_new("<close xmlns='" uri_XFRAMING "' />", sizeof(uri_XFRAMING) + 17, NULL, NULL), 0); else jqueue_push(s->wbufq, _sx_buffer_new("</stream:stream>", 16, NULL, NULL), 0); s->want_write = 1; } _sx_state(s, state_CLOSING); return; } }
int sx_can_write(sx_t s) { sx_buf_t out; int ret, written; assert((int) (s != NULL)); /* do we care? */ if(!s->want_write && s->state < state_CLOSING) return 0; /* no more thanks */ _sx_debug(ZONE, "%d ready for writing", s->tag); ret = _sx_get_pending_write(s); if (ret < 0) { /* fatal error */ _sx_debug(ZONE, "fatal error after attempt to write on fd %d", s->tag); /* permanent error so inform the app it can kill us */ sx_kill(s); return 0; } /* if there's nothing to write, then we're done */ if(s->wbufpending == NULL) { if(s->want_read) _sx_event(s, event_WANT_READ, NULL); return s->want_write; } out = s->wbufpending; s->wbufpending = NULL; /* get the callback to do the write */ _sx_debug(ZONE, "handing app %d bytes to write", out->len); written = _sx_event(s, event_WRITE, (void *) out); if(written < 0) { /* bail if something went wrong */ _sx_buffer_free(out); s->want_read = 0; s->want_write = 0; return 0; } else if(written < out->len) { /* if not fully written, this buffer is still pending */ out->len -= written; out->data += written; s->wbufpending = out; s->want_write ++; } else { /* notify */ if(out->notify != NULL) (out->notify)(s, out->notify_arg); /* done with this */ _sx_buffer_free(out); } /* if we've written everything, and we're closed, then inform the app it can kill us */ if(s->want_write == 0 && s->state == state_CLOSING) { _sx_state(s, state_CLOSED); _sx_event(s, event_CLOSED, NULL); return 0; } if(s->state == state_CLOSED) return 0; if(s->want_read) _sx_event(s, event_WANT_READ, NULL); return s->want_write; }
static int _sx_sasl_canon_user(sasl_conn_t *conn, void *ctx, const char *user, unsigned ulen, unsigned flags, const char *user_realm, const char *out_user, unsigned out_umax, unsigned *out_ulen) { char *buf; char principal[3072]; char out_buf[3072]; // node(1023) + '@'(1) + domain/realm(1023) + '@'(1) + krb domain(1023) + '\0'(1) _sx_sasl_data_t sd = (_sx_sasl_data_t)ctx; char user_null_term[1024]; if (ulen > (sizeof(user_null_term)-1)) { _sx_debug(ZONE, "Got a SASL argument \"user\" that exceeds our maximum length, rejecting"); return SASL_BADAUTH; } // make a NULL terminated copy for ourself memcpy(user_null_term, user, ulen); user_null_term[ulen] = '\0'; sasl_getprop(conn, SASL_MECHNAME, (const void **) &buf); if (strncmp(buf, "GSSAPI", 7) == 0) { // Reformat the user argument for odkerb_get_im_handle // (Remove the default realm from string if necessary) char adjusted_user[1024]; char *s = strdup(user_null_term); if (s) { char *c = strsep(&s, "@"); if (c) { strlcpy(adjusted_user, c, sizeof(adjusted_user)); c = strsep(&s, "@"); if (c) { // should be the default realm - ignore c = strsep(&s, "@"); if (c) { // should be a foreign realm that we want to check strlcat(adjusted_user, "@", sizeof(adjusted_user)); strlcat(adjusted_user, c, sizeof(adjusted_user)); } } else { _sx_debug(ZONE, "Notice: unexpected format of SASL \"user\" argument: %s", user_null_term); } } else { _sx_debug(ZONE, "Error getting SASL argument \"user\""); free(s); return SASL_BADAUTH; } free(s); } else { _sx_debug(ZONE, "Error copying SASL argument \"user\""); return SASL_BADAUTH; } snprintf(principal, sizeof(principal), "%s@%s", adjusted_user, user_realm); if (odkerb_get_im_handle(principal, sd->stream->req_to, "JABBER:", out_buf, ((out_umax > sizeof(out_buf)) ? sizeof(out_buf) : out_umax)) == 0) { strlcpy(out_user, out_buf, out_umax); *out_ulen = strlen(out_user); _sx_debug(ZONE, "Got IM handle: %s for user %s, realm %s", out_buf, user_null_term, user_realm); } else { return SASL_BADAUTH; } } else if (strncmp(buf, "ANONYMOUS", 10) == 0) { sd->ctx->cb(sx_sasl_cb_GEN_AUTHZID, NULL, (void **)&buf, sd->stream, sd->ctx->cbarg); strncpy(out_user, buf, out_umax); out_user[out_umax]='\0'; *out_ulen=strlen(out_user); } else { memcpy(out_user,user,ulen); *out_ulen = ulen; } return SASL_OK; }