int tls_stop(tls_t *tls) { int retries = 0; int error; int ret; while (1) { ++retries; ret = SSL_shutdown(tls->ssl); error = ret < 0 ? SSL_get_error(tls->ssl, ret) : 0; if (ret == 1 || !tls_is_recoverable(error) || retries >= TLS_SHUTDOWN_MAX_RETRIES) { break; } _tls_sock_wait(tls, error); } if (error == SSL_ERROR_SYSCALL && errno == 0) { /* * Handle special case when peer closes connection instead of * proper shutdown. */ error = 0; ret = 1; } _tls_set_error(tls, error); return ret <= 0 ? 0 : 1; }
int tls_start(tls_t *tls) { int error; int ret; long x509_res; /* Since we're non-blocking, loop the connect call until it succeeds or fails */ while (1) { ret = SSL_connect(tls->ssl); error = ret <= 0 ? SSL_get_error(tls->ssl, ret) : 0; if (ret == -1 && tls_is_recoverable(error)) { /* wait for something to happen on the sock before looping back */ _tls_sock_wait(tls, error); continue; } /* success or fatal error */ break; } x509_res = SSL_get_verify_result(tls->ssl); xmpp_debug(tls->ctx, "tls", "Certificate verification %s", x509_res == X509_V_OK ? "passed" : "FAILED"); _tls_set_error(tls, error); return ret <= 0 ? 0 : 1; }
static void _tls_set_error(tls_t *tls, int error) { if (error != 0 && !tls_is_recoverable(error)) { xmpp_debug(tls->ctx, "tls", "error=%d errno=%d", error, errno); _tls_log_error(tls->ctx); } tls->lasterror = error; }
int tls_write(tls_t *tls, const void * const buff, const size_t len) { SecBufferDesc sbdenc; SecBuffer sbenc[4]; const unsigned char *p = buff; int sent = 0, ret, remain = len; ret = tls_clear_pending_write(tls); if (ret <= 0) { return ret; } tls->sendbufferpos = 0; tls->sendbufferlen = 0; memset(&sbdenc, 0, sizeof(sbdenc)); sbdenc.ulVersion = SECBUFFER_VERSION; sbdenc.cBuffers = 4; sbdenc.pBuffers = sbenc; memset(&(sbenc[0]), 0, sizeof(sbenc[0])); sbenc[0].BufferType = SECBUFFER_STREAM_HEADER; memset(&(sbenc[1]), 0, sizeof(sbenc[1])); sbenc[1].BufferType = SECBUFFER_DATA; memset(&(sbenc[2]), 0, sizeof(sbenc[2])); sbenc[2].BufferType = SECBUFFER_STREAM_TRAILER; memset(&(sbenc[3]), 0, sizeof(sbenc[3])); sbenc[3].BufferType = SECBUFFER_EMPTY; sbenc[0].pvBuffer = tls->sendbuffer; sbenc[0].cbBuffer = tls->spcss.cbHeader; sbenc[1].pvBuffer = tls->sendbuffer + tls->spcss.cbHeader; while (remain > 0) { if (remain > tls->spcss.cbMaximumMessage) { sbenc[1].cbBuffer = tls->spcss.cbMaximumMessage; } else { sbenc[1].cbBuffer = remain; } sbenc[2].pvBuffer = (unsigned char *)sbenc[1].pvBuffer + sbenc[1].cbBuffer; sbenc[2].cbBuffer = tls->spcss.cbTrailer; memcpy(sbenc[1].pvBuffer, p, sbenc[1].cbBuffer); p += tls->spcss.cbMaximumMessage; tls->sendbufferlen = sbenc[0].cbBuffer + sbenc[1].cbBuffer + sbenc[2].cbBuffer; ret = tls->sft->EncryptMessage(&(tls->hctxt), 0, &sbdenc, 0); if (ret != SEC_E_OK) { tls->lasterror = ret; return -1; } tls->sendbufferpos = 0; ret = tls_clear_pending_write(tls); if (ret == -1 && !tls_is_recoverable(tls_error(tls))) { return -1; } if (remain > tls->spcss.cbMaximumMessage) { sent += tls->spcss.cbMaximumMessage; remain -= tls->spcss.cbMaximumMessage; } else { sent += remain; remain = 0; } if (ret == 0 || (ret == -1 && tls_is_recoverable(tls_error(tls)))) { return sent; } } return sent; }
int tls_read(tls_t *tls, void * const buff, const size_t len) { int bytes; /* first, if we've got some ready data, put that in the buffer */ if (tls->readybufferpos < tls->readybufferlen) { if (len < tls->readybufferlen - tls->readybufferpos) { bytes = len; } else { bytes = tls->readybufferlen - tls->readybufferpos; } memcpy(buff, tls->readybuffer + tls->readybufferpos, bytes); if (len < tls->readybufferlen - tls->readybufferpos) { tls->readybufferpos += bytes; return bytes; } else { unsigned char *newbuff = buff; int read; tls->readybufferpos += bytes; newbuff += bytes; read = tls_read(tls, newbuff, len - bytes); if (read == -1) { if (tls_is_recoverable(tls->lasterror)) { return bytes; } return -1; } return bytes + read; } } /* next, top up our recv buffer */ bytes = sock_read(tls->sock, tls->recvbuffer + tls->recvbufferpos, tls->recvbuffermaxlen - tls->recvbufferpos); if (bytes == 0) { tls->lasterror = WSAECONNRESET; return -1; } if (bytes == -1) { if (!tls_is_recoverable(sock_error())) { tls->lasterror = sock_error(); return -1; } } if (bytes > 0) { tls->recvbufferpos += bytes; } /* next, try to decrypt the recv buffer */ if (tls->recvbufferpos > 0) { SecBufferDesc sbddec; SecBuffer sbdec[4]; int ret; memset(&sbddec, 0, sizeof(sbddec)); sbddec.ulVersion = SECBUFFER_VERSION; sbddec.cBuffers = 4; sbddec.pBuffers = sbdec; memset(&(sbdec[0]), 0, sizeof(sbdec[0])); sbdec[0].BufferType = SECBUFFER_DATA; sbdec[0].pvBuffer = tls->recvbuffer; sbdec[0].cbBuffer = tls->recvbufferpos; memset(&(sbdec[1]), 0, sizeof(sbdec[1])); sbdec[1].BufferType = SECBUFFER_EMPTY; memset(&(sbdec[2]), 0, sizeof(sbdec[2])); sbdec[2].BufferType = SECBUFFER_EMPTY; memset(&(sbdec[3]), 0, sizeof(sbdec[3])); sbdec[3].BufferType = SECBUFFER_EMPTY; ret = tls->sft->DecryptMessage(&(tls->hctxt), &sbddec, 0, NULL); if (ret == SEC_E_OK) { memcpy(tls->readybuffer, sbdec[1].pvBuffer, sbdec[1].cbBuffer); tls->readybufferpos = 0; tls->readybufferlen = sbdec[1].cbBuffer; /* have we got some data left over? If so, copy it to the start * of the recv buffer */ if (sbdec[3].BufferType == SECBUFFER_EXTRA) { memcpy(tls->recvbuffer, sbdec[3].pvBuffer, sbdec[3].cbBuffer); tls->recvbufferpos = sbdec[3].cbBuffer; } else { tls->recvbufferpos = 0; } return tls_read(tls, buff, len); } else if (ret == SEC_E_INCOMPLETE_MESSAGE) { tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; return -1; } else if (ret == SEC_I_RENEGOTIATE) { ret = tls_start(tls); if (!ret) { return -1; } /* fake an incomplete message so we're called again */ tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; return -1; } /* something bad happened, so we bail */ tls->lasterror = ret; return -1; } tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; return -1; }
/** Run the event loop once. * This function will run send any data that has been queued by * xmpp_send and related functions and run through the Strophe even * loop a single time, and will not wait more than timeout * milliseconds for events. This is provided to support integration * with event loops outside the library, and if used, should be * called regularly to achieve low latency event handling. * * @param ctx a Strophe context object * @param timeout time to wait for events in milliseconds * * @ingroup EventLoop */ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) { xmpp_connlist_t *connitem; xmpp_conn_t *conn; fd_set rfds, wfds; sock_t max = 0; int ret; struct timeval tv; xmpp_send_queue_t *sq, *tsq; int towrite; char buf[4096]; uint64_t next; long usec; int tls_read_bytes = 0; if (ctx->loop_status == XMPP_LOOP_QUIT) return; ctx->loop_status = XMPP_LOOP_RUNNING; /* send queued data */ connitem = ctx->connlist; while (connitem) { conn = connitem->conn; if (conn->state != XMPP_STATE_CONNECTED) { connitem = connitem->next; continue; } /* if we're running tls, there may be some remaining data waiting to * be sent, so push that out */ if (conn->tls) { ret = tls_clear_pending_write(conn->tls); if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { /* an error occured */ xmpp_debug(ctx, "xmpp", "Send error occured, disconnecting."); conn->error = ECONNABORTED; conn_disconnect(conn); } } /* write all data from the send queue to the socket */ sq = conn->send_queue_head; while (sq) { towrite = sq->len - sq->written; if (conn->tls) { ret = tls_write(conn->tls, &sq->data[sq->written], towrite); if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { /* an error occured */ conn->error = tls_error(conn->tls); break; } else if (ret < towrite) { /* not all data could be sent now */ if (ret >= 0) sq->written += ret; break; } } else { ret = sock_write(conn->sock, &sq->data[sq->written], towrite); if (ret < 0 && !sock_is_recoverable(sock_error())) { /* an error occured */ conn->error = sock_error(); break; } else if (ret < towrite) { /* not all data could be sent now */ if (ret >= 0) sq->written += ret; break; } } /* all data for this queue item written, delete and move on */ xmpp_free(ctx, sq->data); tsq = sq; sq = sq->next; xmpp_free(ctx, tsq); /* pop the top item */ conn->send_queue_head = sq; /* if we've sent everything update the tail */ if (!sq) conn->send_queue_tail = NULL; } /* tear down connection on error */ if (conn->error) { /* FIXME: need to tear down send queues and random other things * maybe this should be abstracted */ xmpp_debug(ctx, "xmpp", "Send error occured, disconnecting."); conn->error = ECONNABORTED; conn_disconnect(conn); } connitem = connitem->next; } /* reset parsers if needed */ for (connitem = ctx->connlist; connitem; connitem = connitem->next) { if (connitem->conn->reset_parser) conn_parser_reset(connitem->conn); } /* fire any ready timed handlers, then make sure we don't wait past the time when timed handlers need to be called */ next = handler_fire_timed(ctx); usec = ((next < timeout) ? next : timeout) * 1000; tv.tv_sec = usec / 1000000; tv.tv_usec = usec % 1000000; FD_ZERO(&rfds); FD_ZERO(&wfds); /* find events to watch */ connitem = ctx->connlist; while (connitem) { conn = connitem->conn; switch (conn->state) { case XMPP_STATE_CONNECTING: /* connect has been called and we're waiting for it to complete */ /* connection will give us write or error events */ /* make sure the timeout hasn't expired */ if (time_elapsed(conn->timeout_stamp, time_stamp()) <= conn->connect_timeout) FD_SET(conn->sock, &wfds); else { conn->error = ETIMEDOUT; xmpp_info(ctx, "xmpp", "Connection attempt timed out."); conn_disconnect(conn); } break; case XMPP_STATE_CONNECTED: FD_SET(conn->sock, &rfds); break; case XMPP_STATE_DISCONNECTED: /* do nothing */ default: break; } /* Check if there is something in the SSL buffer. */ if (conn->tls) { tls_read_bytes += tls_pending(conn->tls); } if (conn->state != XMPP_STATE_DISCONNECTED && conn->sock > max) max = conn->sock; connitem = connitem->next; } /* check for events */ if (max > 0) ret = select(max + 1, &rfds, &wfds, NULL, &tv); else { if (timeout > 0) _sleep(timeout); return; } /* select errored */ if (ret < 0) { if (!sock_is_recoverable(sock_error())) xmpp_error(ctx, "xmpp", "event watcher internal error %d", sock_error()); return; } /* no events happened */ if (ret == 0 && tls_read_bytes == 0) return; /* process events */ connitem = ctx->connlist; while (connitem) { conn = connitem->conn; switch (conn->state) { case XMPP_STATE_CONNECTING: if (FD_ISSET(conn->sock, &wfds)) { /* connection complete */ /* check for error */ ret = sock_connect_error(conn->sock); if (ret != 0) { /* connection failed */ xmpp_debug(ctx, "xmpp", "connection failed, error %d", ret); conn_disconnect(conn); break; } conn->state = XMPP_STATE_CONNECTED; xmpp_debug(ctx, "xmpp", "connection successful"); if (conn->tls_legacy_ssl) { xmpp_debug(ctx, "xmpp", "using legacy SSL connection"); ret = conn_tls_start(conn); if (ret != 0) { conn_disconnect(conn); break; } } /* send stream init */ conn_open_stream(conn); } break; case XMPP_STATE_CONNECTED: if (FD_ISSET(conn->sock, &rfds) || (conn->tls && tls_pending(conn->tls))) { if (conn->tls) { ret = tls_read(conn->tls, buf, 4096); } else { ret = sock_read(conn->sock, buf, 4096); } if (ret > 0) { ret = parser_feed(conn->parser, buf, ret); if (!ret) { /* parse error, we need to shut down */ /* FIXME */ xmpp_debug(ctx, "xmpp", "parse error, disconnecting"); conn_disconnect(conn); } } else { if (conn->tls) { if (!tls_is_recoverable(tls_error(conn->tls))) { xmpp_debug(ctx, "xmpp", "Unrecoverable TLS error, %d.", tls_error(conn->tls)); conn->error = tls_error(conn->tls); conn_disconnect(conn); } } else { /* return of 0 means socket closed by server */ xmpp_debug(ctx, "xmpp", "Socket closed by remote host."); conn->error = ECONNRESET; conn_disconnect(conn); } } } break; case XMPP_STATE_DISCONNECTED: /* do nothing */ default: break; } connitem = connitem->next; } /* fire any ready handlers */ handler_fire_timed(ctx); }