/* * LibNoPoll: A websocket library * Copyright (C) 2013 Advanced Software Production Line, S.L. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * * You may find a copy of the license under this software is released * at COPYING file. This is LGPL software: you are welcome to develop * proprietary applications using this library without any royalty or * fee but returning back any change, improvement or addition in the * form of source code, project image, documentation patches, etc. * * For commercial support on build Websocket enabled solutions * contact us: * * Postal address: * Advanced Software Production Line, S.L. * Edificio Alius A, Oficina 102, * C/ Antonio Suarez Nº 10, * Alcalá de Henares 28802 Madrid * Spain * * Email address: * info@aspl.es - http://www.aspl.es/nopoll */ /** * \defgroup nopoll_conn noPoll Connection: functions required to create WebSocket client connections. */ /** * \addtogroup nopoll_conn * @{ */ #include #include /** * @brief Allows to enable/disable non-blocking/blocking behavior on * the provided socket. * * @param socket The socket to be configured. * * @param enable nopoll_true to enable blocking I/O, otherwise use * nopoll_false to enable non blocking I/O. * * @return nopoll_true if the operation was properly done, otherwise * nopoll_false is returned. */ nopoll_bool nopoll_conn_set_sock_block (NOPOLL_SOCKET socket, nopoll_bool enable) { #if defined(NOPOLL_OS_UNIX) int flags; #endif if (enable) { /* enable blocking mode */ #if defined(NOPOLL_OS_WIN32) if (!nopoll_win32_blocking_enable (socket)) { return nopoll_false; } #else if ((flags = fcntl (socket, F_GETFL, 0)) < -1) { return nopoll_false; } /* end if */ flags &= ~O_NONBLOCK; if (fcntl (socket, F_SETFL, flags) < -1) { return nopoll_false; } /* end if */ #endif } else { /* enable nonblocking mode */ #if defined(NOPOLL_OS_WIN32) /* win32 case */ if (!nopoll_win32_nonblocking_enable (socket)) { return nopoll_false; } #else /* unix case */ if ((flags = fcntl (socket, F_GETFL, 0)) < -1) { return nopoll_false; } flags |= O_NONBLOCK; if (fcntl (socket, F_SETFL, flags) < -1) { return nopoll_false; } #endif } /* end if */ return nopoll_true; } /** * @brief Allows to get current timeout set for \ref noPollConn * connect operation. * * See also \ref nopoll_conn_connect_timeout. * * @return Current timeout configured. Returned value is measured in * microseconds (1 second = 1000000 microseconds). If a null value is * received, 0 is return and no timeout is implemented. */ long nopoll_conn_get_connect_timeout (noPollCtx * ctx) { /* check context recevied */ if (ctx == NULL) { /* get the the default connect */ return (0); } /* end if */ /* return current value */ return ctx->conn_connect_std_timeout; } /** * @brief Allows to configure nopoll connect timeout. * * This function allows to set the TCP connect timeout used by \ref * nopoll_conn_new. * * @param ctx The context where the operation will be performed. * * @param microseconds_to_wait Timeout value to be used. The value * provided is measured in microseconds. Use 0 to restore the connect * timeout to the default value. */ void nopoll_conn_connect_timeout (noPollCtx * ctx, long microseconds_to_wait) { /* check reference received */ if (ctx == NULL) return; /* configure new value */ ctx->conn_connect_std_timeout = microseconds_to_wait; return; } /** * @brief Allows to configure tcp no delay flag (enable/disable Nagle * algorithm). * * @param socket The socket to be configured. * * @param enable The value to be configured, nopoll_true to enable tcp no * delay. * * @return nopoll_true if the operation is completed. */ nopoll_bool nopoll_conn_set_sock_tcp_nodelay (NOPOLL_SOCKET socket, nopoll_bool enable) { /* local variables */ int result; #if defined(NOPOLL_OS_WIN32) BOOL flag = enable ? TRUE : FALSE; result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(BOOL)); #else int flag = enable; result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof (flag)); #endif if (result == NOPOLL_INVALID_SOCKET) { return nopoll_false; } /* properly configured */ return nopoll_true; } /* end */ /** * @internal Allows to create a plain socket connection against the * host and port provided. * * @param ctx The context where the connection happens. * * @param host The host server to connect to. * * @param port The port server to connect to. * * @return A connected socket or -1 if it fails. */ NOPOLL_SOCKET nopoll_conn_sock_connect (noPollCtx * ctx, const char * host, const char * port) { struct hostent * hostent; struct sockaddr_in saddr; NOPOLL_SOCKET session; /* resolve hosting name */ hostent = gethostbyname (host); if (hostent == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "unable to resolve host name %s", host); return -1; } /* end if */ /* create the socket and check if it */ session = socket (AF_INET, SOCK_STREAM, 0); if (session == NOPOLL_INVALID_SOCKET) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "unable to create socket"); return -1; } /* end if */ /* disable nagle */ nopoll_conn_set_sock_tcp_nodelay (session, nopoll_true); /* prepare socket configuration to operate using TCP/IP * socket */ memset(&saddr, 0, sizeof(saddr)); saddr.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; saddr.sin_family = AF_INET; saddr.sin_port = htons((uint16_t) strtol (port, NULL, 10)); /* set non blocking status */ // nopoll_conn_set_sock_block (session, nopoll_false); /* do a tcp connect */ if (connect (session, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { if(errno != NOPOLL_EINPROGRESS && errno != NOPOLL_EWOULDBLOCK && errno != NOPOLL_ENOTCONN) { shutdown (session, SHUT_RDWR); nopoll_close_socket (session); nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "unable to connect to remote host %s:%s errno=%d", host, port, errno); return -1; } /* end if */ } /* end if */ /* return socket created */ return session; } /** * @internal Function that builds the client init greetings that will * be send to the server according to registered implementation. */ char * __nopoll_conn_get_client_init (noPollConn * conn, noPollConnOpts * opts) { /* build sec-websocket-key */ char key[50]; int key_size = 50; char nonce[17]; /* get the nonce */ if (! nopoll_nonce (nonce, 16)) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Failed to get nonce, unable to produce Sec-WebSocket-Key."); return NULL; } /* end if */ /* now base 64 */ if (! nopoll_base64_encode (nonce, 16, key, &key_size)) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to base 64 encode characters for Sec-WebSocket-Key"); return NULL; } nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Created Sec-WebSocket-Key nonce: %s", key); /* create accept and store */ conn->handshake = nopoll_new (noPollHandShake, 1); conn->handshake->expected_accept = nopoll_strdup (key); /* send initial handshake |cookie |prot | */ return nopoll_strdup_printf ("GET %s HTTP/1.1\r\nHost: %s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %s\r\nOrigin: %s\r\n%s%s%s%s%s%s%s%sSec-WebSocket-Version: %d\r\n\r\n", conn->get_url, conn->host_name, /* sec-websocket-key */ key, /* Origin */ conn->origin, /* Cookie */ (opts && opts->cookie) ? "Cookie" : "", (opts && opts->cookie) ? ": " : "", (opts && opts->cookie) ? opts->cookie : "", (opts && opts->cookie) ? "\r\n" : "", /* protocol part */ conn->protocols ? "Sec-WebSocket-Protocol" : "", conn->protocols ? ": " : "", conn->protocols ? conn->protocols : "", conn->protocols ? "\r\n" : "", conn->ctx->protocol_version); } /** * @internal Function that dumps all errors found on current ssl context. */ int nopoll_conn_log_ssl (noPollConn * conn) { #if defined(SHOW_DEBUG_LOG) noPollCtx * ctx = conn->ctx; #endif char log_buffer [512]; unsigned long err; int error_position; int aux_position; while ((err = ERR_get_error()) != 0) { ERR_error_string_n (err, log_buffer, sizeof (log_buffer)); nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "tls stack: %s (find reason(code) at openssl/ssl.h)", log_buffer); /* find error code position */ error_position = 0; while (log_buffer[error_position] != ':' && log_buffer[error_position] != 0 && error_position < 511) error_position++; error_position++; aux_position = error_position; while (log_buffer[aux_position] != 0) { if (log_buffer[aux_position] == ':') { log_buffer[aux_position] = 0; break; } aux_position++; } /* end while */ nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, " details, run: openssl errstr %s", log_buffer + error_position); } recv (conn->session, log_buffer, 1, MSG_PEEK); nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, " noPoll id=%d, socket: %d (after testing errno: %d)", conn->id, conn->session, errno); return (0); } int __nopoll_conn_tls_handle_error (noPollConn * conn, int res, const char * label, nopoll_bool * needs_retry) { int ssl_err; (*needs_retry) = nopoll_false; /* get error returned */ ssl_err = SSL_get_error (conn->ssl, res); switch (ssl_err) { case SSL_ERROR_NONE: /* no error, return the number of bytes read */ /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "%s, ssl_err=%d, perfect, no error reported, bytes read=%d", label, ssl_err, res); */ return res; case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_X509_LOOKUP: nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "%s, ssl_err=%d returned that isn't ready to read/write: you should retry", label, ssl_err); (*needs_retry) = nopoll_true; return -2; case SSL_ERROR_SYSCALL: if(res < 0) { /* not EOF */ if(errno == NOPOLL_EINTR) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "%s interrupted by a signal: retrying", label); /* report to retry */ return -2; } nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "SSL_read (SSL_ERROR_SYSCALL)"); return -1; } nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "SSL socket closed on %s (res=%d, ssl_err=%d, errno=%d)", label, res, ssl_err, errno); nopoll_conn_log_ssl (conn); return res; case SSL_ERROR_ZERO_RETURN: /* close_notify received */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "SSL closed on %s", label); return res; case SSL_ERROR_SSL: nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "%s function error (received SSL_ERROR_SSL) (res=%d, ssl_err=%d, errno=%d)", label, res, ssl_err, errno); nopoll_conn_log_ssl (conn); return -1; default: /* nothing to handle */ break; } nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "%s/SSL_get_error returned %d", label, res); return -1; } /** * @internal Default connection receive until handshake is complete. */ int nopoll_conn_tls_receive (noPollConn * conn, char * buffer, int buffer_size) { int res; nopoll_bool needs_retry; int tries = 0; /* call to read content */ while (tries < 50) { res = SSL_read (conn->ssl, buffer, buffer_size); /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "SSL: received %d bytes..", res); */ /* call to handle error */ res = __nopoll_conn_tls_handle_error (conn, res, "SSL_read", &needs_retry); if (! needs_retry) break; /* next operation */ tries++; } /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " SSL: after procesing error %d bytes..", res); */ return res; } /** * @internal Default connection send until handshake is complete. */ int nopoll_conn_tls_send (noPollConn * conn, char * buffer, int buffer_size) { int res; nopoll_bool needs_retry; int tries = 0; /* call to read content */ while (tries < 50) { res = SSL_write (conn->ssl, buffer, buffer_size); nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "SSL: sent %d bytes (requested: %d)..", res, buffer_size); /* call to handle error */ res = __nopoll_conn_tls_handle_error (conn, res, "SSL_write", &needs_retry); /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " SSL: after processing error, sent %d bytes (requested: %d)..", res, buffer_size); */ if (! needs_retry) break; /* next operation */ nopoll_sleep (tries * 10000); tries++; } return res; } SSL_CTX * __nopoll_conn_get_ssl_context (noPollCtx * ctx, noPollConn * conn, noPollConnOpts * opts, nopoll_bool is_client) { /* call to user defined function if the context creator is defined */ if (ctx && ctx->context_creator) return ctx->context_creator (ctx, conn, opts, is_client, ctx->context_creator_data); if (opts == NULL) { /* printf ("**** REPORTING TLSv1 ****\n"); */ return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ()); } /* end if */ switch (opts->ssl_protocol) { case NOPOLL_METHOD_TLSV1: return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ()); //#if defined(TLSv1_1_client_method) case NOPOLL_METHOD_TLSV1_1: /* printf ("**** REPORTING TLSv1.1 ****\n"); */ return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_1_client_method () : TLSv1_1_server_method ()); //#endif case NOPOLL_METHOD_SSLV3: /* printf ("**** REPORTING SSLv3 ****\n"); */ return (SSL_CTX *)SSL_CTX_new (is_client ? SSLv3_client_method () : SSLv3_server_method ()); case NOPOLL_METHOD_SSLV23: /* printf ("**** REPORTING SSLv23 ****\n"); */ return (SSL_CTX *)SSL_CTX_new (is_client ? SSLv23_client_method () : SSLv23_server_method ()); } /* reached this point, report default TLSv1 method */ return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ()); } noPollCtx * __nopoll_conn_ssl_ctx_debug = NULL; int __nopoll_conn_ssl_verify_callback (int ok, X509_STORE_CTX * store) { char data[256]; X509 * cert; #if defined(SHOW_DEBUG_LOG) int depth; int err; #endif if (! ok) { cert = (X509 *)X509_STORE_CTX_get_current_cert (store); #if defined(SHOW_DEBUG_LOG) depth = X509_STORE_CTX_get_error_depth (store); err = X509_STORE_CTX_get_error (store); #endif nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: error=%d at depth: %d", err, depth); X509_NAME_oneline (X509_get_issuer_name (cert), data, 256); nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: issuer: %s", data); X509_NAME_oneline (X509_get_subject_name (cert), data, 256); nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: subject: %s", data); nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: error %d:%s", err, X509_verify_cert_error_string (err)); } return ok; /* return same value */ } nopoll_bool __nopoll_conn_set_ssl_client_options (noPollCtx * ctx, noPollConn * conn, noPollConnOpts * options) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking to establish SSL options (%p)", options); if (options && options->ca_certificate) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting CA certificate: %s", options->ca_certificate); if (SSL_CTX_load_verify_locations (conn->ssl_ctx, options->ca_certificate, NULL) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure CA certificate (%s), SSL_CTX_load_verify_locations () failed", options->ca_certificate); return nopoll_false; } /* end if */ } /* end if */ /* enable default verification paths */ if (SSL_CTX_set_default_verify_paths (conn->ssl_ctx) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to configure default verification paths, SSL_CTX_set_default_verify_paths () failed"); return nopoll_false; } /* end if */ if (options && options->chain_certificate) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting chain certificate: %s", options->chain_certificate); if (SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, options->chain_certificate) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure chain certificate (%s), SSL_CTX_use_certificate_chain_file () failed", options->chain_certificate); return nopoll_false; } /* end if */ } /* end if */ if (options && options->certificate) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting certificate: %s", options->certificate); if (SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, options->certificate) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure client certificate (%s), SSL_CTX_use_certificate_file () failed", options->certificate); return nopoll_false; } /* end if */ } /* end if */ if (options && options->private_key) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting private key: %s", options->private_key); if (SSL_CTX_use_PrivateKey_file (conn->ssl_ctx, options->private_key, SSL_FILETYPE_PEM) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure private key (%s), SSL_CTX_use_PrivateKey_file () failed", options->private_key); return nopoll_false; } /* end if */ } /* end if */ if (options && options->private_key && options->certificate) { if (!SSL_CTX_check_private_key (conn->ssl_ctx)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Certificate and private key do not matches, verification fails, SSL_CTX_check_private_key ()"); return nopoll_false; } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Certificate (%s) and private key (%s) matches", options->certificate, options->private_key); } /* end if */ /* if no option and it is not disabled */ if (options == NULL || ! options->disable_ssl_verify) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Enabling certificate peer verification"); /** really, really ugly hack to let * __nopoll_conn_ssl_verify_callback to be able to get * access to the context required to drop some logs */ __nopoll_conn_ssl_ctx_debug = ctx; SSL_CTX_set_verify (conn->ssl_ctx, SSL_VERIFY_PEER, __nopoll_conn_ssl_verify_callback); SSL_CTX_set_verify_depth (conn->ssl_ctx, 10); } /* end if */ return nopoll_true; } /** * @internal Internal implementation used to do a connect. */ noPollConn * __nopoll_conn_new_common (noPollCtx * ctx, noPollConnOpts * options, nopoll_bool enable_tls, const char * host_ip, const char * host_port, const char * host_name, const char * get_url, const char * protocols, const char * origin) { noPollConn * conn; NOPOLL_SOCKET session; char * content; int size; int ssl_error; X509 * server_cert; int iterator; long remaining_timeout; if (! ctx || ! host_ip) { /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return NULL; } /* end if */ /* set default connection port */ if (host_port == NULL) host_port = "80"; /* create socket connection in a non block manner */ session = nopoll_conn_sock_connect (ctx, host_ip, host_port); if (session == NOPOLL_INVALID_SOCKET) { /* release connection options */ __nopoll_conn_opts_release_if_needed (options); nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to connect to remote host %s:%s", host_ip, host_port); return NULL; } /* end if */ /* create the connection */ conn = nopoll_new (noPollConn, 1); if (conn == NULL) { /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return NULL; } /* end if */ conn->refs = 1; /* create mutex */ conn->ref_mutex = nopoll_mutex_create (); /* register connection into context */ if (! nopoll_ctx_register_conn (ctx, conn)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to register connection into the context, unable to create connection"); nopoll_free (conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return NULL; } nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Created noPoll conn-id=%d (ptr: %p, context: %p, socket: %d)", conn->id, conn, ctx, session); /* configure context */ conn->ctx = ctx; conn->session = session; conn->role = NOPOLL_ROLE_CLIENT; /* record host and port */ conn->host = nopoll_strdup (host_ip); conn->port = nopoll_strdup (host_port); /* configure default handlers */ conn->receive = nopoll_conn_default_receive; conn->sends = nopoll_conn_default_send; /* build host name */ if (host_name == NULL) conn->host_name = nopoll_strdup (host_ip); else conn->host_name = nopoll_strdup (host_name); /* build origin */ if (origin == NULL) conn->origin = nopoll_strdup_printf ("http://%s", conn->host_name); else conn->origin = nopoll_strdup (origin); /* get url */ if (get_url == NULL) conn->get_url = nopoll_strdup ("/"); else conn->get_url = nopoll_strdup (get_url); /* protocols */ if (protocols != NULL) conn->protocols = nopoll_strdup (protocols); /* get client init payload */ content = __nopoll_conn_get_client_init (conn, options); if (content == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to build client init message, unable to connect"); nopoll_conn_shutdown (conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return NULL; } /* end if */ /* check for TLS support */ if (enable_tls) { /* found TLS connection request, enable it */ conn->ssl_ctx = __nopoll_conn_get_ssl_context (ctx, conn, options, nopoll_true); SSL_CTX_set_default_read_buffer_len(conn->ssl_ctx, 4096); /* check for client side SSL configuration */ if (! __nopoll_conn_set_ssl_client_options (ctx, conn, options)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to configure additional SSL options, unable to continue", conn->ssl_ctx, conn->ssl); goto fail_ssl_connection; } /* end if */ /* create context and check for result */ conn->ssl = (SSL*)SSL_new (conn->ssl_ctx); if (conn->ssl_ctx == NULL || conn->ssl == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to create SSL context internal references are null (conn->ssl_ctx=%p, conn->ssl=%p)", conn->ssl_ctx, conn->ssl); fail_ssl_connection: nopoll_free (content); nopoll_conn_shutdown (conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return conn; } /* end if */ /* set socket */ SSL_set_fd (conn->ssl, conn->session); /* do the initial connect connect */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "connecting to remote TLS site"); iterator = 0; while (SSL_connect (conn->ssl) <= 0) { /* get ssl error */ ssl_error = SSL_get_error (conn->ssl, -1); switch (ssl_error) { case SSL_ERROR_WANT_READ: nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because read wanted, conn-id=%d (%p, session: %d), errno=%d", conn->id, conn, conn->session, errno); break; case SSL_ERROR_WANT_WRITE: nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because write wanted, conn-id=%d (%p)", conn->id, conn); break; case SSL_ERROR_SYSCALL: nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "syscall error while doing TLS handshake, ssl error (code:%d), conn-id: %d (%p), errno: %d, session: %d", ssl_error, conn->id, conn, errno, conn->session); nopoll_conn_log_ssl (conn); nopoll_conn_shutdown (conn); nopoll_free (content); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return conn; default: nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "there was an error with the TLS negotiation, ssl error (code:%d) : %s", ssl_error, ERR_error_string (ssl_error, NULL)); nopoll_conn_log_ssl (conn); nopoll_conn_shutdown (conn); nopoll_free (content); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return conn; } /* end switch */ /* try and limit max reconnect allowed */ iterator++; if (iterator > 100) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Max retry calls=%d to SSL_connect reached, shutting down connection id=%d, errno=%d", iterator, conn->id, errno); nopoll_free (content); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return conn; } /* end if */ /* wait a bit before retry */ nopoll_sleep (10000); } /* end while */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Client TLS handshake finished, configuring I/O handlers"); /* check remote certificate (if it is present) */ server_cert = (X509*)SSL_get_peer_certificate (conn->ssl); if (server_cert == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "server side didn't set a certificate for this session, these are bad news"); /* release connection options */ nopoll_free (content); __nopoll_conn_opts_release_if_needed (options); return conn; } /* call to check post ssl checks after SSL finalization */ if (conn->ctx && conn->ctx->post_ssl_check) { if (! conn->ctx->post_ssl_check (conn->ctx, conn, conn->ssl_ctx, conn->ssl, conn->ctx->post_ssl_check_data)) { /* TLS post check failed */ nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "TLS/SSL post check function failed, dropping connection"); nopoll_conn_shutdown (conn); return NULL; } /* end if */ } /* end if */ /* configure default handlers */ conn->receive = nopoll_conn_tls_receive; conn->sends = nopoll_conn_tls_send; nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "TLS I/O handlers configured"); conn->tls_on = nopoll_true; } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Sending websocket client init: %s", content); size = strlen (content); /* call to send content */ remaining_timeout = ctx->conn_connect_std_timeout; while (remaining_timeout > 0) { if (size != conn->sends (conn, content, size)) { /* for some reason, under FreeBSD, a ENOTCONN is reported when they should be returning EINPROGRESS and/or EWOULDBLOCK */ if (errno == NOPOLL_EWOULDBLOCK || errno == NOPOLL_EINPROGRESS || errno == NOPOLL_ENOTCONN) { /* nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Connection in progress (errno=%d), session: %d", errno, session); */ nopoll_sleep (10000); remaining_timeout -= 10000; continue; } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to send websocket init message, error code was: %d (2), closing session", errno); nopoll_conn_shutdown (conn); } /* end if */ break; } nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Web socket initial client handshake sent"); /* release content */ nopoll_free (content); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); /* return connection created */ return conn; } /** * @brief Creates a new Websocket connection to the provided * destination, physically located at host_ip and host_port. * * @param ctx The noPoll context to which this new connection will be associated. * * @param host_ip The websocket server address to connect to. * * @param host_port The websocket server port to connect to. If NULL * is provided, port 80 is used. * * @param host_name This is the Host: header value that will be * sent. This header is used by the websocket server to activate the * right virtual host configuration. If null is provided, Host: will * use host_ip value. * * @param get_url As part of the websocket handshake, an url is passed * to the remote server inside a GET method. This parameter allows to * configure this. If NULL is provided, then / will be used. * * @param origin Websocket origin to be notified to the server. * * @param protocols Optional protocols requested to be activated for * this connection (an string of list of strings separated by a white * space). If the server accepts the connection you can use \ref * nopoll_conn_get_accepted_protocol to get the protocol accepted by * the server. * * @return A reference to the connection created or NULL if it * fails. Keep in mind the connection reported may not be connected at * the time is returned by this function. You can use \ref * nopoll_conn_is_ready and \ref nopoll_conn_is_ok to ensure it can be * used. There is also a helper function (NOTE it is blocking) that * can help you implement a very simple wait until ready operation: * \ref nopoll_conn_wait_until_connection_ready (however, it is not * recommended for any serious, non-command line programming). */ noPollConn * nopoll_conn_new (noPollCtx * ctx, const char * host_ip, const char * host_port, const char * host_name, const char * get_url, const char * protocols, const char * origin) { /* call common implementation */ return __nopoll_conn_new_common (ctx, NULL, nopoll_false, host_ip, host_port, host_name, get_url, protocols, origin); } /** * @brief Creates a new Websocket connection to the provided * destination, physically located at host_ip and host_port and * allowing to provide a noPollConnOpts object. * * @param ctx The noPoll context to which this new connection will be associated. * * @param opts The connection options to use during the connection and * the usage of this connection. * * @param host_ip The websocket server address to connect to. * * @param host_port The websocket server port to connect to. If NULL * is provided, port 80 is used. * * @param host_name This is the Host: header value that will be * sent. This header is used by the websocket server to activate the * right virtual host configuration. If null is provided, Host: will * use host_ip value. * * @param get_url As part of the websocket handshake, an url is passed * to the remote server inside a GET method. This parameter allows to * configure this. If NULL is provided, then / will be used. * * @param origin Websocket origin to be notified to the server. * * @param protocols Optional protocols requested to be activated for * this connection (an string of list of strings separated by a white * space). If the server accepts the connection you can use \ref * nopoll_conn_get_accepted_protocol to get the protocol accepted by * the server. * * @return A reference to the connection created or NULL if it * fails. Keep in mind the connection reported may not be connected at * the time is returned by this function. You can use \ref * nopoll_conn_is_ready and \ref nopoll_conn_is_ok to ensure it can be * used. There is also a helper function (NOTE it is blocking) that * can help you implement a very simple wait until ready operation: * \ref nopoll_conn_wait_until_connection_ready (however, it is not * recommended for any serious, non-command line programming). */ noPollConn * nopoll_conn_new_opts (noPollCtx * ctx, noPollConnOpts * opts, const char * host_ip, const char * host_port, const char * host_name, const char * get_url, const char * protocols, const char * origin) { /* call common implementation */ return __nopoll_conn_new_common (ctx, opts, nopoll_false, host_ip, host_port, host_name, get_url, protocols, origin); } nopoll_bool __nopoll_tls_was_init = nopoll_false; /** * @brief Allows to create a client WebSocket connection over TLS. * * The function works like nopoll_tls_conn_new with the same semantics * but providing a way to create a WebSocket session under the * supervision of TLS. * * @param ctx The context where the operation will take place. * * @param options Optional configuration object. See \ref nopoll_conn_opts_new and \ref nopoll_conn_opts_set_ssl_protocol (for example). * * @param host_ip The websocket server address to connect to. * * @param host_port The websocket server port to connect to. If NULL * is provided, port 443 is used. * * @param host_name This is the Host: header value that will be * sent. This header is used by the websocket server to activate the * right virtual host configuration. If null is provided, Host: will * use host_ip value. * * @param get_url As part of the websocket handshake, an url is passed * to the remote server inside a GET method. This parameter allows to * configure this. If NULL is provided, then / will be used. * * @param origin Websocket origin to be notified to the server. * * @param protocols Optional protocols requested to be activated for * this connection (an string of list of strings separated by a white * space). If the server accepts the connection you can use \ref * nopoll_conn_get_accepted_protocol to get the protocol accepted by * the server. * * @return A reference to the connection created or NULL if it * fails. Keep in mind the connection reported may not be connected at * the time is returned by this function. You can use \ref * nopoll_conn_is_ready and \ref nopoll_conn_is_ok to ensure it can be * used. There is also a helper function (NOTE it is blocking) that * can help you implement a very simple wait until ready operation: * \ref nopoll_conn_wait_until_connection_ready (however, it is not * recommended for any serious, non-command line programming). * */ noPollConn * nopoll_conn_tls_new (noPollCtx * ctx, noPollConnOpts * options, const char * host_ip, const char * host_port, const char * host_name, const char * get_url, const char * protocols, const char * origin) { /* init ssl ciphers and engines */ if (! __nopoll_tls_was_init) { __nopoll_tls_was_init = nopoll_true; SSL_library_init (); } /* end if */ /* call common implementation */ return __nopoll_conn_new_common (ctx, options, nopoll_true, host_ip, host_port, host_name, get_url, protocols, origin); } /** * @brief Allows to acquire a reference to the provided connection. * * @param conn The conection to be referenced * * @return nopoll_true In the case the function acquired a reference * otherwise nopoll_false is returned. */ nopoll_bool nopoll_conn_ref (noPollConn * conn) { if (conn == NULL) return nopoll_false; /* lock the mutex */ nopoll_mutex_lock (conn->ref_mutex); if (conn->refs <= 0) { /* unlock the mutex */ nopoll_mutex_unlock (conn->ref_mutex); return nopoll_false; } conn->refs++; /* release here the mutex */ nopoll_mutex_unlock (conn->ref_mutex); return nopoll_true; } /** * @brief Allows to get current reference counting state for the * provided connection. * * @param conn The connection queried for its reference counting. * * @return The reference counting or -1 if it fails. */ int nopoll_conn_ref_count (noPollConn * conn) { if (! conn) return -1; return conn->refs; } /** * @brief Allows to check if the provided connection is in connected * state (just to the connection). This is different to be ready to send and receive content * because the session needs to be first established. * * You can use \ref nopoll_conn_is_ready to ensure the connection is * ready to be used (read or write operation can be done because * handshake has finished). * * For example, you might connect to a raw socket server and * nopoll_conn_is_ok will report that everything is ok because the * socket is indeed connected but because you are connecting to a * non-websocket server, it will not work because the WebSocket * session establishment didn't take place and hence * nopoll_conn_is_ready will fail. * * Please, see the following link for a complete example that connects * and ensure the connection is ready (for a client): http://www.aspl.es/nopoll/html/nopoll_core_library_manual.html#creating_basic_web_socket_client * * @param conn The websocket connection to be checked. * * @return nopoll_true in the case the connection is working otherwise * nopoll_false is returned. */ nopoll_bool nopoll_conn_is_ok (noPollConn * conn) { if (conn == NULL) return nopoll_false; /* return current socket status */ return conn->session != NOPOLL_INVALID_SOCKET; } /** * @brief Allows to check if the connection is ready to be used * (handshake completed). * * @param conn The connection to be checked. * * @return nopoll_true in the case the handshake was completed * otherwise nopoll_false is returned. The function also returns * nopoll_false in case \ref nopoll_conn_is_ok is failing. */ nopoll_bool nopoll_conn_is_ready (noPollConn * conn) { if (conn == NULL) return nopoll_false; if (conn->session == NOPOLL_INVALID_SOCKET) return nopoll_false; if (! conn->handshake_ok) { /* acquire here handshake mutex */ nopoll_mutex_lock (conn->ref_mutex); /* complete handshake */ nopoll_conn_complete_handshake (conn); /* release here handshake mutex */ nopoll_mutex_unlock (conn->ref_mutex); } return conn->handshake_ok; } /** * @brief Allows to check if the provided connection is working under a TLS session. * * @param conn The connection where the TLS is being queired to be enabled. * * @return nopoll_true in the case TLS is enabled, otherwise* nopoll_false is returned. Note the function also returns* nopoll_false when the reference received is NULL. */ nopoll_bool nopoll_conn_is_tls_on (noPollConn * conn) { if (! conn) return nopoll_false; /* return TLS on */ return conn->tls_on; } /** * @brief Allows to get the socket associated to this nopoll * connection. * * @return The socket reference or -1 if it fails. */ NOPOLL_SOCKET nopoll_conn_socket (noPollConn * conn) { if (conn == NULL) return -1; return conn->session; } /** * @brief Allows to set up the socket reference to be used by this * noPollConn. * * @param conn The connection to setup with a new socket. * * @param _socket The socket that will be configured. */ void nopoll_conn_set_socket (noPollConn * conn, NOPOLL_SOCKET _socket) { if (conn == NULL) return; conn->session = _socket; return; } /** * @brief Allows to get the connection id from the provided * connection. * * @param conn The connection from where the unique identifier will be * returned. * * @return The identifier or -1 if it fails. */ int nopoll_conn_get_id (noPollConn * conn) { if (conn == NULL) return -1; return conn->id; } /** * @brief Allows to get the noPollCtx context object associated to the * connection (or where the connection is working). * * @param conn The connection that is requested to return its context. * * @return A reference to the context or NULL if it fails. */ noPollCtx * nopoll_conn_ctx (noPollConn * conn) { if (conn == NULL) return NULL; return conn->ctx; } /** * @brief Allows to get the connection role. * * @return The connection role, see \ref noPollRole for details. */ noPollRole nopoll_conn_role (noPollConn * conn) { if (conn == NULL) return NOPOLL_ROLE_UNKNOWN; return conn->role; } /** * @brief Returns the host location this connection connects to or it is * listening (according to the connection role \ref noPollRole). * * If you are looking for a way to get the Host: header value received * for this connection during the handshake, use: \ref * nopoll_conn_get_host_header. * * @param conn The connection to check for the host value. * * @return The host location value or NULL if it fails. */ const char * nopoll_conn_host (noPollConn * conn) { if (conn == NULL) return NULL; return conn->host; } /** * @brief Allows to get the connection Origin header content received. * * @param conn The websocket connection where the operation takes place. * * @return The Origin value received during the handshake for this * connection or NULL if it fails to get this value. */ const char * nopoll_conn_get_origin (noPollConn * conn) { if (conn == NULL || conn->handshake == NULL) return NULL; return conn->origin; } /* end if */ /** * @brief Allows to get the Host: header value that was received for * this connection during the handshake. * * @param conn The websocket connection where the operation takes place. * * @return The Host value received during the handshake for this * connection or NULL if it fails to get this value (or it wasn't * defined). */ const char * nopoll_conn_get_host_header (noPollConn * conn) { if (conn == NULL || conn->host_name == NULL) return NULL; return conn->host_name; } /** * @brief Allows to get cookie header content received during * handshake (if received). * * @param conn The websocket connection where the operation takes place. * * @return A reference to the cookie value or NULL if nothing is * configured or if failed to get it. */ const char * nopoll_conn_get_cookie (noPollConn * conn) { if (conn == NULL || conn->handshake == NULL) return NULL; return conn->handshake->cookie; } /** * @brief Allows to get accepted protocol in the case a protocol was * requested during connection. * * @param conn The connection where the operation is taking place. * * @return A reference to the protocol accepted or NULL if no protocol * was mentioned during the handshake. */ const char * nopoll_conn_get_accepted_protocol (noPollConn * conn) { if (conn == NULL) return NULL; return conn->accepted_protocol; /* report accepted protocol */ } /** * @brief Allows to get requested protocols for the provided * connection * * @param conn The connection where the operation is taking place. * * @return A reference to the protocol or list of protocols requested * by this connection. */ const char * nopoll_conn_get_requested_protocol (noPollConn * conn) { if (conn == NULL) return NULL; return conn->protocols; /* report requested protocol */ } /** * @brief Allows to configure accepted protocol on the provided * connection. * * @param conn The connection where the accepted protocol will be * notified. This function is only useful at server side to notify * accepted protocol to the connecting client. Caller is entirely * responsible on setting an appropriate value that is understood by * the client. No especial check is done on the value provided to this function. * * @param protocol The protocol to configure in the reply, that is, * the protocol accepted by the server. * * If no protocol is configured and the client requests a protocol, * the server will reply by default the same accepting it. */ void nopoll_conn_set_accepted_protocol (noPollConn * conn, const char * protocol) { if (conn == NULL || protocol == NULL) return; /* set accepted protocol */ conn->accepted_protocol = nopoll_strdup (protocol); return; } /** * @brief Returns the port location this connection connects to or it * is listening (according to the connection role \ref noPollRole). * * @param conn The connection to check for the port value. * * @return The port location value or NULL if it fails. */ const char * nopoll_conn_port (noPollConn * conn) { if (conn == NULL) return NULL; return conn->port; } /** * @brief Optionally, remote peer can close the connection providing an * error code (\ref nopoll_conn_get_close_status) and a reason (\ref nopoll_conn_get_close_reason). * * These functions are used to get those values. * * @param conn The connection where the peer reported close status is being asked. * * @return Status code reported by the remote peer or 0 if nothing was reported. */ int nopoll_conn_get_close_status (noPollConn * conn) { if (conn == NULL) return 0; return conn->peer_close_status; } /** * @brief Optionally, remote peer can close the connection providing an * error code (\ref nopoll_conn_get_close_status) and a reason (\ref nopoll_conn_get_close_reason). * * These functions are used to get those values. * * @param conn The connection where the peer reported close reason is being asked. * * @return Status close reason reported by the remote peer or NULL if nothing was reported. */ const char * nopoll_conn_get_close_reason (noPollConn * conn) { if (conn == NULL) return NULL; return conn->peer_close_reason; } /** * @brief Call to close the connection immediately without going * through websocket close negotiation. * * @param conn The connection to be shutted down. */ void nopoll_conn_shutdown (noPollConn * conn) { #if defined(SHOW_DEBUG_LOG) const char * role = NULL; #endif if (conn == NULL) return; /* report connection close */ #if defined(SHOW_DEBUG_LOG) if (conn->role == NOPOLL_ROLE_LISTENER) role = "listener"; else if (conn->role == NOPOLL_ROLE_MAIN_LISTENER) role = "master-listener"; else if (conn->role == NOPOLL_ROLE_UNKNOWN) role = "unknown"; else if (conn->role == NOPOLL_ROLE_CLIENT) role = "client"; else role = "unknown"; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "shutting down connection id=%d (session: %d, role: %s)", conn->id, conn->session, role); #endif /* call to on close handler if defined */ if (conn->session != NOPOLL_INVALID_SOCKET && conn->on_close) conn->on_close (conn->ctx, conn, conn->on_close_data); /* shutdown connection here */ if (conn->session != NOPOLL_INVALID_SOCKET) { shutdown (conn->session, SHUT_RDWR); nopoll_close_socket (conn->session); } conn->session = NOPOLL_INVALID_SOCKET; return; } /** * @brief Allows to close an opened \ref noPollConn no matter its role * (\ref noPollRole). * * @param conn The connection to close. * * @param status Optional status code to send to remote side. If * status is < 0, no status code is sent. * * @param reason Pointer to the content to be sent. * * @param reason_size The amount of bytes that should be used from content pointer. */ void nopoll_conn_close_ext (noPollConn * conn, int status, const char * reason, int reason_size) { int refs; char * content; #if defined(SHOW_DEBUG_LOG) const char * role = "unknown"; #endif /* check input data */ if (conn == NULL) return; #if defined(SHOW_DEBUG_LOG) if (conn->role == NOPOLL_ROLE_LISTENER) role = "listener"; else if (conn->role == NOPOLL_ROLE_MAIN_LISTENER) role = "master-listener"; else if (conn->role == NOPOLL_ROLE_UNKNOWN) role = "unknown"; else if (conn->role == NOPOLL_ROLE_CLIENT) role = "client"; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Calling to close close id=%d (session %d, refs: %d, role: %s)", conn->id, conn->session, conn->refs, role); #endif if (conn->session != NOPOLL_INVALID_SOCKET) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "requested proper connection close id=%d (session %d)", conn->id, conn->session); /* build reason indication */ content = NULL; if (reason && reason_size > 0) { /* send content */ content = nopoll_new (char, reason_size + 3); if (content) { nopoll_set_16bit (status, content); memcpy (content + 2, reason, reason_size); } /* end if */ } /* end if */ /* send close without reason */ nopoll_conn_send_frame (conn, nopoll_true /* has_fin */, /* masked */ conn->role == NOPOLL_ROLE_CLIENT, NOPOLL_CLOSE_FRAME, /* content size and content */ reason_size > 0 ? reason_size + 2 : 0, content, /* sleep in header */ 0); /* release content (if defined) */ nopoll_free (content); /* call to shutdown connection */ nopoll_conn_shutdown (conn); } /* end if */ /* unregister connection from context */ refs = nopoll_conn_ref_count (conn); nopoll_ctx_unregister_conn (conn->ctx, conn); /* avoid calling next unref in the case not enough references * are found */ if (refs <= 1) return; /* call to unref connection */ nopoll_conn_unref (conn); return; } /** * @brief Allows to close an opened \ref noPollConn no matter its role * (\ref noPollRole). * * @param conn The connection to close. * * There is available an alternative extended version that allows to * send the status code and the error message: \ref * nopoll_conn_close_ext */ void nopoll_conn_close (noPollConn * conn) { /* call to close without providing a reason */ nopoll_conn_close_ext (conn, 0, NULL, 0); return; } /** * @brief Allows to define a user level pointer associated to this * connection. This pointer can be later be retrieved using \ref * nopoll_conn_get_hook. * * @param conn The connection where the pointer will be associated. * * @param ptr The pointer to associate to the connection. */ void nopoll_conn_set_hook (noPollConn * conn, noPollPtr ptr) { if (conn == NULL) return; conn->hook = ptr; return; } /** * @brief Allows to get the user level pointer defined by \ref * nopoll_conn_get_hook. * * @param conn The connection where the uesr level pointer was stored. * * @return A reference to the pointer stored. */ noPollPtr nopoll_conn_get_hook (noPollConn * conn) { if (conn == NULL) return NULL; return conn->hook; } /** * @brief Allows to unref connection reference acquired via \ref * nopoll_conn_ref. * * @param conn The connection to be unrefered. */ void nopoll_conn_unref (noPollConn * conn) { if (conn == NULL) return; /* acquire here the mutex */ nopoll_mutex_lock (conn->ref_mutex); conn->refs--; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Releasing connection id %d reference, current ref count status is: %d", conn->id, conn->refs); if (conn->refs != 0) { /* release here the mutex */ nopoll_mutex_unlock (conn->ref_mutex); return; } /* release here the mutex */ nopoll_mutex_unlock (conn->ref_mutex); /* release message */ if (conn->pending_msg) nopoll_msg_unref (conn->pending_msg); /* release ctx */ if (conn->ctx) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Released context refs, now: %d", conn->ctx->refs); nopoll_ctx_unref (conn->ctx); } /* end if */ conn->ctx = NULL; /* free all internal strings */ nopoll_free (conn->host); nopoll_free (conn->port); nopoll_free (conn->host_name); nopoll_free (conn->origin); nopoll_free (conn->protocols); nopoll_free (conn->accepted_protocol); nopoll_free (conn->get_url); /* close reason if any */ nopoll_free (conn->peer_close_reason); /* release TLS certificates */ nopoll_free (conn->certificate); nopoll_free (conn->private_key); nopoll_free (conn->chain_certificate); /* release uncomplete message */ if (conn->previous_msg) nopoll_msg_unref (conn->previous_msg); if (conn->ssl) SSL_free (conn->ssl); if (conn->ssl_ctx) SSL_CTX_free (conn->ssl_ctx); /* release handshake internal data */ if (conn->handshake) { nopoll_free (conn->handshake->websocket_key); nopoll_free (conn->handshake->websocket_version); nopoll_free (conn->handshake->websocket_accept); nopoll_free (conn->handshake->expected_accept); nopoll_free (conn->handshake->cookie); nopoll_free (conn->handshake); } /* end if */ /* release connection options if defined and reuse flag is not defined */ if (conn->opts && ! conn->opts->reuse) nopoll_conn_opts_free (conn->opts); /* release pending write buffer */ nopoll_free (conn->pending_write); /* release mutex */ nopoll_mutex_destroy (conn->ref_mutex); nopoll_free (conn); return; } /** * @internal Default connection receive until handshake is complete. */ int nopoll_conn_default_receive (noPollConn * conn, char * buffer, int buffer_size) { return recv (conn->session, buffer, buffer_size, 0); } /** * @internal Default connection send until handshake is complete. */ int nopoll_conn_default_send (noPollConn * conn, char * buffer, int buffer_size) { return send (conn->session, buffer, buffer_size, 0); } /** * @internal Read the next line, byte by byte until it gets a \n or * maxlen is reached. Some code errors are used to manage exceptions * (see return values) * * @param connection The connection where the read operation will be done. * * @param buffer A buffer to store content read from the network. * * @param maxlen max content to read from the network. * * @return values returned by this function follows: * 0 - remote peer have closed the connection * -1 - an error have happened while reading * -2 - could read because this connection is on non-blocking mode and there is no data. * n - some data was read. * **/ int nopoll_conn_readline (noPollConn * conn, char * buffer, int maxlen) { int n, rc; int desp; char c, *ptr; #if defined(SHOW_DEBUG_LOG) # if !defined(SHOW_FORMAT_BUGS) noPollCtx * ctx = conn->ctx; # endif #endif /* clear the buffer received */ /* memset (buffer, 0, maxlen * sizeof (char )); */ /* check for pending line read */ desp = 0; if (conn->pending_line) { /* get size and check exceeded values */ desp = strlen (conn->pending_line); if (desp >= maxlen) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "found fragmented frame line header but allowed size was exceeded (desp:%d >= maxlen:%d)", desp, maxlen); nopoll_conn_shutdown (conn); return -1; } /* end if */ /* now store content into the buffer */ memcpy (buffer, conn->pending_line, desp); /* clear from the conn the line */ nopoll_free (conn->pending_line); conn->pending_line = NULL; } /* read current next line */ ptr = (buffer + desp); for (n = 1; n < (maxlen - desp); n++) { nopoll_readline_again: if (( rc = conn->receive (conn, &c, 1)) == 1) { *ptr++ = c; if (c == '\x0A') break; }else if (rc == 0) { if (n == 1) return 0; else break; } else { if (errno == NOPOLL_EINTR) goto nopoll_readline_again; if ((errno == NOPOLL_EWOULDBLOCK) || (errno == NOPOLL_EAGAIN) || (rc == -2)) { if (n > 0) { /* store content read until now */ if ((n + desp - 1) > 0) { buffer[n+desp - 1] = 0; conn->pending_line = nopoll_strdup (buffer); } /* end if */ } /* end if */ return -2; } if (nopoll_conn_is_ok (conn) && errno == 0 && rc == 0) { nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "unable to read line, but errno is 0, and connection is ok, return to keep on trying.."); return -2; } /* if the conn is closed, just return * without logging a message */ nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "unable to read line, error code errno: %d, rc: %d (%s)", errno, rc, strerror (errno)); return (-1); } } *ptr = 0; return (n + desp); } /** * @brief Allows to get the master listener that was used to accept * the provided connection that represents a listener connection. * * @param conn A listener connection that was accepted for which we * want to get the master listener connection. The connection must be * a \ref NOPOLL_ROLE_LISTENER (as reported by \ref nopoll_conn_role). * * @return A reference to the master listener connection or NULL if it * fails. It can only fail when NULL reference is received or the * connection is not a \ref NOPOLL_ROLE_LISTENER. */ noPollConn * nopoll_conn_get_listener (noPollConn * conn) { /* check if the incoming connection is a NOPOLL_ROLE_LISTENER * that is an accepted connection */ if (conn == NULL || conn->role != NOPOLL_ROLE_LISTENER) return NULL; return conn->listener; } void __nopoll_pack_content (char * buffer, int start, int bytes) { int iterator = 0; while (iterator < bytes) { /* copy bytes to the begining of the array */ buffer[iterator] = buffer[start + iterator]; /* next position */ iterator++; } /* end while */ return; } /** * @internal Function used to read bytes from the wire. * * @return The function returns the number of bytes read, 0 when no * bytes were available and -1 when it fails. */ int __nopoll_conn_receive (noPollConn * conn, char * buffer, int maxlen) { int nread; if (conn->pending_buf_bytes > 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Calling with bytes we can reuse (%d), requested: %d", conn->pending_buf_bytes, maxlen); if (conn->pending_buf_bytes >= maxlen) { /* we have more in the buffer to serve than * requested, ok, copy into the buffer and * pack */ memcpy (buffer, conn->pending_buf, maxlen); __nopoll_pack_content (conn->pending_buf, maxlen, conn->pending_buf_bytes - maxlen); conn->pending_buf_bytes -= maxlen; return maxlen; } /* ok, we don't have enough bytes to serve * directly, so copy what we have */ memcpy (buffer, conn->pending_buf, conn->pending_buf_bytes); /* copy number of bytes served to reduce next request */ nread = conn->pending_buf_bytes; conn->pending_buf_bytes = 0; /* call again to get bytes reducing the request in the * amount of bytes served */ return __nopoll_conn_receive (conn, buffer + nread, maxlen - nread) + nread; } /* end if */ keep_reading: /* clear buffer */ /* memset (buffer, 0, maxlen * sizeof (char )); */ #if defined(NOPOLL_OS_UNIX) errno = 0; #endif if ((nread = conn->receive (conn, buffer, maxlen)) == NOPOLL_SOCKET_ERROR) { /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " returning errno=%d (%s)", errno, strerror (errno)); */ if (errno == NOPOLL_EAGAIN) return 0; if (errno == NOPOLL_EWOULDBLOCK) return 0; if (errno == NOPOLL_EINTR) goto keep_reading; nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "unable to readn=%d, error code was: %d (%s) (shutting down connection)", maxlen, errno, strerror (errno)); nopoll_conn_shutdown (conn); return -1; } /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " returning bytes read = %d", nread); */ if (nread == 0) { /* check for blocking operations */ if (errno == NOPOLL_EAGAIN || errno == NOPOLL_EWOULDBLOCK) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "unable to read from conn-id=%d (%s:%s), connection is not ready (errno: %d : %s)", conn->id, conn->host, conn->port, errno, strerror (errno)); return 0; } /* end if */ nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "received connection close while reading from conn id %d (errno=%d : %s) (%d, %d, %d), shutting down connection..", conn->id, errno, strerror (errno), NOPOLL_EAGAIN, NOPOLL_EWOULDBLOCK, NOPOLL_EINTR); nopoll_conn_shutdown (conn); } /* end if */ /* ensure we don't access outside the array */ if (nread < 0) nread = 0; buffer[nread] = 0; return nread; } nopoll_bool nopoll_conn_get_http_url (noPollConn * conn, const char * buffer, int buffer_size, const char * method, char ** url) { int iterator; int iterator2; #if defined(SHOW_DEBUG_LOG) # if ! defined(SHOW_FORMAT_BUGS) noPollCtx * ctx = conn->ctx; # endif #endif /* check if we already received method */ if (conn->get_url) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received GET method declartion when it was already received during handshake..closing session"); nopoll_conn_shutdown (conn); return nopoll_false; } /* end if */ /* the get url must have a minimum size: GET / HTTP/1.1\r\n 16 (15 if only \n) */ if (buffer_size < 15) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received uncomplete GET method during handshake, closing session"); nopoll_conn_shutdown (conn); return nopoll_false; } /* end if */ /* skip white spaces */ iterator = 4; while (iterator < buffer_size && buffer[iterator] == ' ') iterator++; if (buffer_size == iterator) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method without an starting request url, closing session", method); nopoll_conn_shutdown (conn); return nopoll_false; } /* end if */ /* now check url format */ if (buffer[iterator] != '/') { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method with a request url that do not start with /, closing session", method); nopoll_conn_shutdown (conn); return nopoll_false; } /* ok now find the rest of the url content util the next white space */ iterator2 = (iterator + 1); while (iterator2 < buffer_size && buffer[iterator2] != ' ') iterator2++; if (buffer_size == iterator2) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method with an uncomplate request url, closing session", method); nopoll_conn_shutdown (conn); return nopoll_false; } /* end if */ (*url) = nopoll_new (char, iterator2 - iterator + 1); memcpy (*url, buffer + iterator, iterator2 - iterator); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Found url method: '%s'", *url); /* now check final HTTP header */ iterator = iterator2 + 1; while (iterator < buffer_size && buffer[iterator] == ' ') iterator++; if (buffer_size == iterator) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method with an uncomplate request url, closing session", method); nopoll_conn_shutdown (conn); return nopoll_false; } /* end if */ /* now check trailing content */ return nopoll_cmp (buffer + iterator, "HTTP/1.1\r\n") || nopoll_cmp (buffer + iterator, "HTTP/1.1\n"); } /** * @internal Function that parses the mime header found on the * provided buffer. */ nopoll_bool nopoll_conn_get_mime_header (noPollCtx * ctx, noPollConn * conn, const char * buffer, int buffer_size, char ** header, char ** value) { int iterator = 0; int iterator2 = 0; /* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Processing content: %s", buffer); */ /* ok, find the first : */ while (iterator < buffer_size && buffer[iterator] && buffer[iterator] != ':') iterator++; if (buffer[iterator] != ':') { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Expected to find mime header separator : but it wasn't found (buffer_size=%d, iterator=%d, content: [%s]..", buffer_size, iterator, buffer); return nopoll_false; } /* copy the header value */ (*header) = nopoll_new (char, iterator + 1); memcpy (*header, buffer, iterator); /* now get the mime header value */ iterator2 = iterator + 1; while (iterator2 < buffer_size && buffer[iterator2] && buffer[iterator2] != '\n') iterator2++; if (buffer[iterator2] != '\n') { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Expected to find mime header value end (13) but it wasn't found (iterator=%d, iterator2=%d, for header: [%s], found value: [%d]), inside content: [%s]..", iterator, iterator2, (*header), (int)buffer[iterator2], buffer); nopoll_free (*header); (*header) = NULL; (*value) = NULL; return nopoll_false; } /* copy the value */ (*value) = nopoll_new (char, (iterator2 - iterator) + 1); memcpy (*value, buffer + iterator + 1, iterator2 - iterator); /* trim content */ nopoll_trim (*value, NULL); nopoll_trim (*header, NULL); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Found MIME header: '%s' -> '%s'", *header, *value); return nopoll_true; } /** * @internal Function that ensures we don't receive any */ nopoll_bool nopoll_conn_check_mime_header_repeated (noPollConn * conn, char * header, char * value, const char * ref_header, noPollPtr check) { if (strcasecmp (ref_header, header) == 0) { if (check) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Provided header %s twice, closing connection", header); nopoll_free (header); nopoll_free (value); nopoll_conn_shutdown (conn); return nopoll_true; } /* end if */ } /* end if */ return nopoll_false; } char * nopoll_conn_produce_accept_key (noPollCtx * ctx, const char * websocket_key) { const char * static_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; char * accept_key; int accept_key_size; int key_length; if (websocket_key == NULL) return NULL; key_length = strlen (websocket_key); unsigned char buffer[EVP_MAX_MD_SIZE]; EVP_MD_CTX mdctx; const EVP_MD * md = NULL; unsigned int md_len = EVP_MAX_MD_SIZE; accept_key_size = key_length + 36 + 1; accept_key = nopoll_new (char, accept_key_size); memcpy (accept_key, websocket_key, key_length); memcpy (accept_key + key_length, static_guid, 36); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "base key value: %s", accept_key); /* now sha-1 */ md = (EVP_MD *)EVP_sha1 (); EVP_DigestInit (&mdctx, md); EVP_DigestUpdate (&mdctx, accept_key, strlen (accept_key)); EVP_DigestFinal (&mdctx, buffer, &md_len); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Sha-1 length is: %u", md_len); /* now convert into base64 */ if (! nopoll_base64_encode ((const char *) buffer, md_len, (char *) accept_key, &accept_key_size)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to base64 sec-websocket-key value.."); return NULL; } nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Returning Sec-Websocket-Accept: %s", accept_key); return accept_key; } nopoll_bool nopoll_conn_complete_handshake_check_listener (noPollCtx * ctx, noPollConn * conn) { char * reply; int reply_size; char * accept_key; noPollActionHandler on_ready; noPollPtr on_ready_data; const char * protocol; /* call to check listener handshake */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking client handshake data.."); /* ensure we have all minumum data */ if (! conn->handshake->upgrade_websocket || ! conn->handshake->connection_upgrade || ! conn->handshake->websocket_key || ! conn->origin || ! conn->handshake->websocket_version) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Client from %s:%s didn't provide all websocket handshake values required, closing session (Upgraded: websocket %d, Connection: upgrade%d, Sec-WebSocket-Key: %p, Origin: %p, Sec-WebSocket-Version: %p)", conn->host, conn->port, conn->handshake->upgrade_websocket, conn->handshake->connection_upgrade, conn->handshake->websocket_key, conn->origin, conn->handshake->websocket_version); return nopoll_false; } /* end if */ /* check protocol support */ if (strtol (conn->handshake->websocket_version, NULL, 10) != ctx->protocol_version) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received request for an unsupported protocol version: %s, expected: %d", conn->handshake->websocket_version, ctx->protocol_version); return nopoll_false; } /* end if */ /* now call the user app level to accept the websocket connection */ if (ctx->on_open) { if (! ctx->on_open (ctx, conn, ctx->on_open_data)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Client from %s:%s was denied by application level (on open handler %p), clossing session", conn->host, conn->port, ctx->on_open); nopoll_conn_shutdown (conn); return nopoll_false; } } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Client from %s:%s was accepted, replying accept", conn->host, conn->port); /* produce accept key */ accept_key = nopoll_conn_produce_accept_key (ctx, conn->handshake->websocket_key); /* ok, send handshake reply */ if (conn->protocols || conn->accepted_protocol) { /* set protocol in the reply taking preference by the value configured at conn->accepted_protocol */ protocol = conn->accepted_protocol; if (! protocol) protocol = conn->protocols; /* send accept header accepting protocol requested by the user */ reply = nopoll_strdup_printf ("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\nSec-WebSocket-Protocol: %s\r\n\r\n", accept_key, protocol); } else { /* send accept header without telling anything about protocols */ reply = nopoll_strdup_printf ("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n", accept_key); } nopoll_free (accept_key); if (reply == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to build reply, closing session"); return nopoll_false; } /* end if */ reply_size = strlen (reply); if (reply_size != conn->sends (conn, reply, reply_size)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to send reply, there was a failure, error code was: %d", errno); nopoll_free (reply); return nopoll_false; } /* end if */ /* free reply */ nopoll_free (reply); /* now call the user app level to accept the websocket connection */ if (ctx->on_ready || conn->on_ready) { /* set on ready handler, first considering conn->on_ready and then ctx->on_ready */ on_ready = conn->on_ready; on_ready_data = conn->on_ready_data; if (! on_ready) { on_ready = ctx->on_ready; on_ready_data = ctx->on_ready_data; } /* end if */ if (on_ready && ! on_ready (ctx, conn, on_ready_data)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Client from %s:%s was denied by application level (on ready handler: %p), clossing session", conn->host, conn->port, on_ready); nopoll_conn_shutdown (conn); return nopoll_false; } } /* end if */ return nopoll_true; /* signal handshake was completed */ } nopoll_bool nopoll_conn_complete_handshake_check_client (noPollCtx * ctx, noPollConn * conn) { char * accept; nopoll_bool result; /* check all data received */ if (! conn->handshake->websocket_accept || ! conn->handshake->upgrade_websocket || ! conn->handshake->connection_upgrade) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received uncomplete listener handshake reply (%p %d %d)", conn->handshake->websocket_accept, conn->handshake->upgrade_websocket, conn->handshake->connection_upgrade); return nopoll_false; } /* end if */ /* check accept value here */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking accept key from listener.."); accept = nopoll_conn_produce_accept_key (ctx, conn->handshake->websocket_key); result = nopoll_cmp (accept, conn->handshake->websocket_key); if (! result) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to accept connection Sec-Websocket-Accept %s is not expected %s, closing session", accept, conn->handshake->websocket_key); nopoll_conn_shutdown (conn); } nopoll_free (accept); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Sec-Websocket-Accept matches expected value..nopoll_conn_complete_handshake_check_client (%p, %p)=%d", ctx, conn, result); return result; } /** * @internal Function used to validate all handshake received until * now. */ void nopoll_conn_complete_handshake_check (noPollConn * conn) { noPollCtx * ctx = conn->ctx; nopoll_bool result = nopoll_false; nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "calling to check handshake received on connection id %d role %d..", conn->id, conn->role); if (conn->role == NOPOLL_ROLE_LISTENER) { result = nopoll_conn_complete_handshake_check_listener (ctx, conn); } else if (conn->role == NOPOLL_ROLE_CLIENT) { result = nopoll_conn_complete_handshake_check_client (ctx, conn); } /* end if */ /* flag connection as ready: now we can get messages */ if (result) { conn->handshake_ok = nopoll_true; } else { nopoll_conn_shutdown (conn); } /* end if */ return; } /** * @internal Handler that implements one step of the websocket * listener handshake received from client, until it is completed. * * @return Returns 0 to return (because error or because no data is * available) or 1 to signal the caller continue reading if it is * possible. */ int nopoll_conn_complete_handshake_listener (noPollCtx * ctx, noPollConn * conn, char * buffer, int buffer_size) { char * header; char * value; /* handle content */ if (nopoll_ncmp (buffer, "GET ", 4)) { /* get url method */ nopoll_conn_get_http_url (conn, buffer, buffer_size, "GET", &conn->get_url); return 1; } /* end if */ /* get mime header */ if (! nopoll_conn_get_mime_header (ctx, conn, buffer, buffer_size, &header, &value)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to acquire mime header from remote peer during handshake, closing connection"); nopoll_conn_shutdown (conn); return 0; } /* ok, process here predefined headers */ if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Host", conn->host_name)) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Upgrade", INT_TO_PTR (conn->handshake->upgrade_websocket))) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Connection", INT_TO_PTR (conn->handshake->connection_upgrade))) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Key", conn->handshake->websocket_key)) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Origin", conn->origin)) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Protocol", conn->protocols)) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Version", conn->handshake->websocket_version)) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Cookie", conn->handshake->cookie)) return 0; /* set the value if required */ if (strcasecmp (header, "Host") == 0) conn->host_name = value; else if (strcasecmp (header, "Sec-Websocket-Key") == 0) conn->handshake->websocket_key = value; else if (strcasecmp (header, "Origin") == 0) conn->origin = value; else if (strcasecmp (header, "Sec-Websocket-Protocol") == 0) conn->protocols = value; else if (strcasecmp (header, "Sec-Websocket-Version") == 0) conn->handshake->websocket_version = value; else if (strcasecmp (header, "Upgrade") == 0) { conn->handshake->upgrade_websocket = 1; nopoll_free (value); } else if (strcasecmp (header, "Connection") == 0) { conn->handshake->connection_upgrade = 1; nopoll_free (value); } else if (strcasecmp (header, "Cookie") == 0) { /* record cookie so it can be used by the application level */ conn->handshake->cookie = value; } else { /* release value, no body claimed it */ nopoll_free (value); } /* end if */ /* release the header */ nopoll_free (header); return 1; /* continue reading lines */ } /** * @internal Handler that implements one step of the websocket * client handshake received from the server, until it is completed. * * @return Returns 0 to return (because error or because no data is * available) or 1 to signal the caller continue reading if it is * possible. */ int nopoll_conn_complete_handshake_client (noPollCtx * ctx, noPollConn * conn, char * buffer, int buffer_size) { char * header; char * value; int iterator; /* handle content */ if (! conn->handshake->received_101 && nopoll_ncmp (buffer, "HTTP/1.1 ", 9)) { iterator = 9; while (iterator < buffer_size && buffer[iterator] && buffer[iterator] == ' ') iterator++; if (! nopoll_ncmp (buffer + iterator, "101", 3)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "websocket server denied connection with: %s", buffer + iterator); return 0; /* do not continue */ } /* end if */ /* flag that we have received HTTP/1.1 101 indication */ conn->handshake->received_101 = nopoll_true; return 1; } /* end if */ /* get mime header */ if (! nopoll_conn_get_mime_header (ctx, conn, buffer, buffer_size, &header, &value)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to acquire mime header from remote peer during handshake, closing connection"); nopoll_conn_shutdown (conn); return 0; } /* ok, process here predefined headers */ if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Upgrade", INT_TO_PTR (conn->handshake->upgrade_websocket))) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Connection", INT_TO_PTR (conn->handshake->connection_upgrade))) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Accept", conn->handshake->websocket_accept)) return 0; if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Protocol", conn->accepted_protocol)) return 0; /* set the value if required */ if (strcasecmp (header, "Sec-Websocket-Accept") == 0) conn->handshake->websocket_accept = value; else if (strcasecmp (header, "Sec-Websocket-Protocol") == 0) conn->accepted_protocol = value; else if (strcasecmp (header, "Upgrade") == 0) { conn->handshake->upgrade_websocket = 1; nopoll_free (value); } else if (strcasecmp (header, "Connection") == 0) { conn->handshake->connection_upgrade = 1; nopoll_free (value); } else { /* release value, no body claimed it */ nopoll_free (value); } /* end if */ /* release the header */ nopoll_free (header); return 1; /* continue reading lines */ } /** * @internal Function that completes the handshake in an non-blocking * manner taking into consideration the connection type (listener or * client). */ void nopoll_conn_complete_handshake (noPollConn * conn) { char buffer[1024]; int buffer_size; noPollCtx * ctx = conn->ctx; /* ensure we didn't complete handshake */ if (conn->handshake_ok) return; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Checking to complete conn-id=%d WebSocket handshake, role %d", conn->id, conn->role); /* ensure handshake object is created */ if (conn->handshake == NULL) conn->handshake = nopoll_new (noPollHandShake, 1); /* get lines and complete the handshake data */ while (nopoll_true) { /* clear buffer for debugging functions */ buffer[0] = 0; /* get next line to process */ buffer_size = nopoll_conn_readline (conn, buffer, 1024); if (buffer_size == 0 || buffer_size == -1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unexpected connection close during handshake..closing connection"); nopoll_conn_shutdown (conn); return; } /* end if */ /* no data at this moment, return to avoid consuming data */ if (buffer_size == -2) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "No more data available on connection id %d", conn->id); return; } /* drop a debug line */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Bytes read %d from connection id %d: %s", buffer_size, conn->id, buffer); /* check if we have received the end of the websocket client handshake */ if (buffer_size == 2 && nopoll_ncmp (buffer, "\r\n", 2)) { nopoll_conn_complete_handshake_check (conn); return; } if (conn->role == NOPOLL_ROLE_LISTENER) { /* call to complete listener handshake */ if (nopoll_conn_complete_handshake_listener (ctx, conn, buffer, buffer_size) == 1) continue; } else if (conn->role == NOPOLL_ROLE_CLIENT) { /* call to complete listener handshake */ if (nopoll_conn_complete_handshake_client (ctx, conn, buffer, buffer_size) == 1) continue; } else { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Called to handle connection handshake on a connection with an unexpected role: %d, closing session", conn->role); nopoll_conn_shutdown (conn); return; } } /* end while */ return; } void nopoll_conn_mask_content (noPollCtx * ctx, char * payload, int payload_size, char * mask, int desp) { int iter = 0; int mask_index = 0; while (iter < payload_size) { /* rotate mask and apply it */ mask_index = (iter + desp) % 4; payload[iter] ^= mask[mask_index]; iter++; } /* end while */ return; } /** * @brief Allows to get the next message available on the provided * connection. The function returns NULL in the case no message is * still ready to be returned. * * The function do not block. * * @param conn The connection where the read operation will take * place. * * @return A reference to a noPollMsg object or NULL if there is * nothing available. In case the function returns NULL, check * connection status with \ref nopoll_conn_is_ok. */ noPollMsg * nopoll_conn_get_msg (noPollConn * conn) { char buffer[20]; int bytes; noPollMsg * msg; int ssl_error; int header_size; #if defined(SHOW_DEBUG_LOG) long result; #endif #if defined(NOPOLL_64BIT_PLATFORM) unsigned char *len; #endif if (conn == NULL) return NULL; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "=== START: conn-id=%d (errno=%d, session: %d, conn->handshake_ok: %d, conn->pending_ssl_accept: %d) ===", conn->id, errno, conn->session, conn->handshake_ok, conn->pending_ssl_accept); /* check for accept SSL connection */ if (conn->pending_ssl_accept) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received connect over a connection (id %d) with TLS handshake pending to be finished, processing..", conn->id); /* get ssl error */ ssl_error = SSL_accept (conn->ssl); if (ssl_error == -1) { /* get error */ ssl_error = SSL_get_error (conn->ssl, -1); nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "accept function have failed (for listener side) ssl_error=%d : dumping error stack..", ssl_error); switch (ssl_error) { case SSL_ERROR_WANT_READ: nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because read wanted conn-id=%d (%p, session %d)", conn->id, conn, conn->session); return NULL; case SSL_ERROR_WANT_WRITE: nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because write wanted conn-id=%d (%p)", conn->id, conn); return NULL; default: break; } /* end switch */ /* TLS-fication process have failed */ nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "there was an error while accepting TLS connection"); nopoll_conn_log_ssl (conn); nopoll_conn_shutdown (conn); return NULL; } /* end if */ /* ssl accept */ conn->pending_ssl_accept = nopoll_false; nopoll_conn_set_sock_block (conn->session, nopoll_false); #if defined(SHOW_DEBUG_LOG) result = SSL_get_verify_result (conn->ssl); nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Completed TLS operation from %s:%s (conn id %d, ssl veriry result: %d)", conn->host, conn->port, conn->id, (int) result); #endif /* configure default handlers */ conn->receive = nopoll_conn_tls_receive; conn->sends = nopoll_conn_tls_send; /* call to check post ssl checks after SSL finalization */ if (conn->ctx && conn->ctx->post_ssl_check) { if (! conn->ctx->post_ssl_check (conn->ctx, conn, conn->ssl_ctx, conn->ssl, conn->ctx->post_ssl_check_data)) { /* TLS post check failed */ nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "TLS/SSL post check function failed, dropping connection"); nopoll_conn_shutdown (conn); return NULL; } /* end if */ } /* end if */ /* set this connection has TLS ok */ conn->tls_on = nopoll_true; #if defined(NOPOLL_OS_UNIX) /* report NULL because this was a call to complete TLS */ errno = NOPOLL_EWOULDBLOCK; /* simulate there is no data available to stop here. If there is no data indeed, on next call it will not fail */ #endif return NULL; } /* end if */ /* check connection status */ if (! conn->handshake_ok) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Connection id %d handshake is not complete, running..", conn->id); /* acquire here handshake mutex */ nopoll_mutex_lock (conn->ref_mutex); /* complete handshake */ nopoll_conn_complete_handshake (conn); /* release here handshake mutex */ nopoll_mutex_unlock (conn->ref_mutex); if (! conn->handshake_ok) return NULL; } /* end if */ if (conn->previous_msg) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Reading bytes (previously read %d) from a previous unfinished frame (pending: %d) over conn-id=%d", conn->previous_msg->payload_size, conn->previous_msg->remain_bytes, conn->id); /* build next message holder to continue with this content */ if (conn->previous_msg->payload_size > 0) { msg = nopoll_msg_new (); if (msg == NULL) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Failed to allocate memory for received message, closing session id: %d", conn->id); nopoll_conn_shutdown (conn); return NULL; } /* end if */ /* flag this message as a fragment */ msg->is_fragment = nopoll_true; /* get fin bytes */ msg->has_fin = 1; /* for now final message */ msg->op_code = 0; /* continuation frame */ /* copy initial mask indication */ msg->is_masked = conn->previous_msg->is_masked; msg->payload_size = conn->previous_msg->remain_bytes; msg->unmask_desp = conn->previous_msg->unmask_desp; /* copy mask */ memcpy (msg->mask, conn->previous_msg->mask, 4); if (msg->is_masked) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Reusing mask value = %d from previous frame (%d)", nopoll_get_32bit (msg->mask)); nopoll_show_byte (conn->ctx, msg->mask[0], "mask[0]"); nopoll_show_byte (conn->ctx, msg->mask[1], "mask[1]"); nopoll_show_byte (conn->ctx, msg->mask[2], "mask[2]"); nopoll_show_byte (conn->ctx, msg->mask[3], "mask[3]"); } /* release previous reference because de don't need it anymore */ nopoll_msg_unref (conn->previous_msg); } else { /* reuse reference */ msg = conn->previous_msg; /* update remaining bytes */ msg->payload_size = msg->remain_bytes; nopoll_free (msg->payload); nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "reusing noPollMsg reference (%p) since last payload read was 0, remaining: %d", msg, msg->payload_size); } /* nullify references */ conn->previous_msg = NULL; goto read_payload; } /* end if */ /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/63) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ */ /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Found data in opened connection id %d..", conn->id);*/ /* get the first 4 bytes from the websocket header */ bytes = __nopoll_conn_receive (conn, buffer, 2); if (bytes == 0) { /* connection not ready */ nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Connection id=%d without data, errno=%d : %s, returning no message", conn->id, errno, strerror (errno)); return NULL; } if (bytes <= 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received connection close, finishing connection session"); nopoll_conn_shutdown (conn); return NULL; } /* end if */ if (bytes != 2) { /* ok, store content read into the pending buffer for next call */ memcpy (conn->pending_buf + conn->pending_buf_bytes, buffer, bytes); conn->pending_buf_bytes += bytes; nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Expected to receive complete websocket frame header but found only %d bytes over conn-id=%d, saving to reuse later", bytes, conn->id); return NULL; } /* end if */ /* record header size */ header_size = 2; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received %d bytes for websocket header", bytes); nopoll_show_byte (conn->ctx, buffer[0], "header[0]"); nopoll_show_byte (conn->ctx, buffer[1], "header[1]"); /* build next message */ msg = nopoll_msg_new (); if (msg == NULL) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Failed to allocate memory for received message, closing session id: %d", conn->id); nopoll_conn_shutdown (conn); return NULL; } /* end if */ /* get fin bytes */ msg->has_fin = nopoll_get_bit (buffer[0], 7); msg->op_code = buffer[0] & 0x0F; msg->is_masked = nopoll_get_bit (buffer[1], 7); msg->payload_size = buffer[1] & 0x7F; /* ensure FIN = 1 in case we are listener */ if (conn->role == NOPOLL_ROLE_LISTENER && ! msg->is_masked) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received websocket frame with mask bit set to zero, closing session id: %d", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ /* check payload size value */ if (msg->payload_size < 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received wrong payload size at first 7 bits, closing session id: %d", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "interim payload size received: %d", (int) msg->payload_size); /* read the rest */ if (msg->payload_size < 126) { /* nothing to declare here */ } else if (msg->payload_size == 126) { /* get extended 2 bytes length as unsigned 16 bit unsigned integer */ bytes = __nopoll_conn_receive (conn, buffer + 2, 2); if (bytes != 2) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Failed to get next 2 bytes to read header from the wire, failed to received content, shutting down id=%d the connection", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ /* add to the header bytes read */ header_size += bytes; msg->payload_size = nopoll_get_16bit (buffer + 2); } else if (msg->payload_size == 127) { #if defined(NOPOLL_64BIT_PLATFORM) /* read more content (next 8 bytes) */ if ((bytes = __nopoll_conn_receive (conn, buffer, 8)) != 8) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Expected to receive next 6 bytes for websocket frame header but found only %d bytes, closing session: %d", bytes, conn->id); nopoll_conn_shutdown (conn); return NULL; } /* end if */ len = (unsigned char*)buffer; msg->payload_size = 0; msg->payload_size |= ((long)(len[0]) << 56); msg->payload_size |= ((long)(len[1]) << 48); msg->payload_size |= ((long)(len[2]) << 40); msg->payload_size |= ((long)(len[3]) << 32); msg->payload_size |= ((long)(len[4]) << 24); msg->payload_size |= ((long)(len[5]) << 16); msg->payload_size |= ((long)(len[6]) << 8); msg->payload_size |= len[7]; #else nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "noPoll doesn't support messages bigger than 65k on this plataform (support for 64bit not found)"); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; #endif } /* end if */ if (msg->op_code == NOPOLL_PONG_FRAME) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "PONG received over connection id=%d", conn->id); nopoll_msg_unref (msg); return NULL; } /* end if */ if (msg->op_code == NOPOLL_CLOSE_FRAME) { if (msg->payload_size == 0) { /* nothing more to add here, close frame without content received, so we have no reason to keep on reading */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Proper connection close frame received id=%d, shutting down", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ /* received close frame with content, try to read the content */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Proper connection close frame received id=%d with content bytes=%d, reading reason..", conn->id, msg->payload_size); } /* end if */ if (msg->op_code == NOPOLL_PING_FRAME) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "PING received over connection id=%d, replying PONG", conn->id); nopoll_msg_unref (msg); /* call to send pong */ nopoll_conn_send_pong (conn); return NULL; } /* end if */ /* get more bytes */ if (msg->is_masked) { bytes = __nopoll_conn_receive (conn, (char *) msg->mask, 4); if (bytes != 4) { /* record header read so far */ memcpy (conn->pending_buf, buffer, header_size); conn->pending_buf_bytes = header_size; /* record mask read so far if required */ if (bytes > 0) { memcpy (conn->pending_buf + header_size, msg->mask, bytes); conn->pending_buf_bytes += bytes; } /* end if */ /* release message because it not available here */ nopoll_msg_unref (msg); if (bytes >= 0 && nopoll_conn_is_ok (conn)) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Expected to receive incoming mask after header (4 bytes) but found %d bytes on conn-id=%d, saving %d for future operations ", bytes, conn->id, conn->pending_buf_bytes); return NULL; } /* end if */ nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Expected to receive incoming mask after header (4 bytes) but found %d bytes, shutting down id=%d the connection", bytes, conn->id); nopoll_conn_shutdown (conn); return NULL; } /* end if */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received mask value = %d", nopoll_get_32bit (msg->mask)); nopoll_show_byte (conn->ctx, msg->mask[0], "mask[0]"); nopoll_show_byte (conn->ctx, msg->mask[1], "mask[1]"); nopoll_show_byte (conn->ctx, msg->mask[2], "mask[2]"); nopoll_show_byte (conn->ctx, msg->mask[3], "mask[3]"); } /* end if */ /* check payload size */ if (msg->payload_size == 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Found incoming frame with payload size 0, shutting down id=%d the connection", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Detected incoming websocket frame: fin(%d), op_code(%d), is_masked(%d), payload size(%ld), mask=%d", msg->has_fin, msg->op_code, msg->is_masked, msg->payload_size, nopoll_get_32bit (msg->mask)); /* check here for the limit of message we are willing to accept */ /* FIX SECURITY ISSUE */ read_payload: /* copy payload received */ msg->payload = nopoll_new (char, msg->payload_size + 1); if (msg->payload == NULL) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to acquire memory to read the incoming frame, dropping connection id=%d", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ bytes = __nopoll_conn_receive (conn, (char *) msg->payload, msg->payload_size); if (bytes < 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Connection lost during message reception, dropping connection id=%d, bytes=%d, errno=%d : %s", conn->id, bytes, errno, strerror (errno)); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } /* end if */ if (bytes != msg->payload_size) { /* record we've got content pending to be read */ msg->remain_bytes = msg->payload_size - bytes; /* set connection in remaining data to read */ nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Received fewer bytes than expected (bytes: %d < payload size: %d)", bytes, (int) msg->payload_size); msg->payload_size = bytes; /* grab a reference to previous message to reuse itsdata but only when bytes > 0 because when bytes == 0, the reference is reused since it is not returned to the caller (see next lines) */ if (bytes > 0) nopoll_msg_ref (msg); conn->previous_msg = msg; /* flag this message as a fragment */ msg->is_fragment = nopoll_true; /* flag that this message doesn't have FIN = 0 because * we wasn't able to read it entirely */ msg->has_fin = 0; } /* end if */ /* flag the message was being a fragment according to previous flag */ msg->is_fragment = msg->is_fragment || conn->previous_was_fragment || msg->has_fin == 0; /* update was a fragment */ conn->previous_was_fragment = msg->is_fragment && msg->has_fin == 0; /* do not notify any frame since no content was found */ if (bytes == 0 && msg == conn->previous_msg) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "bytes == %d, msg (%p) == conn->previous_msg (%p)", bytes, msg, conn->previous_msg); return NULL; } /* end if */ /* now unmask content (if required) */ if (msg->is_masked) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Unmasking (payload size %d, mask: %d, msg: %p, desp: %d)", msg->payload_size, nopoll_get_32bit (msg->mask), msg, msg->unmask_desp); nopoll_conn_mask_content (conn->ctx, (char*) msg->payload, msg->payload_size, (char*) msg->mask, msg->unmask_desp); /* flag what was unmasked */ msg->unmask_desp += msg->payload_size; } /* end if */ /* check here close frame with reason */ if (msg->op_code == NOPOLL_CLOSE_FRAME) { /* try to read reason and report those values */ if (msg->payload_size >= 2) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Close frame received id=%d with content bytes=%d, peer status=%d, peer reason=%s, reading reason..", conn->id, msg->payload_size, nopoll_get_16bit ((char*)msg->payload), (char*)msg->payload + 2); /* get values so the user can get them */ conn->peer_close_status = nopoll_get_16bit (msg->payload); conn->peer_close_reason = nopoll_strdup ((const char*)msg->payload + 2); } /* end if */ /* release message, close the connection and return NULL to notify caller nothing to read for the application */ nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; } return msg; } /** * @internal Implementation to send Frames according to various * parameters passed in into the function. This is the core function * used to send frames in noPoll. * * @param conn The connection where the content will be sent. * * @param content The content that will be sent (user level content) * * @param length The length of such content to be sent. * * @param has_fin nopoll_true/nopoll_false to signal FIN header flag * * @param sleep_in_header Optional hacking option that allows to * include a pause between sending the header and the rest of the * content. */ int __nopoll_conn_send_common (noPollConn * conn, const char * content, long length, nopoll_bool has_fin, long sleep_in_header, noPollOpCode frame_type) { if (conn == NULL || content == NULL || length == 0 || length < -1) return -1; if (conn->role == NOPOLL_ROLE_MAIN_LISTENER) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Trying to send content over a master listener connection"); return -1; } /* end if */ if (length == -1) { if (NOPOLL_BINARY_FRAME == frame_type) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received length == -1 for binary frame. Unable to guess length"); return -1; } /* end if */ length = strlen (content); } nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_send_text: Attempting to send %d bytes", (int) length); /* sending content as client */ if (conn->role == NOPOLL_ROLE_CLIENT) { return nopoll_conn_send_frame (conn, /* fin */ has_fin, /* masked */ nopoll_true, frame_type, length, (noPollPtr) content, sleep_in_header); } /* end if */ /* sending content as listener */ return nopoll_conn_send_frame (conn, /* fin */ has_fin, /* masked */ nopoll_false, frame_type, length, (noPollPtr) content, sleep_in_header); } /** * @brief Allows to send an UTF-8 text (op code 1) message over the * provided connection with the provided length. * * @param conn The connection where the message will be sent. * * @param content The content to be sent (it should be utf-8 content * or the function will fail). * * @param length Amount of bytes to take from the content to be * sent. If provided -1, it is assumed you are passing in a C-like * string nul terminated, so, that's the content to be sent. * * @return The number of bytes written otherwise < 0 is returned in * case of failure. The function will fail if some parameter is NULL * or undefined, or the content provided is not UTF-8. In the case of * failure, also check errno variable to know more what went wrong. * * See \ref nopoll_manual_retrying_write_operations to know more about error codes and when it is possible to retry write operations. */ int nopoll_conn_send_text (noPollConn * conn, const char * content, long length) { /* do a send common operation with FIN = 1 */ return __nopoll_conn_send_common (conn, content, length, nopoll_true, 0, NOPOLL_TEXT_FRAME); } /** * @brief Allows to send an UTF-8 text (op code 1) message over the * provided connection with the provided length but flagging the frame * sent as not complete (more frames to come, that is, FIN = 0). * * @param conn The connection where the message will be sent. * * @param content The content to be sent (it should be utf-8 content * or the function will fail). * * @param length Amount of bytes to take from the content to be * sent. If provided -1, it is assumed you are passing in a C-like * string nul terminated, so, that's the content to be sent. * * @return The number of bytes written otherwise < 0 is returned in * case of failure. The function will fail if some parameter is NULL * or undefined, or the content provided is not UTF-8. In the case of * failure, also check errno variable to know more what went wrong. * * See \ref nopoll_manual_retrying_write_operations to know more about * error codes and when it is possible to retry write operations. */ int nopoll_conn_send_text_fragment (noPollConn * conn, const char * content, long length) { /* do a send common operation with FIN = 0 */ return __nopoll_conn_send_common (conn, content, length, nopoll_false, 0, NOPOLL_TEXT_FRAME); } /** * @brief Allows to send a binary (op code 2) message over the * provided connection with the provided length. * * @param conn The connection where the message will be sent. * * @param content The content to be sent (it should be utf-8 content * or the function will fail). * * @param length Amount of bytes to take from the content to be * sent. Note you cannot pass in -1 (unlike \ref nopoll_conn_send_text). * * @return The number of bytes written otherwise < 0 is returned in * case of failure. The function will fail if some parameter is NULL * or undefined. In the case of failure, also check errno variable to * know more what went wrong. * * See \ref nopoll_manual_retrying_write_operations to know more about error codes and when it is possible to retry write operations. */ int nopoll_conn_send_binary (noPollConn * conn, const char * content, long length) { return __nopoll_conn_send_common (conn, content, length, nopoll_true, 0, NOPOLL_BINARY_FRAME); } /** * @brief Allows to send a binary (op code 2) message over the * provided connection with the provided length but flagging the frame * sent as not complete (more frames to come, that is, FIN = 0). * * @param conn The connection where the message will be sent. * * @param content The content to be sent (it should be utf-8 content * or the function will fail). * * @param length Amount of bytes to take from the content to be * sent. Note you cannot pass in -1 (unlike \ref * nopoll_conn_send_text). * * @return The number of bytes written otherwise < 0 is returned in * case of failure. The function will fail if some parameter is NULL * or undefined. In the case of failure, also check errno variable to * know more what went wrong. * * See \ref nopoll_manual_retrying_write_operations to know more about * error codes and when it is possible to retry write operations. */ int nopoll_conn_send_binary_fragment (noPollConn * conn, const char * content, long length) { return __nopoll_conn_send_common (conn, content, length, nopoll_true, 0, NOPOLL_BINARY_FRAME); } /** * @brief Allows to read the provided amount of bytes from the * provided connection, leaving the content read on the buffer * provided. * * Optionally, the function allows blocking the caller until the * amount of bytes requested are satisfied. Also, the function allows * to timeout the operation after provided amount of time. * * @param conn The connection where the read operation will take place. * * @param buffer The buffer where the result is returned. Memory * buffer must be enough to hold bytes requested and must be acquired * by the caller. * * @param bytes Number of bytes to be read from the connection. * * @param block If nopoll_true, the caller will be blocked until the * amount of bytes requested are satisfied or until the timeout is * reached (if enabled). If nopoll_false is provided, the function * won't block and will return all bytes available at this moment. * * @param timeout (milliseconds 1sec = 1000ms) If provided a value * higher than 0, a timeout will be enabled to complete the * operation. If the timeout is reached, the function will return the * bytes read so far. Please note that the function has a precision of * 10ms. * * @return Number of bytes read or -1 if it fails. The function * returns -1 when no content is available to be read and you pass * block == nopoll_false * * Note that the function doesn't clear the buffer received. Only * memory (bytes) notified by the value returned by this function * should be accessed by the caller. In the same direction you can't * use the buffer as a nul-terminated string because the function * doesn't add the final \0 to the content read. * */ int nopoll_conn_read (noPollConn * conn, char * buffer, int bytes, nopoll_bool block, long int timeout) { long int wait_slice = 0; noPollMsg * msg = NULL; struct timeval start; struct timeval stop; struct timeval diff; long ellapsed = 0; int desp = 0; int amount; int total_read = 0; int total_pending = 0; /* report error value */ if (conn == NULL || buffer == NULL || bytes <= 0) return -1; if (timeout > 1000) wait_slice = 100; else if (timeout > 100) wait_slice = 50; else if (timeout > 10) wait_slice = 10; if (timeout > 0) #if defined(NOPOLL_OS_WIN32) nopoll_win32_gettimeofday (&start, NULL); #else gettimeofday (&start, NULL); #endif /* clear the buffer */ memset (buffer, 0, bytes); /* check here if we have a pending message to read */ if (conn->pending_msg) { /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_read (found pending content: %d, requested %d)", conn->pending_diff, bytes); */ /* get references to pending data */ amount = conn->pending_diff; msg = conn->pending_msg; if (amount > bytes) { /* check if bytes requested are bigger the * conn->pending_diff */ if (bytes < conn->pending_diff) { conn->pending_diff -= bytes; } else { /* update values */ bytes = conn->pending_diff; conn->pending_diff = 0; } /* end if */ amount = bytes; } else { conn->pending_diff = 0; } /* read content */ memcpy (buffer, ((unsigned char *) nopoll_msg_get_payload (msg)) + conn->pending_desp, amount); total_read += amount; desp = amount; /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_read total amount satisfied is not %d, requested %d", total_read, bytes); */ /* increase pending desp */ conn->pending_desp += amount; /* now release internally the content if consumed the message */ if (conn->pending_diff == 0) { nopoll_msg_unref (conn->pending_msg); conn->pending_msg = NULL; } /* end if */ /* see if we have finished */ if (total_read == bytes || ! block) { if (total_read == 0 && ! block) return -1; return total_read; } /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "### ====> Read %d bytes from previous pending frame, requested %d. Pending diff %d", total_read, bytes, conn->pending_diff); */ } /* end if */ /* for for the content */ while (nopoll_true) { /* call to get next message */ msg = nopoll_conn_get_msg (conn); if (msg == NULL) { if (! nopoll_conn_is_ok (conn)) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received websocket conn-id=%d close during wait reply..", conn->id); if (total_read == 0 && ! block) return -1; return total_read; } /* end if */ if (! block) { if (total_read == 0 && ! block) return -1; return total_read; } /* end if */ } /* end if */ /* get the message content into the buffer */ if (msg) { /* get the amount of bytes we can read */ amount = nopoll_msg_get_payload_size (msg); total_pending = bytes - total_read; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "(New Frame) received %d bytes (pending requested %d bytes, desp: %d)", amount, total_pending, desp); if (amount > total_pending) { /* save here the difference between * what we have read and remaining data */ conn->pending_desp = total_pending; conn->pending_diff = amount - total_pending; conn->pending_msg = msg; amount = total_pending; /* acquire a reference to the message */ nopoll_msg_ref (msg); } /* end if */ /* copy data */ memcpy (buffer + desp, nopoll_msg_get_payload (msg), amount); total_read += amount; desp += amount; /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_read total amount satisfied is not %d, requested %d, desp: %d", total_read, bytes, desp); */ /* release message */ nopoll_msg_unref (msg); /* return amount read */ if (total_read == bytes || ! block) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Finishing nopoll_conn_read because block=%d or total bytes requested=%d satisfied=%d ", block, bytes, total_read); if (total_read == 0 && ! block) return -1; return total_read; } } /* check to stop due to timeout */ if (timeout > 0) { #if defined(NOPOLL_OS_WIN32) nopoll_win32_gettimeofday (&stop, NULL); #else gettimeofday (&stop, NULL); #endif nopoll_timeval_substract (&stop, &start, &diff); ellapsed = (diff.tv_sec * 1000) + (diff.tv_usec / 1000); if (ellapsed > (timeout)) break; } /* end if */ nopoll_sleep (wait_slice); } /* end while */ /* reached this point, return that timeout was reached */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Finishing nopoll_conn_read timeout reached=%ld ms , returning total bytes requested=%d satisfied=%d ", timeout, bytes, total_read); if (total_read == 0 && ! block) return -1; return total_read; } /** * @brief Allows to send a ping message over the Websocket connection * provided. The function will not block the caller. * * @param conn The connection where the PING operation will be sent. * * @return nopoll_true if the operation was sent without any error, * otherwise nopoll_false is returned. */ nopoll_bool nopoll_conn_send_ping (noPollConn * conn) { return nopoll_conn_send_frame (conn, nopoll_true, nopoll_false, NOPOLL_PING_FRAME, 0, NULL, 0); } /** * @brief Allows to configure an on message handler on the provided * connection that overrides the one configured at \ref noPollCtx. * * @param conn The connection to be configured with a particular on message handler. * * @param on_msg The on message handler configured. * * @param user_data User defined pointer to be passed in into the on message handler when it is called. * */ void nopoll_conn_set_on_msg (noPollConn * conn, noPollOnMessageHandler on_msg, noPollPtr user_data) { if (conn == NULL) return; /* configure on message handler */ conn->on_msg = on_msg; conn->on_msg_data = user_data; return; } /** * @brief Allows to configure a handler that is called when the * connection provided is ready to send and receive because all * WebSocket handshake protocol finished OK. * * Unlike handlers configured at \ref nopoll_ctx_set_on_open and \ref * nopoll_ctx_set_on_accept which get notified when the connection * isn't still working (because WebSocket handshake wasn't finished * yet), on read handlers configured here will get called just after * the WebSocket handshake has taken place. * * @param conn The connection to configure. * * @param on_ready The handler to be called when a connection is fully * ready to send and receive content because WebSocket handshake has * finished. The function must return nopoll_true to accept the * connection. By returning nopoll_false the handler is signalling to * terminate the connection. * * @param user_data Optional user data pointer passed to the on ready * handler. * */ void nopoll_conn_set_on_ready (noPollConn * conn, noPollActionHandler on_ready, noPollPtr user_data) { if (conn == NULL) return; /* set the handler */ conn->on_ready = on_ready; if (conn->on_ready == NULL) conn->on_ready_data = NULL; else conn->on_ready_data = user_data; return; } /** * @brief Allows to configure an OnClose handler that will be called * when the connection is closed. * * @param conn The connection to configure with the on close handle. * * @param on_close The handler to be configured. * * @param user_data A reference pointer to be passed in into the handler. */ void nopoll_conn_set_on_close (noPollConn * conn, noPollOnCloseHandler on_close, noPollPtr user_data) { if (conn == NULL) return; /* configure on close handler */ conn->on_close = on_close; conn->on_close_data = user_data; return; } /** * @internal Allows to send a pong message over the Websocket * connection provided. The function will not block the caller. This * function is not intended to be used by normal API consumer. * * @param conn The connection where the PING operation will be sent. * * @param nopoll_true if the operation was sent without any error, * otherwise nopoll_false is returned. */ nopoll_bool nopoll_conn_send_pong (noPollConn * conn) { return nopoll_conn_send_frame (conn, nopoll_true, nopoll_false, NOPOLL_PONG_FRAME, 0, NULL, 0); } /** * @brief Allows to call to complete last pending write process that may be * pending from a previous uncompleted write operation. The function * returns the number of bytes that were written. * * @param conn The connection where the pending write operation * operation will take place. In the case conn == NULL is received, 0 * is returned. Keep in mind this. * * @return In the case no pending write is in place, the function * returns 0. Otherwise, the function returns the pending bytes that * were written. The function returns -1 in the case of failure. * * You can call this function as many times as you want until you get * a 0. You can view it as a flush operation. */ int nopoll_conn_complete_pending_write (noPollConn * conn) { int bytes_written = 0; char * reference; int pending_bytes; if (conn == NULL || conn->pending_write == NULL) return 0; /* simple implementation */ bytes_written = conn->sends (conn, conn->pending_write, conn->pending_write_bytes); if (bytes_written == conn->pending_write_bytes) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Completed pending write operation with bytes=%d", bytes_written); nopoll_free (conn->pending_write); conn->pending_write = NULL; return bytes_written; } /* end if */ if (bytes_written > 0) { /* bytes written but not everything */ pending_bytes = conn->pending_write_bytes - bytes_written; reference = nopoll_new (char, pending_bytes); memcpy (reference, conn->pending_write + bytes_written, pending_bytes); nopoll_free (conn->pending_write); conn->pending_write = reference; return bytes_written; } nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Found complete write operation didn't finish well, result=%d, errno=%d, conn-id=%d", bytes_written, errno, conn->id); return bytes_written; } /** * @brief Allows to check if there are pending write bytes. The * function returns the number of pending write bytes that are waiting * to be flushed. To do so you must call \ref nopoll_conn_complete_pending_write. * * @param conn The connection to be checked to have pending bytes to be written. * * @return The number of bytes pending to be written. The function * also returns 0 when conn reference received is NULL. */ int nopoll_conn_pending_write_bytes (noPollConn * conn) { if (conn == NULL || conn->pending_write == NULL) return 0; return conn->pending_write_bytes; } /** * @brief Ready to use function that checks for pending write * operations and flush them waiting until they are done or until the * timeout provided by the user is reached. * * This function uses \ref nopoll_conn_pending_write_bytes and \ref * nopoll_conn_complete_pending_write to check and complete pending * write operations. * * Because writing pending bytes is a common operation, this function * is provided as a ready to use function to call after a write operation (for * example \ref nopoll_conn_send_text). * * @param conn The connection where pending bytes must be written. * * @param timeout Timeout in milliseconds to limit the flush operation. * * @param previous_result Optional parameter that can receive the * number of bytes optionally read before this call. The value * received on this function will be added to the result, checking * first it contains a value higher than 0. This is an option to * clarify the interface. If you don't have the value to be passed to * this function at the time needed, just pass 0. * * @return Bytes that were written. If no pending bytes must be written, the function returns 0. */ int nopoll_conn_flush_writes (noPollConn * conn, long timeout, int previous_result) { int iterator = 0; int bytes_written; int total = 0; int multiplier = 1; long wait_implemented = 0; /* check for errno and pending write operations */ if (errno != NOPOLL_EWOULDBLOCK || nopoll_conn_pending_write_bytes (conn) == 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "called flush but nothing is pending=%d or errno=%d isn't %d", nopoll_conn_pending_write_bytes (conn), errno, NOPOLL_EWOULDBLOCK); return previous_result > 0 ? previous_result : 0; } while (iterator < 100 && nopoll_conn_pending_write_bytes (conn) > 0) { /* stop operation if timeout reached */ if (wait_implemented >= timeout) break; nopoll_sleep (100000 * multiplier); wait_implemented += (100000 * multiplier); /* write content pending */ bytes_written = nopoll_conn_complete_pending_write (conn); if (bytes_written > 0) total += bytes_written; /* next position */ iterator++; multiplier++; } /* end while */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "finishing flush operation, total written=%d, added to previous result=%d", total, previous_result); /* add value received */ if (previous_result > 0) return total + previous_result; /* just bytes written */ return total; } /** * @internal Function used to send a frame over the provided * connection. * * @param conn The connection where the send operation will hapen. * * @param fin If the frame to be sent must be flagged as a fin frame. * * @param masked The frame to be sent is masked or not. * * @param op_code The frame op code to be configured. * * @param length The frame payload length. * * @param content Pointer to the data to be sent in the frame. */ int nopoll_conn_send_frame (noPollConn * conn, nopoll_bool fin, nopoll_bool masked, noPollOpCode op_code, long length, noPollPtr content, long sleep_in_header) { char header[14]; int header_size; char * send_buffer; int bytes_written = 0; char mask[4]; unsigned int mask_value = 0; int desp = 0; int tries; #if defined(SHOW_DEBUG_LOG) noPollDebugLevel level; #endif /* check for pending send operation */ if (nopoll_conn_complete_pending_write (conn) != 0) return -1; /* clear header */ memset (header, 0, 14); /* set header codes */ if (fin) nopoll_set_bit (header, 7); if (masked) { nopoll_set_bit (header + 1, 7); /* define a random mask */ #if defined(NOPOLL_OS_WIN32) mask_value = (unsigned int) rand (); #else mask_value = (unsigned int) os_random (); #endif memset (mask, 0, 4); nopoll_set_32bit (mask_value, mask); } /* end if */ if (op_code) { /* set initial 4 bits */ header[0] |= op_code & 0x0f; } /* set default header size */ header_size = 2; /* according to message length */ if (length < 126) { header[1] |= length; } else if (length < 65535) { /* set the next header length is at least 65535 */ header[1] |= 126; header_size += 2; /* set length into the next bytes */ nopoll_set_16bit (length, header + 2); #if defined(NOPOLL_64BIT_PLATFORM) } else if (length < 9223372036854775807ULL) { /* not supported yet */ return -1; #else } else { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to send the requested message, this requested is bigger than the value that can be supported by this platform (it should be < 65k)"); return -1; #endif } /* place mask */ if (masked) { nopoll_set_32bit (mask_value, header + header_size); header_size += 4; } /* end if */ /* allocate enough memory to send content */ send_buffer = nopoll_new (char, length + header_size + 2); if (send_buffer == NULL) { nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to allocate memory to implement send operation"); return -1; } /* end if */ /* copy content to be sent */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Copying into the buffer %d bytes of header (total memory allocated: %d)", header_size, (int) length + header_size + 1); memcpy (send_buffer, header, header_size); if (length > 0) { memcpy (send_buffer + header_size, content, length); /* mask content before sending if requested */ if (masked) { nopoll_conn_mask_content (conn->ctx, send_buffer + header_size, length, mask, 0); } } /* end if */ /* send content */ nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Mask used for this delivery: %d (about to send %d bytes)", nopoll_get_32bit (send_buffer + header_size - 2), (int) length + header_size); /* clear errno status before writting */ desp = 0; tries = 0; while (nopoll_true) { /* try to write bytes */ if (sleep_in_header == 0) { bytes_written = conn->sends (conn, send_buffer + desp, length + header_size - desp); } else { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Found sleep in header indication, sending header: %d bytes (waiting %ld)", header_size, sleep_in_header); bytes_written = conn->sends (conn, send_buffer, header_size); if (bytes_written == header_size) { /* sleep after header ... */ nopoll_sleep (sleep_in_header); /* now send the rest of the content (without the header) */ bytes_written = conn->sends (conn, send_buffer + header_size, length); nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Rest of content written %d (header size: %d, length: %d)", bytes_written, header_size, length); bytes_written = length + header_size; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "final bytes_written %d", bytes_written); } else { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Requested to write %d bytes for the header but %d were written", header_size, bytes_written); return -1; } /* end if */ } /* end if */ if ((bytes_written + desp) != (length + header_size)) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Requested to write %d bytes but found %d written (masked? %d, mask: %u, header size: %d, length: %d), errno = %d : %s", (int) length + header_size - desp, bytes_written, masked, mask_value, header_size, (int) length, errno, strerror (errno)); } else { /* accomulate bytes written to continue */ if (bytes_written > 0) desp += bytes_written; nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Bytes written to the wire %d (masked? %d, mask: %u, header size: %d, length: %d)", bytes_written, masked, mask_value, header_size, (int) length); break; } /* end if */ /* accomulate bytes written to continue */ if (bytes_written > 0) desp += bytes_written; /* increase tries */ tries++; if ((errno != 0) || tries > 50) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Found errno=%d (%s) value while trying to bytes to the WebSocket conn-id=%d or max tries reached=%d", errno, strerror (errno), conn->id, tries); break; } /* end if */ /* wait a bit */ nopoll_sleep (100000); } /* end while */ /* record pending write bytes */ conn->pending_write_bytes = length + header_size - desp; #if defined(SHOW_DEBUG_LOG) level = NOPOLL_LEVEL_DEBUG; if (desp != (length + header_size)) level = NOPOLL_LEVEL_CRITICAL; else if (errno == NOPOLL_EWOULDBLOCK && conn->pending_write_bytes > 0) level = NOPOLL_LEVEL_WARNING; nopoll_log (conn->ctx, level, "Write operation finished with with last result=%d, bytes_written=%d, requested=%d, remaining=%d (conn-id=%d)", /* report want we are going to report: result */ bytes_written <= 0 ? bytes_written : desp - header_size, /* bytes written */ desp - header_size, length, conn->pending_write_bytes, conn->id); #endif /* check pending bytes for the next operation */ if (conn->pending_write_bytes > 0) { conn->pending_write = nopoll_new (char, conn->pending_write_bytes); memcpy (conn->pending_write, send_buffer + length + header_size - conn->pending_write_bytes, conn->pending_write_bytes); nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Stored %d bytes starting from %d out of %d bytes (header size: %d)", conn->pending_write_bytes, length + header_size - conn->pending_write_bytes, length + header_size, header_size); } /* end if */ /* release memory */ nopoll_free (send_buffer); /* report at least what was written */ if (desp - header_size > 0) return desp - header_size; /* report last operation */ return bytes_written; } /** * @brief Allows to accept a new incoming WebSocket connection on the * provided listener. * * @param ctx The context where the operation will take place. * * @param listener The WebSocket listener that is receiving a new incoming connection. * * @return A newly created \ref noPollConn reference or NULL if it * fails. */ noPollConn * nopoll_conn_accept (noPollCtx * ctx, noPollConn * listener) { NOPOLL_SOCKET session; nopoll_return_val_if_fail (ctx, ctx && listener, NULL); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Calling to accept web socket connection over master id=%d, socket=%d", listener->id, listener->session); /* recevied a new connection: accept the * connection and ask the app level to accept * or not */ session = nopoll_listener_accept (listener->session); if (session == NOPOLL_INVALID_SOCKET) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received invalid socket value from accept(2): %d, error code errno=: %d", session, errno); return NULL; } /* end if */ return nopoll_conn_accept_socket (ctx, listener, session); } /** * @brief Allows to accept a new incoming WebSocket connection on the * provided listener but with a socket already accepted. * * @param ctx The context where the operation will take place. * * @param listener The WebSocket listener that is receiving a new incoming connection. * * @param session An already accepted socket from the provided * listener. * * @return A newly created \ref noPollConn reference or NULL if it * fails. */ noPollConn * nopoll_conn_accept_socket (noPollCtx * ctx, noPollConn * listener, NOPOLL_SOCKET session) { noPollConn * conn; nopoll_return_val_if_fail (ctx, ctx && listener, NULL); /* create the connection */ conn = nopoll_listener_from_socket (ctx, session); if (conn == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received NULL pointer after calling to create listener from session.."); return NULL; } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Accepted new WebSocket conn-id=%d, socket=%d, over master id=%d, socket=%d", conn->id, conn->session, listener->id, listener->session); /* configure the listener reference that accepted this * connection */ conn->listener = listener; if (! nopoll_conn_accept_complete (ctx, listener, conn, session, listener->tls_on)) return NULL; /* report listener created */ return conn; } /** * @internal Function to support accept listener operations. */ nopoll_bool __nopoll_conn_accept_complete_common (noPollCtx * ctx, noPollConnOpts * options, noPollConn * listener, noPollConn * conn, NOPOLL_SOCKET session, nopoll_bool tls_on) { const char * certificateFile = NULL; const char * privateKey = NULL; const char * chainCertificate = NULL; const char * serverName = NULL; /* check input parameters */ if (! (ctx && listener && conn && session != NOPOLL_INVALID_SOCKET)) { nopoll_conn_shutdown (conn); nopoll_ctx_unregister_conn (ctx, conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* end if */ /* configure non blocking mode */ nopoll_conn_set_sock_block (session, nopoll_true); /* now check for accept handler */ if (ctx->on_accept) { /* call to on accept */ if (! ctx->on_accept (ctx, conn, ctx->on_accept_data)) { nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "Application level denied accepting connection from %s:%s, closing", conn->host, conn->port); nopoll_conn_shutdown (conn); nopoll_ctx_unregister_conn (ctx, conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* end if */ } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Connection received and accepted from %s:%s (conn refs: %d, ctx refs: %d)", listener->host, listener->port, listener->refs, ctx->refs); if (listener->tls_on || tls_on) { /* reached this point, ensure tls is enabled on this * session */ conn->tls_on = nopoll_true; /* get here SNI to query about the serverName */ /* 1) GET FROM OPTIONS: detect here if we have * certificates provided through options */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Starting TLS process, options=%p, listener=%p", options, listener); if (options) { certificateFile = options->certificate; privateKey = options->private_key; } /* end if */ if (certificateFile == NULL || privateKey == NULL) { /* 2) GET FROM LISTENER: get references to currently configured certificate file */ certificateFile = listener->certificate; privateKey = listener->private_key; if (certificateFile == NULL || privateKey == NULL) { /* 3) GET FROM STORE: check if the * certificate is already installed */ nopoll_ctx_find_certificate (ctx, serverName, &certificateFile, &privateKey, &chainCertificate); } } /* end if */ /* check certificates and private key */ if (certificateFile == NULL || privateKey == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to accept secure web socket connection, certificate file %s and/or key file %s isn't defined", certificateFile ? certificateFile : "", privateKey ? privateKey : ""); nopoll_conn_shutdown (conn); nopoll_ctx_unregister_conn (ctx, conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* end if */ /* init ssl ciphers and engines */ if (! __nopoll_tls_was_init) { __nopoll_tls_was_init = nopoll_true; SSL_library_init (); } /* end if */ /* now configure chainCertificate */ if (listener->chain_certificate) chainCertificate = listener->chain_certificate; else if (options && options->chain_certificate) chainCertificate = options->chain_certificate; /* create ssl context */ conn->ssl_ctx = __nopoll_conn_get_ssl_context (ctx, conn, listener->opts, nopoll_false); /* Configure ca certificate in the case it is defined */ if (options && options->ca_certificate) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting up CA certificate: %s", options->ca_certificate); if (SSL_CTX_load_verify_locations (conn->ssl_ctx, options->ca_certificate, NULL) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure CA certificate (%s), SSL_CTX_load_verify_locations () failed", options->ca_certificate); return nopoll_false; } /* end if */ } /* end if */ /* enable default verification paths */ if (SSL_CTX_set_default_verify_paths (conn->ssl_ctx) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to configure default verification paths, SSL_CTX_set_default_verify_paths () failed"); return nopoll_false; } /* end if */ /* configure chain certificate */ if (chainCertificate) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting up chain certificate: %s", chainCertificate); if (SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, chainCertificate) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure chain certificate (%s), SSL_CTX_use_certificate_chain_file () failed", chainCertificate); return nopoll_false; } /* end if */ } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Using certificate file: %s (with ssl context ref: %p)", certificateFile, conn->ssl_ctx); if (conn->ssl_ctx == NULL || SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, certificateFile) != 1) { /* drop an error log */ if (conn->ssl_ctx == NULL) nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to accept incoming connection, failed to create SSL context. Context creator returned NULL pointer"); else nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "there was an error while setting certificate file into the SSl context, unable to start TLS profile. Failure found at SSL_CTX_use_certificate_file function. Tried certificate file: %s", certificateFile); /* dump error stack */ nopoll_conn_shutdown (conn); nopoll_ctx_unregister_conn (ctx, conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* end if */ nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Using certificate key: %s", privateKey); if (SSL_CTX_use_PrivateKey_file (conn->ssl_ctx, privateKey, SSL_FILETYPE_PEM) != 1) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "there was an error while setting private file into the SSl context, unable to start TLS profile. Failure found at SSL_CTX_use_PrivateKey_file function. Tried private file: %s", privateKey); /* dump error stack */ nopoll_conn_shutdown (conn); nopoll_ctx_unregister_conn (ctx, conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* check for private key and certificate file to match. */ if (! SSL_CTX_check_private_key (conn->ssl_ctx)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "seems that certificate file and private key doesn't match!, unable to start TLS profile. Failure found at SSL_CTX_check_private_key function. Used certificate %s, and key: %s", certificateFile, privateKey); /* dump error stack */ nopoll_conn_shutdown (conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* end if */ if (options != NULL && ! options->disable_ssl_verify) { nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Enabling certificate client peer verification from server"); /** really, really ugly hack to let * __nopoll_conn_ssl_verify_callback to be able to get * access to the context required to drop some logs */ __nopoll_conn_ssl_ctx_debug = ctx; SSL_CTX_set_verify (conn->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, __nopoll_conn_ssl_verify_callback); SSL_CTX_set_verify_depth (conn->ssl_ctx, 5); } /* end if */ /* create SSL context */ conn->ssl = (SSL*)SSL_new (conn->ssl_ctx); if (conn->ssl == NULL) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "error while creating TLS transport, SSL_new (%p) returned NULL", conn->ssl_ctx); nopoll_conn_shutdown (conn); nopoll_ctx_unregister_conn (ctx, conn); /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_false; } /* end if */ /* set the file descriptor */ SSL_set_fd (conn->ssl, conn->session); /* don't complete here the operation but flag it as * pending */ conn->pending_ssl_accept = nopoll_true; nopoll_conn_set_sock_block (conn->session, nopoll_false); nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Prepared TLS session to be activated on next reads (conn id %d)", conn->id); } /* end if */ /* release connection options */ __nopoll_conn_opts_release_if_needed (options); return nopoll_true; } /** * @brief Allows to complete accept operation by setting up all I/O * handlers required to make the WebSocket connection to work. * * @param ctx The context where the operation takes place. * * @param listener The listener where the connection was accepted. * * @param conn The connection that was accepted. * * @param session The socket associated to the listener accepted. * * @param tls_on A boolean indication if the TLS interface should be * enabled or not. * * @return nopoll_true if the listener was accepted otherwise nopoll_false is returned. */ nopoll_bool nopoll_conn_accept_complete (noPollCtx * ctx, noPollConn * listener, noPollConn * conn, NOPOLL_SOCKET session, nopoll_bool tls_on) { if (listener->opts) { if (! nopoll_conn_opts_ref (listener->opts)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to acquire a reference to the connection option at nopoll_conn_accept_complete() function nopoll_conn_opts_ref () failed.."); return nopoll_false; } /* end if */ } /* end if */ return __nopoll_conn_accept_complete_common (ctx, listener->opts, listener, conn, session, tls_on); } /** * @brief Allows to implement a wait operation until the provided * connection is ready or the provided timeout is reached. * * @param conn The connection that is being waited to be created. * * @param timeout The timeout operation to limit the wait * operation. Timeout is provided in seconds. * * @return The function returns when the timeout was reached or the * connection is ready. In the case the connection is ready when the * function finished nopoll_true is returned, otherwise nopoll_false. */ nopoll_bool nopoll_conn_wait_until_connection_ready (noPollConn * conn, int timeout) { long int total_timeout = timeout * 1000000; /* check if the connection already finished its connection handshake */ while (! nopoll_conn_is_ready (conn) && total_timeout > 0) { /* check if the connection is ok */ if (! nopoll_conn_is_ok (conn)) return nopoll_false; /* wait a bit 0,5ms */ nopoll_sleep (500); /* reduce the amount of time we have to wait */ total_timeout = total_timeout - 500; } /* end if */ /* report if the connection is ok */ return nopoll_conn_is_ok (conn) && nopoll_conn_is_ready (conn); } /* @} */