add websocket transport to sofia

This commit is contained in:
Anthony Minessale 2013-01-24 14:13:45 -06:00
parent a70aa8f9be
commit 62e62e1e34
9 changed files with 1212 additions and 10 deletions

View File

@ -234,6 +234,8 @@ struct nta_agent_s
unsigned sa_tport_tcp : 1; /**< Transports support TCP. */
unsigned sa_tport_sctp : 1; /**< Transports support SCTP. */
unsigned sa_tport_tls : 1; /**< Transports support TLS. */
unsigned sa_tport_ws : 1; /**< Transports support WS. */
unsigned sa_tport_wss : 1; /**< Transports support WSS. */
unsigned sa_use_naptr : 1; /**< Use NAPTR lookup */
unsigned sa_use_srv : 1; /**< Use SRV lookup */
@ -2045,22 +2047,24 @@ struct sipdns_tport {
char prefix[14]; /**< Prefix for SRV domains */
char service[10]; /**< NAPTR service */
}
#define SIPDNS_TRANSPORTS (4)
#define SIPDNS_TRANSPORTS (6)
const sipdns_tports[SIPDNS_TRANSPORTS] = {
{ "udp", "5060", "_sip._udp.", "SIP+D2U" },
{ "tcp", "5060", "_sip._tcp.", "SIP+D2T" },
{ "sctp", "5060", "_sip._sctp.", "SIP+D2S" },
{ "tls", "5061", "_sips._tcp.", "SIPS+D2T" },
{ "sctp", "5060", "_sip._sctp.", "SIP+D2S" },
{ "tls", "5061", "_sips._tcp.", "SIPS+D2T" },
{ "ws", "80", "_sips._ws.", "SIP+D2W" },
{ "wss", "443", "_sips._wss.", "SIPS+D2W" },
};
static char const * const tports_sip[] =
{
"udp", "tcp", "sctp", NULL
"udp", "tcp", "sctp", "ws", NULL
};
static char const * const tports_sips[] =
{
"tls", NULL
"tls", "ws", NULL
};
static tport_stack_class_t nta_agent_class[1] =
@ -2188,7 +2192,7 @@ int nta_agent_add_tport(nta_agent_t *self,
if (url->url_params) {
if (url_param(url->url_params, "transport", tp, sizeof(tp)) > 0) {
if (strchr(tp, ',')) {
int i; char *t, *tps[9];
int i; char *t, *tps[9] = {0};
/* Split tp into transports */
for (i = 0, t = tp; t && i < 8; i++) {
@ -2311,6 +2315,8 @@ int agent_init_via(nta_agent_t *self, tport_t *primaries, int use_maddr)
self->sa_tport_tcp = 0;
self->sa_tport_sctp = 0;
self->sa_tport_tls = 0;
self->sa_tport_ws = 0;
self->sa_tport_wss = 0;
/* Set via fields for the tports */
for (tp = primaries; tp; tp = tport_next(tp)) {
@ -2343,6 +2349,10 @@ int agent_init_via(nta_agent_t *self, tport_t *primaries, int use_maddr)
self->sa_tport_tcp = 1;
else if (su_casematch(tpn->tpn_proto, "sctp"))
self->sa_tport_sctp = 1;
else if (su_casematch(tpn->tpn_proto, "ws"))
self->sa_tport_ws = 1;
else if (su_casematch(tpn->tpn_proto, "wss"))
self->sa_tport_wss = 1;
if (tport_has_tls(tp)) self->sa_tport_tls = 1;
@ -2684,8 +2694,12 @@ nta_tpn_by_url(su_home_t *home,
tpn->tpn_ident = NULL;
if (tpn->tpn_proto)
if (tpn->tpn_proto) {
if (su_casematch(url->url_scheme, "sips") && su_casematch(tpn->tpn_proto, "ws")) {
tpn->tpn_proto = "wss";
}
return 1;
}
if (su_casematch(url->url_scheme, "sips"))
tpn->tpn_proto = "tls";

View File

@ -44,7 +44,7 @@ if HAVE_STUN
USE_STUN_SRC = $(STUN_SRC)
endif
HTTP_SRC = tport_type_connect.c
HTTP_SRC = tport_type_connect.c tport_type_ws.c ws.c
if HAVE_NTH
USE_HTTP_SRC = $(HTTP_SRC)
endif

View File

@ -202,7 +202,7 @@ int tport_is_registered(tport_t const *self)
/** Test if transport is stream. */
int tport_is_stream(tport_t const *self)
{
return self && self->tp_addrinfo->ai_socktype == SOCK_STREAM;
return self && !self->tp_pre_framed && self->tp_addrinfo->ai_socktype == SOCK_STREAM;
}
/** Test if transport is dgram. */
@ -1345,10 +1345,12 @@ int tport_set_params(tport_t *self,
extern tport_vtable_t const tport_udp_vtable;
extern tport_vtable_t const tport_tcp_vtable;
extern tport_vtable_t const tport_tls_vtable;
extern tport_vtable_t const tport_ws_vtable;
extern tport_vtable_t const tport_sctp_vtable;
extern tport_vtable_t const tport_udp_client_vtable;
extern tport_vtable_t const tport_tcp_client_vtable;
extern tport_vtable_t const tport_sctp_client_vtable;
extern tport_vtable_t const tport_ws_client_vtable;
extern tport_vtable_t const tport_tls_client_vtable;
extern tport_vtable_t const tport_http_connect_vtable;
extern tport_vtable_t const tport_threadpool_vtable;
@ -1359,6 +1361,8 @@ tport_vtable_t const *tport_vtables[TPORT_NUMBER_OF_TYPES + 1] =
{
#if HAVE_SOFIA_NTH
&tport_http_connect_vtable,
&tport_ws_client_vtable,
&tport_ws_vtable,
#endif
#if HAVE_TLS
&tport_tls_client_vtable,
@ -2426,6 +2430,13 @@ int getprotohints(su_addrinfo_t *hints,
proto = "tcp";
#endif
#if HAVE_SOFIA_NTH
if (su_casematch(proto, "ws"))
proto = "tcp";
if (su_casematch(proto, "wss"))
proto = "tcp";
#endif
#if HAVE_SCTP
if (su_casematch(proto, "sctp")) {
hints->ai_protocol = IPPROTO_SCTP;
@ -2869,7 +2880,7 @@ void tport_recv_event(tport_t *self)
}
if (again >= 0)
tport_parse(self, !again, self->tp_rtime);
tport_parse(self, self->tp_pre_framed ? 1 : !again, self->tp_rtime);
}
while (again > 1);

View File

@ -161,6 +161,7 @@ struct tport_s {
unsigned tp_trunc:1;
unsigned tp_is_connected:1; /**< Connection is established */
unsigned tp_verified:1; /**< Certificate Chain was verified */
unsigned tp_pre_framed:1; /** Data is pre-framed **/
unsigned:0;
tport_t *tp_left, *tp_right, *tp_dad; /**< Links in tport tree */
@ -527,6 +528,10 @@ void tport_recv_timeout_timer(tport_t *self, su_time_t now);
int tport_next_keepalive(tport_t *self, su_time_t *, char const **);
void tport_keepalive_timer(tport_t *self, su_time_t now);
extern tport_vtable_t const tport_ws_vtable;
extern tport_vtable_t const tport_ws_client_vtable;
extern tport_vtable_t const tport_wss_vtable;
extern tport_vtable_t const tport_wss_client_vtable;
extern tport_vtable_t const tport_sctp_vtable;
extern tport_vtable_t const tport_sctp_client_vtable;
extern tport_vtable_t const tport_tls_vtable;

View File

@ -382,6 +382,8 @@ void tport_capt_msg(tport_t const *self, msg_t *msg, size_t n,
if(strcmp(self->tp_name->tpn_proto, "tcp") == 0) hep_header.hp_p = IPPROTO_TCP;
else if(strcmp(self->tp_name->tpn_proto, "tls") == 0) hep_header.hp_p = IPPROTO_IDP; /* FAKE*/
else if(strcmp(self->tp_name->tpn_proto, "sctp") == 0) hep_header.hp_p = IPPROTO_SCTP;
else if(strcmp(self->tp_name->tpn_proto, "ws") == 0) hep_header.hp_p = IPPROTO_TCP;
else if(strcmp(self->tp_name->tpn_proto, "wss") == 0) hep_header.hp_p = IPPROTO_TCP;
else hep_header.hp_p = IPPROTO_UDP; /* DEFAULT UDP */
/* Check destination */

View File

@ -0,0 +1,413 @@
/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2006 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@CFILE tport_type_ws.c WS Transport
*
* See tport.docs for more detailed description of tport interface.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
* @author Martti Mela <Martti.Mela@nokia.com>
*
* @date Created: Fri Mar 24 08:45:49 EET 2006 ppessi
*/
#include "config.h"
#include "tport_internal.h"
#include "tport_ws.h"
#if HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifndef SOL_TCP
#define SOL_TCP IPPROTO_TCP
#endif
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
static char const __func__[] = "tport_type_ws";
#endif
/* ---------------------------------------------------------------------- */
/* WS */
#include <sofia-sip/http.h>
#include <sofia-sip/http_header.h>
tport_vtable_t const tport_ws_vtable =
{
/* vtp_name */ "ws",
/* vtp_public */ tport_type_local,
/* vtp_pri_size */ sizeof (tport_primary_t),
/* vtp_init_primary */ tport_ws_init_primary,
/* vtp_deinit_primary */ NULL,
/* vtp_wakeup_pri */ tport_accept,
/* vtp_connect */ NULL,
/* vtp_secondary_size */ sizeof (tport_ws_t),
/* vtp_init_secondary */ tport_ws_init_secondary,
/* vtp_deinit_secondary */ NULL,
/* vtp_shutdown */ NULL,
/* vtp_set_events */ NULL,
/* vtp_wakeup */ NULL,
/* vtp_recv */ tport_recv_stream_ws,
/* vtp_send */ tport_send_stream_ws,
/* vtp_deliver */ NULL,
/* vtp_prepare */ NULL,
/* vtp_keepalive */ NULL,
/* vtp_stun_response */ NULL,
/* vtp_next_secondary_timer*/ tport_ws_next_timer,
/* vtp_secondary_timer */ tport_ws_timer,
};
tport_vtable_t const tport_ws_client_vtable =
{
/* vtp_name */ "ws",
/* vtp_public */ tport_type_client,
/* vtp_pri_size */ sizeof (tport_primary_t),
/* vtp_init_primary */ tport_ws_init_client,
/* vtp_deinit_primary */ NULL,
/* vtp_wakeup_pri */ NULL,
/* vtp_connect */ NULL,
/* vtp_secondary_size */ sizeof (tport_ws_t),
/* vtp_init_secondary */ tport_ws_init_secondary,
/* vtp_deinit_secondary */ NULL,
/* vtp_shutdown */ NULL,
/* vtp_set_events */ NULL,
/* vtp_wakeup */ NULL,
/* vtp_recv */ tport_recv_stream_ws,
/* vtp_send */ tport_send_stream_ws,
/* vtp_deliver */ NULL,
/* vtp_prepare */ NULL,
/* vtp_keepalive */ NULL,
/* vtp_stun_response */ NULL,
/* vtp_next_secondary_timer*/ tport_ws_next_timer,
/* vtp_secondary_timer */ tport_ws_timer,
};
static int tport_ws_setsndbuf(int socket, int atleast);
/** Receive from stream.
*
* @retval -1 error
* @retval 0 end-of-stream
* @retval 1 normal receive
* @retval 2 incomplete recv, recv again
*
*/
int tport_recv_stream_ws(tport_t *self)
{
msg_t *msg;
ssize_t n, N, veclen, i, m;
int err;
msg_iovec_t iovec[msg_n_fragments] = {{ 0 }};
tport_ws_t *wstp = (tport_ws_t *)self;
wsh_t *ws = wstp->ws;
uint8_t *data;
ws_opcode_t oc;
if ( !wstp->ws_initialized ) {
ws_init(ws, self->tp_socket, 65336, wstp->ws_secure);
wstp->ws_initialized = 1;
self->tp_pre_framed = 1;
return 1;
}
N = ws_read_frame(ws, &oc, &data);
if (N == -1000) {
if (self->tp_msg)
msg_recv_commit(self->tp_msg, 0, 1);
return 0; /* End of stream */
}
if (N < 0) {
err = su_errno();
SU_DEBUG_1(("%s(%p): su_getmsgsize(): %s (%d)\n", __func__, (void *)self,
su_strerror(err), err));
return -1;
}
veclen = tport_recv_iovec(self, &self->tp_msg, iovec, N, 0);
if (veclen < 0)
return -1;
msg = self->tp_msg;
msg_set_address(msg, self->tp_addr, self->tp_addrlen);
for (i = 0, n = 0; i < veclen; i++) {
m = iovec[i].mv_len; assert(N >= n + m);
memcpy(iovec[i].mv_base, data + n, m);
n += m;
}
assert(N == n);
/* Write the received data to the message dump file */
if (self->tp_master->mr_dump_file)
tport_dump_iovec(self, msg, n, iovec, veclen, "recv", "from");
/* Mark buffer as used */
msg_recv_commit(msg, N, 0);
return 1;
}
/** Send to stream */
ssize_t tport_send_stream_ws(tport_t const *self, msg_t *msg,
msg_iovec_t iov[],
size_t iovlen)
{
size_t i, j, n, m, size = 0;
ssize_t nerror;
tport_ws_t *wstp = (tport_ws_t *)self;
wsh_t *ws = wstp->ws;
char xbuf[65536] = "";
int blen = 0;
enum { WSBUFSIZE = 2048 };
for (i = 0; i < iovlen; i = j) {
char *buf = wstp->wstp_buffer;
unsigned wsbufsize = WSBUFSIZE;
if (i + 1 == iovlen) {
buf = NULL; /* Don't bother copying single chunk */
}
if (buf &&
(char *)iov[i].siv_base - buf < WSBUFSIZE &&
(char *)iov[i].siv_base - buf >= 0) {
wsbufsize = buf + WSBUFSIZE - (char *)iov[i].siv_base;
assert(wsbufsize <= WSBUFSIZE);
}
for (j = i, m = 0; buf && j < iovlen; j++) {
if (m + iov[j].siv_len > wsbufsize) {
break;
}
if (buf + m != iov[j].siv_base) {
memcpy(buf + m, iov[j].siv_base, iov[j].siv_len);
}
m += iov[j].siv_len; iov[j].siv_len = 0;
}
if (j == i) {
buf = iov[i].siv_base, m = iov[i].siv_len, j++;
} else {
iov[j].siv_base = buf, iov[j].siv_len = m;
}
//* hacked to push to buffer
if (blen + m < sizeof(xbuf)) {
memcpy(xbuf+blen, buf, m);
nerror = m;
blen += m;
} else {
nerror = -1;
}
//*/
//nerror = ws_write_frame(ws, WSOC_TEXT, buf, m);
SU_DEBUG_9(("tport_ws_writevec: vec %p %p %lu ("MOD_ZD")\n",
(void *)ws, (void *)iov[i].siv_base, (LU)iov[i].siv_len,
nerror));
if (nerror == -1) {
int err = su_errno();
if (su_is_blocking(err))
break;
SU_DEBUG_3(("ws_write: %s\n", strerror(err)));
return -1;
}
n = (size_t)nerror;
size += n;
/* Return if the write buffer is full for now */
if (n != m)
break;
}
//* hacked ....
if (size) {
size = ws_write_frame(ws, WSOC_TEXT, xbuf, blen);
}
//*/
return size;
}
int tport_ws_init_primary(tport_primary_t *pri,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t const *tags,
char const **return_culprit)
{
int socket;
socket = su_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (socket == INVALID_SOCKET)
return *return_culprit = "socket", -1;
tport_ws_setsndbuf(socket, 64 * 1024);
return tport_stream_init_primary(pri, socket, tpn, ai, tags, return_culprit);
}
int tport_ws_init_client(tport_primary_t *pri,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t const *tags,
char const **return_culprit)
{
pri->pri_primary->tp_conn_orient = 1;
return 0;
}
int tport_ws_init_secondary(tport_t *self, int socket, int accepted,
char const **return_reason)
{
int one = 1;
self->tp_has_connection = 1;
if (setsockopt(socket, SOL_TCP, TCP_NODELAY, (void *)&one, sizeof one) == -1)
return *return_reason = "TCP_NODELAY", -1;
if (!accepted)
tport_ws_setsndbuf(socket, 64 * 1024);
return 0;
}
static int tport_ws_setsndbuf(int socket, int atleast)
{
#if SU_HAVE_WINSOCK2
/* Set send buffer size to something reasonable on windows */
int size = 0;
socklen_t sizelen = sizeof size;
if (getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (void *)&size, &sizelen) < 0)
return -1;
if (sizelen != sizeof size)
return su_seterrno(EINVAL);
if (size >= atleast)
return 0; /* OK */
return setsockopt(socket, SOL_SOCKET, SO_SNDBUF,
(void *)&atleast, sizeof atleast);
#else
return 0;
#endif
}
/** Send PING */
int tport_ws_ping(tport_t *self, su_time_t now)
{
ssize_t n;
char *why = "";
if (tport_has_queued(self))
return 0;
n = send(self->tp_socket, "\r\n\r\n", 4, 0);
if (n > 0)
self->tp_ktime = now;
if (n == 4) {
if (self->tp_ptime.tv_sec == 0)
self->tp_ptime = now;
}
else if (n == -1) {
int error = su_errno();
why = " failed";
if (!su_is_blocking(error))
tport_error_report(self, error, NULL);
else
why = " blocking";
return -1;
}
SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n",
__func__, (void *)self,
"sending PING", TPN_ARGS(self->tp_name), why));
return n == -1 ? -1 : 0;
}
/** Send pong */
int tport_ws_pong(tport_t *self)
{
self->tp_ping = 0;
if (tport_has_queued(self) || !self->tp_params->tpp_pong2ping)
return 0;
SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n",
__func__, (void *)self,
"sending PONG", TPN_ARGS(self->tp_name), ""));
return send(self->tp_socket, "\r\n", 2, 0);
}
/** Calculate next timer for WS. */
int tport_ws_next_timer(tport_t *self,
su_time_t *return_target,
char const **return_why)
{
return
tport_next_recv_timeout(self, return_target, return_why) |
tport_next_keepalive(self, return_target, return_why);
}
/** WS timer. */
void tport_ws_timer(tport_t *self, su_time_t now)
{
tport_recv_timeout_timer(self, now);
tport_keepalive_timer(self, now);
tport_base_timer(self, now);
}

View File

@ -0,0 +1,88 @@
/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#ifndef TPORT_WS_H
/** Defined when <tport_ws.h> has been included. */
#define TPORT_WS_H
/**@internal
* @file tport_ws.h
* @brief Internal WS interface
*
* @author Mike Jerris <mike@jerris.com>
*
* Copyright 2013 Michael Jerris. All rights reserved.
*
*/
#ifndef SU_TYPES_H
#include <sofia-sip/su_types.h>
#endif
#include "tport_internal.h"
#include "ws.h"
SOFIA_BEGIN_DECLS
typedef enum {
TPORT_WS_OPCODE_CONTINUATION = 0x0,
TPORT_WS_OPCODE_TEXT = 0x1,
TPORT_WS_OPCODE_BINARY = 0x2,
TPORT_WS_OPCODE_CLOSE = 0x8,
TPORT_WS_OPCODE_PING = 0x9,
TPORT_WS_OPCODE_PONG = 0xA
} tport_ws_opcode_t;
typedef struct tport_ws_s {
tport_t wstp_tp[1];
wsh_t ws[1];
char *wstp_buffer;
unsigned ws_initialized:1;
unsigned ws_secure:1;
unsigned:0;
} tport_ws_t;
int tport_recv_stream_ws(tport_t *self);
ssize_t tport_send_stream_ws(tport_t const *self, msg_t *msg,
msg_iovec_t iov[], size_t iovused);
int tport_ws_ping(tport_t *self, su_time_t now);
int tport_ws_pong(tport_t *self);
int tport_ws_init_primary(tport_primary_t *,
tp_name_t tpn[1],
su_addrinfo_t *, tagi_t const *,
char const **return_culprit);
int tport_ws_init_client(tport_primary_t *,
tp_name_t tpn[1],
su_addrinfo_t *, tagi_t const *,
char const **return_culprit);
int tport_ws_init_secondary(tport_t *self, int socket, int accepted,
char const **return_reason);
int tport_ws_next_timer(tport_t *self, su_time_t *, char const **);
void tport_ws_timer(tport_t *self, su_time_t);
SOFIA_END_DECLS
#endif

View File

@ -0,0 +1,585 @@
#include "ws.h"
#define SHA1_HASH_SIZE 20
struct globals_s globals;
#ifndef PTHREAD
void ssl_init() {}
#else
static unsigned long pthreads_thread_id(void);
static void pthreads_locking_callback(int mode, int type, const char *file, int line);
static pthread_mutex_t *lock_cs;
static long *lock_count;
static void thread_setup(void)
{
int i;
lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
for (i = 0; i < CRYPTO_num_locks(); i++) {
lock_count[i] = 0;
pthread_mutex_init(&(lock_cs[i]), NULL);
}
CRYPTO_set_id_callback(pthreads_thread_id);
CRYPTO_set_locking_callback(pthreads_locking_callback);
}
static void thread_cleanup(void)
{
int i;
CRYPTO_set_locking_callback(NULL);
for (i=0; i<CRYPTO_num_locks(); i++) {
pthread_mutex_destroy(&(lock_cs[i]));
}
OPENSSL_free(lock_cs);
OPENSSL_free(lock_count);
}
static void pthreads_locking_callback(int mode, int type, const char *file, int line)
{
if (mode & CRYPTO_LOCK) {
pthread_mutex_lock(&(lock_cs[type]));
lock_count[type]++;
} else {
pthread_mutex_unlock(&(lock_cs[type]));
}
}
static unsigned long pthreads_thread_id(void)
{
return (unsigned long) pthread_self();
}
void init_ssl(void) {
SSL_library_init();
// OpenSSL_add_all_algorithms(); /* load & register cryptos */
// SSL_load_error_strings(); /* load all error messages */
globals.ssl_method = SSLv23_server_method(); /* create server instance */
globals.ssl_ctx = SSL_CTX_new(globals.ssl_method); /* create context */
assert(globals.ssl_ctx);
/* set the local certificate from CertFile */
SSL_CTX_use_certificate_file(globals.ssl_ctx, globals.cert, SSL_FILETYPE_PEM);
/* set the private key from KeyFile */
SSL_CTX_use_PrivateKey_file(globals.ssl_ctx, globals.key, SSL_FILETYPE_PEM);
/* verify private key */
if ( !SSL_CTX_check_private_key(globals.ssl_ctx) ) {
abort();
}
thread_setup();
}
void deinit_ssl(void) {
thread_cleanup();
}
#endif
static const char c64[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int cheezy_get_var(char *data, char *name, char *buf, size_t buflen)
{
char *p=data;
/* the old way didnt make sure that variable values were used for the name hunt
* and didnt ensure that only a full match of the variable name was used
*/
do {
if(!strncmp(p,name,strlen(name)) && *(p+strlen(name))==':') break;
} while((p = (strstr(p,"\n")+1))!=(char *)1);
if (p != (char *)1 && *p!='\0') {
char *v, *e;
if ((v = strchr(p, ':'))) {
v++;
while(v && *v == ' ') {
v++;
}
if (v) {
if (!(e = strchr(v, '\r'))) {
e = strchr(v, '\n');
}
}
if (v && e) {
int cplen;
int len = e - v;
if (len > buflen - 1) {
cplen = buflen -1;
} else {
cplen = len;
}
strncpy(buf, v, cplen);
*(buf+cplen) = '\0';
return 1;
}
}
}
return 0;
}
static int b64encode(unsigned char *in, size_t ilen, unsigned char *out, size_t olen)
{
int y=0,bytes=0;
size_t x=0;
unsigned int b=0,l=0;
for(x=0;x<ilen;x++) {
b = (b<<8) + in[x];
l += 8;
while (l >= 6) {
out[bytes++] = c64[(b>>(l-=6))%64];
if(++y!=72) {
continue;
}
//out[bytes++] = '\n';
y=0;
}
}
if (l > 0) {
out[bytes++] = c64[((b%16)<<(6-l))%64];
}
if (l != 0) while (l < 6) {
out[bytes++] = '=', l += 2;
}
return 0;
}
#ifdef NO_OPENSSL
static void sha1_digest(char *digest, unsigned char *in)
{
SHA1Context sha;
char *p;
int x;
SHA1Init(&sha);
SHA1Update(&sha, in, strlen(in));
SHA1Final(&sha, digest);
}
#else
static void sha1_digest(unsigned char *digest, char *in)
{
SHA_CTX sha;
SHA1_Init(&sha);
SHA1_Update(&sha, in, strlen(in));
SHA1_Final(digest, &sha);
}
#endif
int ws_handshake(wsh_t *wsh)
{
char key[256] = "";
char version[5] = "";
char proto[256] = "";
char uri[256] = "";
char input[256] = "";
unsigned char output[SHA1_HASH_SIZE] = "";
char b64[256] = "";
char respond[512] = "";
ssize_t bytes;
char *p, *e;
if (wsh->sock == ws_sock_invalid) {
return -3;
}
if (wsh->secure) {
wsh->ssl = SSL_new(globals.ssl_ctx);
SSL_set_fd(wsh->ssl, wsh->sock);
SSL_accept(wsh->ssl);
}
while((bytes = ws_raw_read(wsh, wsh->buffer + wsh->datalen, wsh->buflen - wsh->datalen)) > 0) {
wsh->datalen += bytes;
if (strstr(wsh->buffer, "\r\n\r\n") || strstr(wsh->buffer, "\n\n")) {
break;
}
}
*(wsh->buffer+bytes) = '\0';
if (strncasecmp(wsh->buffer, "GET ", 4)) {
goto err;
}
p = wsh->buffer + 4;
if (!(e = strchr(p, ' '))) {
goto err;
}
strncpy(uri, p, e-p);
cheezy_get_var(wsh->buffer, "Sec-WebSocket-Key", key, sizeof(key));
cheezy_get_var(wsh->buffer, "Sec-WebSocket-Version", version, sizeof(version));
cheezy_get_var(wsh->buffer, "Sec-WebSocket-Protocol", proto, sizeof(proto));
if (!*key) {
goto err;
}
snprintf(input, sizeof(input), "%s%s", key, WEBSOCKET_GUID);
sha1_digest(output, input);
b64encode((unsigned char *)output, SHA1_HASH_SIZE, (unsigned char *)b64, sizeof(b64));
snprintf(respond, sizeof(respond),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"Sec-WebSocket-Protocol: %s\r\n\r\n",
b64,
proto);
ws_raw_write(wsh, respond, strlen(respond));
wsh->handshake = 1;
return 0;
err:
snprintf(respond, sizeof(respond), "HTTP/1.1 400 Bad Request\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n");
//printf("ERR:\n%s\n", respond);
send(wsh->sock, respond, strlen(respond), 0);
ws_close(wsh, WS_NONE);
return -1;
}
ssize_t ws_raw_read(wsh_t *wsh, void *data, size_t bytes)
{
ssize_t r;
if (wsh->ssl) {
return SSL_read(wsh->ssl, data, bytes);
}
do {
r = recv(wsh->sock, data, bytes, 0);
} while (r == -1 && (errno == EAGAIN || errno == EINTR));
if (r<0) {
//printf("READ FAIL: %s\n", strerror(errno));
}
return r;
}
ssize_t ws_raw_write(wsh_t *wsh, void *data, size_t bytes)
{
size_t r;
if (wsh->ssl) {
return SSL_write(wsh->ssl, data, bytes);
}
do {
r = send(wsh->sock, data, bytes, 0);
} while (r == -1 && (errno == EAGAIN || errno == EINTR));
if (r<0) {
//printf("wRITE FAIL: %s\n", strerror(errno));
}
return r;
}
int ws_init(wsh_t *wsh, ws_socket_t sock, size_t buflen, int secure)
{
memset(wsh, 0, sizeof(*wsh));
wsh->sock = sock;
if (buflen > MAXLEN) {
buflen = MAXLEN;
}
wsh->buflen = buflen;
wsh->secure = secure;
if (!wsh->buffer) {
wsh->buffer = malloc(wsh->buflen);
assert(wsh->buffer);
}
while (!wsh->down && !wsh->handshake) {
ws_handshake(wsh);
}
if (wsh->down) {
return -1;
}
return 0;
}
ssize_t ws_close(wsh_t *wsh, int16_t reason)
{
if (wsh->down) {
return -1;
}
wsh->down++;
if (reason) {
uint16_t *u16;
uint8_t fr[4] = {WSOC_CLOSE | 0x80, 2, 0};
u16 = (uint16_t *) &fr[2];
*u16 = htons((int16_t)reason);
ws_raw_write(wsh, fr, 4);
}
if (wsh->ssl) {
SSL_free(wsh->ssl);
wsh->ssl = NULL;
}
//close(wsh->sock);
wsh->sock = ws_sock_invalid;
if (wsh->buffer) {
free(wsh->buffer);
wsh->buffer = NULL;
}
return reason * -1;
}
ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
{
ssize_t need = 2;
char *maskp;
again:
need = 2;
maskp = NULL;
*data = NULL;
if (wsh->down) {
return -1;
}
if (!wsh->handshake) {
return ws_close(wsh, WS_PROTO_ERR);
}
if ((wsh->datalen = ws_raw_read(wsh, wsh->buffer, 14)) < need) {
/* too small - protocol err */
return ws_close(wsh, WS_PROTO_ERR);
}
*oc = *wsh->buffer & 0xf;
switch(*oc) {
case WSOC_CLOSE:
{
wsh->plen = wsh->buffer[1] & 0x7f;
*data = (uint8_t *) &wsh->buffer[2];
return ws_close(wsh, 1000);
}
break;
case WSOC_CONTINUATION:
case WSOC_TEXT:
case WSOC_BINARY:
case WSOC_PING:
case WSOC_PONG:
{
//int fin = (wsh->buffer[0] >> 7) & 1;
int mask = (wsh->buffer[1] >> 7) & 1;
if (mask) {
need += 4;
if (need > wsh->datalen) {
/* too small - protocol err */
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_PROTO_ERR);
}
}
wsh->plen = wsh->buffer[1] & 0x7f;
wsh->payload = &wsh->buffer[2];
if (wsh->plen == 127) {
uint64_t *u64;
need += 8;
if (need > wsh->datalen) {
/* too small - protocol err */
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_PROTO_ERR);
}
u64 = (uint64_t *) wsh->payload;
wsh->payload += 8;
wsh->plen = ntohl(*u64);
} else if (wsh->plen == 126) {
uint16_t *u16;
need += 2;
if (need > wsh->datalen) {
/* too small - protocol err */
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_PROTO_ERR);
}
u16 = (uint16_t *) wsh->payload;
wsh->payload += 2;
wsh->plen = ntohs(*u16);
}
if (mask) {
maskp = (char *)wsh->payload;
wsh->payload += 4;
}
need = (wsh->plen - (wsh->datalen - need));
if ((need + wsh->datalen) > wsh->buflen) {
/* too big - Ain't nobody got time fo' dat */
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_DATA_TOO_BIG);
}
wsh->rplen = wsh->plen - need;
while(need) {
ssize_t r = ws_raw_read(wsh, wsh->payload + wsh->rplen, need);
if (r < 1) {
/* invalid read - protocol err .. */
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_PROTO_ERR);
}
wsh->datalen += r;
wsh->rplen += r;
need -= r;
}
if (mask && maskp) {
uint32_t i;
for (i = 0; i < wsh->datalen; i++) {
wsh->payload[i] ^= maskp[i % 4];
}
}
if (*oc == WSOC_PING) {
ws_write_frame(wsh, WSOC_PONG, wsh->payload, wsh->rplen);
goto again;
}
*(wsh->payload+wsh->rplen) = '\0';
*data = (uint8_t *)wsh->payload;
//printf("READ[%ld][%d]-----------------------------:\n[%s]\n-------------------------------\n", wsh->rplen, *oc, (char *)*data);
return wsh->rplen;
}
break;
default:
{
/* invalid op code - protocol err .. */
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_PROTO_ERR);
}
break;
}
}
ssize_t ws_write_frame(wsh_t *wsh, ws_opcode_t oc, void *data, size_t bytes)
{
uint8_t hdr[14] = { 0 };
size_t hlen = 2;
if (wsh->down) {
return -1;
}
//printf("WRITE[%ld]-----------------------------:\n[%s]\n-----------------------------------\n", bytes, (char *) data);
hdr[0] = oc | 0x80;
if (bytes < 126) {
hdr[1] = bytes;
} else if (bytes < 0x10000) {
uint16_t *u16;
hdr[1] = 126;
hlen += 2;
u16 = (uint16_t *) &hdr[2];
*u16 = htons((uint16_t) bytes);
} else {
uint64_t *u64;
hdr[1] = 127;
hlen += 8;
u64 = (uint64_t *) &hdr[2];
*u64 = htonl(bytes);
}
if (ws_raw_write(wsh, (void *) &hdr[0], hlen) != hlen) {
return -1;
}
if (ws_raw_write(wsh, data, bytes) != bytes) {
return -2;
}
return bytes;
}

View File

@ -0,0 +1,84 @@
#ifndef _WS_H
#define _WS_H
#define MAXLEN 0x10000
#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define B64BUFFLEN 1024
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <assert.h>
#include <errno.h>
//#include "sha1.h"
#include <openssl/ssl.h>
struct globals_s {
const SSL_METHOD *ssl_method;
SSL_CTX *ssl_ctx;
char cert[512];
char key[512];
};
extern struct globals_s globals;
typedef int ws_socket_t;
#define ws_sock_invalid -1
typedef enum {
WS_NONE = 0,
WS_NORMAL = 1000,
WS_PROTO_ERR = 1002,
WS_DATA_TOO_BIG = 1009
} ws_cause_t;
typedef enum {
WSOC_CONTINUATION = 0x0,
WSOC_TEXT = 0x1,
WSOC_BINARY = 0x2,
WSOC_CLOSE = 0x8,
WSOC_PING = 0x9,
WSOC_PONG = 0xA
} ws_opcode_t;
typedef struct wsh_s {
ws_socket_t sock;
char *buffer;
size_t buflen;
ssize_t datalen;
char *payload;
ssize_t plen;
ssize_t rplen;
SSL *ssl;
int handshake;
uint8_t down;
int secure;
} wsh_t;
ssize_t ws_raw_read(wsh_t *wsh, void *data, size_t bytes);
ssize_t ws_raw_write(wsh_t *wsh, void *data, size_t bytes);
ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data);
ssize_t ws_write_frame(wsh_t *wsh, ws_opcode_t oc, void *data, size_t bytes);
int ws_init(wsh_t *wsh, ws_socket_t sock, size_t buflen, int secure);
ssize_t ws_close(wsh_t *wsh, int16_t reason);
void init_ssl(void);
void deinit_ssl(void);
static inline uint64_t get_unaligned_uint64(const void *p)
{
const struct { uint64_t d; } __attribute__((packed)) *pp = p;
return pp->d;
}
#endif