From d6e19bdc91b0c4c6b5a069e11898741ec082b289 Mon Sep 17 00:00:00 2001 From: russell Date: Fri, 18 Jan 2008 22:04:33 +0000 Subject: [PATCH] Merge changes from team/group/sip-tcptls This set of changes introduces TCP and TLS support for chan_sip. There are various new options in configs/sip.conf.sample that are used to enable these features. Also, there is a document, doc/siptls.txt that describes some things in more detail. This code was implemented by Brett Bryant and James Golovich. It was reviewed by Joshua Colp and myself. A number of other people participated in the testing of this code, but since it was done outside of the bug tracker, I do not have their names. If you were one of them, thanks a lot for the help! (closes issue #4903, but with completely different code that what exists there.) git-svn-id: http://svn.digium.com/svn/asterisk/trunk@99085 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 5 + CREDITS | 7 +- channels/chan_sip.c | 783 +++++++++++++++++++++++++++++++++----- configs/sip.conf.sample | 16 +- doc/siptls.txt | 94 +++++ include/asterisk/http.h | 86 +---- include/asterisk/tcptls.h | 166 ++++++++ main/Makefile | 2 +- main/http.c | 200 +--------- main/manager.c | 3 +- main/tcptls.c | 452 ++++++++++++++++++++++ 11 files changed, 1424 insertions(+), 390 deletions(-) create mode 100644 doc/siptls.txt create mode 100644 include/asterisk/tcptls.h create mode 100644 main/tcptls.c diff --git a/CHANGES b/CHANGES index b208f65b8..00757c4b1 100644 --- a/CHANGES +++ b/CHANGES @@ -128,6 +128,11 @@ SIP changes SIP uri. * Added a new global and per-peer option, qualifyfreq, which allows you to configure the qualify frequency. + * Added SIP Session Timers support (RFC 4028). This prevents stuck SIP sessions that + were not properly torn down due to network or endpoint failures during an established + SIP session. + * Added TCP and TLS support for SIP. See doc/siptls.txt and configs/sip.conf.sample for + more information on how it is used. IAX2 changes ------------ diff --git a/CREDITS b/CREDITS index cb59dbe1b..a7a432d69 100644 --- a/CREDITS +++ b/CREDITS @@ -56,7 +56,7 @@ Anthony Minessale II - Countless big and small fixes, and relentless forward and sip configs. anthmct(AT)yahoo.com http://www.asterlink.com -James Golovich - Innumerable contributions +James Golovich - Innumerable contributions, including SIP TCP and TLS support. You can find him and asterisk-perl at http://asterisk.gnuinter.net Andre Bierwirth - Extension hints and status @@ -176,6 +176,11 @@ Marta Carbone - console_video and the astobj2 framework Luigi Rizzo - astobj2, console_video, windows build, chan_oss cleanup, and a bunch of infrastructure work (loader, new_cli, ...) +Brett Bryant - digit option for musiconhold selection, ENUMQUERY and ENUMRESULT functions, + feature group configuration for features.conf, per-file CLI debug and verbose settings, + TCP and TLS support for SIP, and various bug fixes. + brettbryant(AT)gmail.com + === OTHER CONTRIBUTIONS === John Todd - Monkey sounds and associated teletorture prompt Michael Jerris - bug marshaling diff --git a/channels/chan_sip.c b/channels/chan_sip.c index c36706763..1d2f38feb 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -175,6 +175,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/translate.h" #include "asterisk/version.h" #include "asterisk/event.h" +#include "asterisk/tcptls.h" #ifndef FALSE #define FALSE 0 @@ -574,8 +575,9 @@ static const struct cfsip_options { /*! \brief SIP Extensions we support */ #define SUPPORTED_EXTENSIONS "replaces, timer" -/*! \brief Standard SIP port from RFC 3261. DO NOT CHANGE THIS */ +/*! \brief Standard SIP and TLS port from RFC 3261. DO NOT CHANGE THIS */ #define STANDARD_SIP_PORT 5060 +#define STANDARD_TLS_PORT 5061 /* Note: in many SIP headers, absence of a port number implies port 5060, * and this is why we cannot change the above constant. * There is a limited number of places in asterisk where we could, @@ -757,7 +759,23 @@ static int *sipsock_read_id; /*!< ID of IO entry for sipsock FD */ #define DEC_CALL_RINGING 2 #define INC_CALL_RINGING 3 -/*! \brief The data grabbed from the UDP socket +/*!< Define some SIP transports */ +enum sip_transport { + SIP_TRANSPORT_UDP = 1, + SIP_TRANSPORT_TCP = 1 << 1, + SIP_TRANSPORT_TLS = 1 << 2, +}; + +struct sip_socket { + ast_mutex_t *lock; + enum sip_transport type; + int fd; + uint16_t port; + struct server_instance *ser; +}; + +/*! \brief sip_request: The data grabbed from the UDP socket + * * \verbatim * Incoming messages: we first store the data from the socket in data[], * adding a trailing \0 to make string parsing routines happy. @@ -795,6 +813,7 @@ struct sip_request { char *header[SIP_MAX_HEADERS]; char *line[SIP_MAX_LINES]; char data[SIP_MAX_PACKET]; + struct sip_socket socket; }; /*! \brief structure used in transfers */ @@ -1175,6 +1194,7 @@ struct sip_pvt { AST_STRING_FIELD(rpid_from); /*!< Our RPID From header */ AST_STRING_FIELD(url); /*!< URL to be sent with next message to peer */ ); + struct sip_socket socket; unsigned int ocseq; /*!< Current outgoing seqno */ unsigned int icseq; /*!< Current incoming seqno */ ast_group_t callgroup; /*!< Call group */ @@ -1397,6 +1417,7 @@ struct sip_mailbox { struct sip_peer { ASTOBJ_COMPONENTS(struct sip_peer); /*!< name, refcount, objflags, object pointers */ /*!< peer->name is the unique name of this object */ + struct sip_socket socket; char secret[80]; /*!< Password */ char md5secret[80]; /*!< Password in MD5 */ struct sip_auth *auth; /*!< Realm authentication list */ @@ -1500,6 +1521,7 @@ struct sip_registry { AST_STRING_FIELD(callback); /*!< Contact extension */ AST_STRING_FIELD(random); ); + enum sip_transport transport; int portno; /*!< Optional port override */ int expire; /*!< Sched ID of expiration */ int expiry; /*!< Value to use for the Expires header */ @@ -1516,8 +1538,19 @@ struct sip_registry { char lastmsg[256]; /*!< Last Message sent/received */ }; +struct sip_threadinfo { + int stop; + pthread_t threadid; + struct server_instance *ser; + enum sip_transport type; /* We keep a copy of the type here so we can display it in the connection list */ + AST_LIST_ENTRY(sip_threadinfo) list; +}; + /* --- Linked lists of various objects --------*/ +/*! \brief The thread list of TCP threads */ +static AST_LIST_HEAD_STATIC(threadl, sip_threadinfo); + /*! \brief The user list: Users and friends */ static struct ast_user_list { ASTOBJ_CONTAINER_COMPONENTS(struct sip_user); @@ -1605,6 +1638,8 @@ static struct sockaddr_in stunaddr; /*!< stun server address */ */ static struct ast_ha *localaddr; /*!< List of local networks, on the same side of NAT as this Asterisk */ +static int ourport_tcp; +static int ourport_tls; static struct sockaddr_in debugaddr; static struct ast_config *notify_types; /*!< The list of manual NOTIFY types we know how to send */ @@ -1638,6 +1673,10 @@ static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int sip_senddigit_begin(struct ast_channel *ast, char digit); static int sip_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); +static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin); +static int sip_standard_port(struct sip_socket s); +static int sip_prepare_socket(struct sip_pvt *p); + /*--- Transmitting responses and requests */ static int sipsock_read(int *id, int fd, short events, void *ignore); static int __sip_xmit(struct sip_pvt *p, char *data, int len); @@ -1811,6 +1850,11 @@ static int acf_channel_read(struct ast_channel *chan, const char *funcname, char static void sip_dump_history(struct sip_pvt *dialog); /* Dump history to debuglog at end of dialog, before destroying data */ static inline int sip_debug_test_addr(const struct sockaddr_in *addr); static inline int sip_debug_test_pvt(struct sip_pvt *p); + + +/*! \brief Append to SIP dialog history + \return Always returns 0 */ +#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args) static void append_history_full(struct sip_pvt *p, const char *fmt, ...); static void sip_dump_history(struct sip_pvt *dialog); @@ -1998,6 +2042,31 @@ static const struct ast_channel_tech sip_tech = { */ static struct ast_channel_tech sip_tech_info; +static void *sip_tcp_worker_fn(void *); + +static struct ast_tls_config sip_tls_cfg; +static struct ast_tls_config default_tls_cfg; + +static struct server_args sip_tcp_desc = { + .accept_fd = -1, + .master = AST_PTHREADT_NULL, + .tls_cfg = NULL, + .poll_timeout = -1, + .name = "sip tcp server", + .accept_fn = server_root, + .worker_fn = sip_tcp_worker_fn, +}; + +static struct server_args sip_tls_desc = { + .accept_fd = -1, + .master = AST_PTHREADT_NULL, + .tls_cfg = &sip_tls_cfg, + .poll_timeout = -1, + .name = "sip tls server", + .accept_fn = server_root, + .worker_fn = sip_tcp_worker_fn, +}; + /* wrapper macro to tell whether t points to one of the sip_tech descriptors */ #define IS_SIP_TECH(t) ((t) == &sip_tech || (t) == &sip_tech_info) @@ -2038,6 +2107,119 @@ static struct ast_rtp_protocol sip_rtp = { .get_codec = sip_get_codec, }; +static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct server_instance *ser); + +static void *sip_tcp_helper_thread(void *data) +{ + struct sip_pvt *pvt = data; + struct server_instance *ser = pvt->socket.ser; + + return _sip_tcp_helper_thread(pvt, ser); +} + +static void *sip_tcp_worker_fn(void *data) +{ + struct server_instance *ser = data; + + return _sip_tcp_helper_thread(NULL, ser); +} + +/*! \brief SIP TCP helper function */ +static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct server_instance *ser) +{ + int res, cl; + struct sip_request req = { 0, } , reqcpy = { 0, }; + struct sip_threadinfo *me; + char buf[1024]; + + me = ast_calloc(1, sizeof(*me)); + + if (!me) + goto cleanup2; + + me->threadid = pthread_self(); + me->ser = ser; + if (ser->ssl) + me->type = SIP_TRANSPORT_TLS; + else + me->type = SIP_TRANSPORT_TCP; + + AST_LIST_LOCK(&threadl); + AST_LIST_INSERT_TAIL(&threadl, me, list); + AST_LIST_UNLOCK(&threadl); + + req.socket.lock = ast_calloc(1, sizeof(*req.socket.lock)); + + if (!req.socket.lock) + goto cleanup; + + ast_mutex_init(req.socket.lock); + + for (;;) { + memset(req.data, 0, sizeof(req.data)); + req.len = 0; + req.ignore = 0; + + req.socket.fd = ser->fd; + if (ser->ssl) { + req.socket.type = SIP_TRANSPORT_TLS; + req.socket.port = htons(ourport_tls); + } else { + req.socket.type = SIP_TRANSPORT_TCP; + req.socket.port = htons(ourport_tcp); + } + res = ast_wait_for_input(ser->fd, -1); + if (res < 0) { + ast_log(LOG_DEBUG, "ast_wait_for_input returned %d\n", res); + goto cleanup; + } + + /* Read in headers one line at a time */ + while (req.len < 4 || strncmp((char *)&req.data + req.len - 4, "\r\n\r\n", 4)) { + if (req.socket.lock) + ast_mutex_lock(req.socket.lock); + if (!fgets(buf, sizeof(buf), ser->f)) + goto cleanup; + if (req.socket.lock) + ast_mutex_unlock(req.socket.lock); + if (me->stop) + goto cleanup; + strncat(req.data, buf, sizeof(req.data) - req.len); + req.len = strlen(req.data); + } + parse_copy(&reqcpy, &req); + if (sscanf(get_header(&reqcpy, "Content-Length"), "%d", &cl)) { + while (cl > 0) { + if (req.socket.lock) + ast_mutex_lock(req.socket.lock); + if (!fread(buf, (cl < sizeof(buf)) ? cl : sizeof(buf), 1, ser->f)) + goto cleanup; + if (req.socket.lock) + ast_mutex_unlock(req.socket.lock); + if (me->stop) + goto cleanup; + cl -= strlen(buf); + strncat(req.data, buf, sizeof(req.data) - req.len); + req.len = strlen(req.data); + } + } + req.socket.ser = ser; + handle_request_do(&req, &ser->requestor); + } + +cleanup: + AST_LIST_LOCK(&threadl); + AST_LIST_REMOVE(&threadl, me, list); + AST_LIST_UNLOCK(&threadl); + ast_free(me); +cleanup2: + fclose(ser->f); + ast_free(ser); + ast_free(req.socket.lock); + + return NULL; +} + #define sip_pvt_lock(x) ast_mutex_lock(&x->pvt_lock) #define sip_pvt_unlock(x) ast_mutex_unlock(&x->pvt_lock) @@ -2077,10 +2259,6 @@ static struct ast_udptl_protocol sip_udptl = { set_udptl_peer: sip_set_udptl_peer, }; -/*! \brief Append to SIP dialog history - \return Always returns 0 */ -#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args) - static void append_history_full(struct sip_pvt *p, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); @@ -2283,12 +2461,45 @@ static inline int sip_debug_test_pvt(struct sip_pvt *p) return sip_debug_test_addr(sip_real_dst(p)); } +static inline const char *get_transport(enum sip_transport t) +{ + switch (t) { + case SIP_TRANSPORT_UDP: + return "UDP"; + case SIP_TRANSPORT_TCP: + return "TCP"; + case SIP_TRANSPORT_TLS: + return "TLS"; + } + + return "UNKNOWN"; +} + /*! \brief Transmit SIP message */ static int __sip_xmit(struct sip_pvt *p, char *data, int len) { - int res; + int res = 0; const struct sockaddr_in *dst = sip_real_dst(p); - res = sendto(sipsock, data, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in)); + + ast_log(LOG_DEBUG, "Trying to put '%.10s' onto %s socket...\n", data, get_transport(p->socket.type)); + + if (sip_prepare_socket(p) < 0) + return XMIT_ERROR; + + if (p->socket.lock) + ast_mutex_lock(p->socket.lock); + + if (p->socket.type & SIP_TRANSPORT_UDP) + res = sendto(p->socket.fd, data, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in)); + else { + if (p->socket.ser->f) + res = server_write(p->socket.ser, data, len); + else + ast_log(LOG_DEBUG, "No p->socket.ser->f len=%d\n", len); + } + + if (p->socket.lock) + ast_mutex_unlock(p->socket.lock); if (res == -1) { switch (errno) { @@ -2301,10 +2512,10 @@ static int __sip_xmit(struct sip_pvt *p, char *data, int len) } if (res != len) ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno)); + return res; } - /*! \brief Build a Via header for a request */ static void build_via(struct sip_pvt *p) { @@ -2312,7 +2523,8 @@ static void build_via(struct sip_pvt *p) const char *rport = ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_RFC3581 ? ";rport" : ""; /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */ - ast_string_field_build(p, via, "SIP/2.0/UDP %s:%d;branch=z9hG4bK%08x%s", + ast_string_field_build(p, via, "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s", + get_transport(p->socket.type), ast_inet_ntoa(p->ourip.sin_addr), ntohs(p->ourip.sin_port), p->branch, rport); } @@ -2543,10 +2755,22 @@ static int retrans_pkt(const void *data) */ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, char *data, int len, int fatal, int sipmethod) { - struct sip_pkt *pkt; + struct sip_pkt *pkt = NULL; int siptimer_a = DEFAULT_RETRANS; int xmitres = 0; + /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */ + /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */ + /* According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */ + if (!(p->socket.type & SIP_TRANSPORT_UDP)) { + xmitres = __sip_xmit(dialog_ref(p), data, len); /* Send packet */ + if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */ + append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)"); + return AST_FAILURE; + } else + return AST_SUCCESS; + } + if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1))) return AST_FAILURE; /* copy data, add a terminator and save length */ @@ -2893,6 +3117,7 @@ static char *get_in_brackets(char *tmp) ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp); } } + return tmp; } @@ -2929,7 +3154,7 @@ static int parse_uri(char *uri, char *scheme, if (!strncasecmp(uri, scheme, l)) uri += l; else { - ast_log(LOG_NOTICE, "Missing scheme '%s' in '%s'\n", scheme, uri); + ast_debug(1, "Missing scheme '%s' in '%s'\n", scheme, uri); error = -1; } } @@ -3542,6 +3767,8 @@ static void set_t38_capabilities(struct sip_pvt *p) */ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer) { + dialog->socket = peer->socket; + if ((peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr) && (!peer->maxms || ((peer->lastms >= 0) && (peer->lastms <= peer->maxms)))) { dialog->sa = (peer->addr.sin_addr.s_addr) ? peer->addr : peer->defaddr; @@ -3701,13 +3928,13 @@ static int create_addr(struct sip_pvt *dialog, const char *opeer) then hostname lookup */ hostn = peername; - portno = port ? atoi(port) : STANDARD_SIP_PORT; + portno = port ? atoi(port) : (dialog->socket.type & SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT; if (global_srvlookup) { char service[MAXHOSTNAMELEN]; int tportno; int ret; - snprintf(service, sizeof(service), "_sip._udp.%s", peername); + snprintf(service, sizeof(service), "_sip._%s.%s", get_transport(dialog->socket.type), peername); ret = ast_get_srv(NULL, host, sizeof(host), &tportno, service); if (ret > 0) { hostn = host; @@ -3825,6 +4052,7 @@ static int sip_call(struct ast_channel *ast, char *dest, int timeout) p->t38.jointcapability = p->t38.capability; ast_debug(2,"Our T38 capability (%d), joint T38 capability (%d)\n", p->t38.capability, p->t38.jointcapability); + xmitres = transmit_invite(p, SIP_INVITE, 1, 2); if (xmitres == XMIT_ERROR) return -1; @@ -5284,6 +5512,8 @@ static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *si ast_mutex_init(&p->pvt_lock); + p->socket.fd = -1; + p->socket.type = SIP_TRANSPORT_UDP; p->method = intended_method; p->initid = -1; p->waitid = -1; @@ -5539,14 +5769,40 @@ static int sip_register(const char *value, int lineno) { struct sip_registry *reg; int portnum = 0; - char username[256] = ""; + enum sip_transport transport = SIP_TRANSPORT_UDP; + char buf[256] = ""; + char *username = NULL; char *hostname=NULL, *secret=NULL, *authuser=NULL; char *porta=NULL; char *callback=NULL; + char *trans=NULL; if (!value) return -1; - ast_copy_string(username, value, sizeof(username)); + + ast_copy_string(buf, value, sizeof(buf)); + + username = strstr(buf, "://"); + + if (username) { + *username = '\0'; + username += 3; + + trans = buf; + + if (!strcasecmp(trans, "udp")) + transport = SIP_TRANSPORT_UDP; + else if (!strcasecmp(trans, "tcp")) + transport = SIP_TRANSPORT_TCP; + else if (!strcasecmp(trans, "tls")) + transport = SIP_TRANSPORT_TLS; + else + ast_log(LOG_WARNING, "'%s' is not a valid transport value for registration '%s' at line '%d'\n", trans, value, lineno); + } else { + username = buf; + ast_log(LOG_DEBUG, "no trans\n"); + } + /* First split around the last '@' then parse the two components. */ hostname = strrchr(username, '@'); /* allow @ in the first part */ if (hostname) @@ -5600,6 +5856,7 @@ static int sip_register(const char *value, int lineno) ast_string_field_set(reg, authuser, authuser); if (secret) ast_string_field_set(reg, secret, secret); + reg->transport = transport; reg->expire = -1; reg->expiry = default_expiry; reg->timeout = -1; @@ -7045,6 +7302,12 @@ static int transmit_response_using_temp(ast_string_field callid, struct sockaddr build_via(p); ast_string_field_set(p, callid, callid); + p->socket.lock = req->socket.lock; + p->socket.type = req->socket.type; + p->socket.fd = req->socket.fd; + p->socket.port = req->socket.port; + p->socket.ser = req->socket.ser; + /* Use this temporary pvt structure to send the message */ __transmit_response(p, msg, req, XMIT_UNRELIABLE); @@ -7933,10 +8196,13 @@ static void extract_uri(struct sip_pvt *p, struct sip_request *req) static void build_contact(struct sip_pvt *p) { /* Construct Contact: header */ - if (ntohs(p->ourip.sin_port) != STANDARD_SIP_PORT) - ast_string_field_build(p, our_contact, "", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr), ntohs(p->ourip.sin_port)); - else - ast_string_field_build(p, our_contact, "", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr)); + if (p->socket.type & SIP_TRANSPORT_UDP) { + if (!sip_standard_port(p->socket)) + ast_string_field_build(p, our_contact, "", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr), ntohs(p->socket.port)); + else + ast_string_field_build(p, our_contact, "", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr)); + } else + ast_string_field_build(p, our_contact, "", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr), ntohs(p->socket.port), get_transport(p->socket.type)); } /*! \brief Build the Remote Party-ID & From using callingpres options */ @@ -8085,8 +8351,8 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho l = tmp_l; } - if (ntohs(p->ourip.sin_port) != STANDARD_SIP_PORT && ast_strlen_zero(p->fromdomain)) - snprintf(from, sizeof(from), "\"%s\" ;tag=%s", n, l, S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)), ntohs(p->ourip.sin_port), p->tag); + if (!sip_standard_port(p->socket) && ast_strlen_zero(p->fromdomain)) + snprintf(from, sizeof(from), "\"%s\" ;tag=%s", n, l, S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)), ntohs(p->socket.port), p->tag); else snprintf(from, sizeof(from), "\"%s\" ;tag=%s", n, l, S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)), p->tag); @@ -8367,7 +8633,7 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim ast_copy_string(from, get_header(&p->initreq, "From"), sizeof(from)); c = get_in_brackets(from); - if (strncasecmp(c, "sip:", 4)) { + if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); return -1; } @@ -8376,7 +8642,7 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to)); c = get_in_brackets(to); - if (strncasecmp(c, "sip:", 4)) { + if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); return -1; } @@ -8682,6 +8948,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * ast_log(LOG_WARNING, "Unable to allocate registration transaction (memory or socket error)\n"); return 0; } + if (p->do_history) append_history(p, "RegistryInit", "Account: %s@%s", r->username, r->hostname); @@ -8733,6 +9000,9 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * if (!ast_strlen_zero(r->callback)) ast_string_field_set(p, exten, r->callback); + /* Set transport and port so the correct contact is built */ + p->socket.type = r->transport; + p->socket.port = htons(r->portno); /* check which address we should use in our contact header based on whether the remote host is on the external or @@ -8838,6 +9108,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char * r->regstate = auth ? REG_STATE_AUTHSENT : REG_STATE_REGSENT; r->regattempts++; /* Another attempt */ ast_debug(4, "REGISTER attempt %d to %s@%s\n", r->regattempts, r->username, r->hostname); + return send_request(p, &req, XMIT_CRITICAL, p->ocseq); } @@ -8892,10 +9163,12 @@ static int transmit_refer(struct sip_pvt *p, const char *dest) ast_copy_string(from, of, sizeof(from)); of = get_in_brackets(from); ast_string_field_set(p, from, of); - if (strncasecmp(of, "sip:", 4)) - ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); - else + if (!strncasecmp(of, "sip:", 4)) of += 4; + else if (!strncasecmp(of, "sips:", 5)) + of += 5; + else + ast_log(LOG_NOTICE, "From address missing 'sip(s):', using it anyway\n"); /* Get just the username part */ if ((c = strchr(dest, '@'))) c = NULL; @@ -9148,7 +9421,7 @@ static int parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req) ast_string_field_set(pvt, okcontacturi, c); /* We should return false for URI:s we can't handle, - like sips:, tel:, mailto:,ldap: etc */ + like tel:, mailto:,ldap: etc */ return TRUE; } @@ -9159,7 +9432,7 @@ static int set_address_from_contact(struct sip_pvt *pvt) struct ast_hostent ahp; int port; char *host, *pt; - char *contact; + char *contact, *contact2; if (ast_test_flag(&pvt->flags[0], SIP_NAT_ROUTE)) { @@ -9172,11 +9445,21 @@ static int set_address_from_contact(struct sip_pvt *pvt) /* Work on a copy */ contact = ast_strdupa(pvt->fullcontact); + contact2 = ast_strdupa(pvt->fullcontact); /* We have only the part in here so we just need to parse a SIP URI.*/ - if (parse_uri(contact, "sip:", &contact, NULL, &host, &pt, NULL)) - ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact); - port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT; + if (pvt->socket.type == SIP_TRANSPORT_TLS) { + if (parse_uri(contact, "sips:", &contact, NULL, &host, &pt, NULL)) { + if (parse_uri(contact2, "sip:", &contact, NULL, &host, &pt, NULL)) + ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact); + } + port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_TLS_PORT; + } else { + if (parse_uri(contact, "sip:", &contact, NULL, &host, &pt, NULL)) + ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact); + port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT; + } + ast_verbose("--- set_address_from_contact host '%s'\n", host); /* XXX This could block for a long time XXX */ @@ -9201,7 +9484,7 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st char data[BUFSIZ]; const char *expires = get_header(req, "Expires"); int expiry = atoi(expires); - char *curi, *host, *pt; + char *curi, *host, *pt, *curi2; int port; const char *useragent; struct hostent *hp; @@ -9222,11 +9505,14 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st } } + pvt->socket = peer->socket = req->socket; + /* Look for brackets */ curi = contact; if (strchr(contact, '<') == NULL) /* No <, check for ; and strip it */ strsep(&curi, ";"); /* This is Header options, not URI options */ curi = get_in_brackets(contact); + curi2 = ast_strdupa(curi); /* if they did not specify Contact: or Expires:, they are querying what we currently have stored as their contact address, so return @@ -9264,9 +9550,18 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st ast_string_field_build(pvt, our_contact, "<%s>", curi); /* Make sure it's a SIP URL */ - if (parse_uri(curi, "sip:", &curi, NULL, &host, &pt, NULL)) - ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:) trying to use anyway\n"); - port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT; + if (pvt->socket.type == SIP_TRANSPORT_TLS) { + if (parse_uri(curi, "sips:", &curi, NULL, &host, &pt, NULL)) { + if (parse_uri(curi2, "sip:", &curi, NULL, &host, &pt, NULL)) + ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:) trying to use anyway\n"); + } + port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_TLS_PORT; + } else { + if (parse_uri(curi, "sip:", &curi, NULL, &host, &pt, NULL)) + ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:) trying to use anyway\n"); + port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT; + } + oldsin = peer->addr; if (!ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE)) { /* XXX This could block for a long time XXX */ @@ -9302,7 +9597,8 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st ast_sched_add(sched, (expiry + 10) * 1000, expire_register, peer); pvt->expiry = expiry; snprintf(data, sizeof(data), "%s:%d:%d:%s:%s", ast_inet_ntoa(peer->addr.sin_addr), ntohs(peer->addr.sin_port), expiry, peer->username, peer->fullcontact); - if (!peer->rt_fromcontact) + /* Saving TCP connections is useless, we won't be able to reconnect */ + if (!peer->rt_fromcontact && (peer->socket.type & SIP_TRANSPORT_UDP)) ast_db_put("SIP/Registry", peer->name, data); manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Registered\r\n", peer->name); @@ -9738,6 +10034,8 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr if (!strncasecmp(c, "sip:", 4)) { name = c + 4; + } else if (!strncasecmp(c, "sips:", 5)) { + name = c + 5; } else { name = c; ast_log(LOG_NOTICE, "Invalid to address: '%s' from %s (missing sip:) trying to use anyway...\n", c, ast_inet_ntoa(sin->sin_addr)); @@ -9775,6 +10073,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr } else res = AUTH_NOT_FOUND; } + if (peer) { /* Set Frame packetization */ if (p->rtp) { @@ -9928,11 +10227,14 @@ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq) return 0; exten = get_in_brackets(tmp); - if (strncasecmp(exten, "sip:", 4)) { + if (!strncasecmp(exten, "sip:", 4)) { + exten += 4; + } else if (!strncasecmp(exten, "sips:", 5)) { + exten += 5; + } else { ast_log(LOG_WARNING, "Huh? Not an RDNIS SIP header (%s)?\n", exten); return -1; } - exten += 4; /* Get diversion-reason param if present */ if ((params = strchr(tmp, ';'))) { @@ -9996,12 +10298,15 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq) ast_uri_decode(tmp); uri = get_in_brackets(tmp); - - if (strncasecmp(uri, "sip:", 4)) { + + if (!strncasecmp(uri, "sip:", 4)) { + uri += 4; + } else if (!strncasecmp(uri, "sips:", 5)) { + uri += 5; + } else { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", uri); return -1; } - uri += 4; /* Now find the From: caller ID and name */ /* XXX Why is this done in get_destination? Isn't it already done? @@ -10015,11 +10320,14 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq) } if (!ast_strlen_zero(from)) { - if (strncasecmp(from, "sip:", 4)) { + if (!strncasecmp(from, "sip:", 4)) { + from += 4; + } else if (!strncasecmp(from, "sips:", 5)) { + from += 5; + } else { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", from); return -1; } - from += 4; if ((a = strchr(from, '@'))) *a++ = '\0'; else @@ -10188,11 +10496,14 @@ static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoi if (pedanticsipchecking) ast_uri_decode(refer_to); - if (strncasecmp(refer_to, "sip:", 4)) { + if (!strncasecmp(refer_to, "sip:", 4)) { + refer_to += 4; /* Skip sip: */ + } else if (!strncasecmp(refer_to, "sips:", 5)) { + refer_to += 5; + } else { ast_log(LOG_WARNING, "Can't transfer to non-sip: URI. (Refer-to: %s)?\n", refer_to); return -3; } - refer_to += 4; /* Skip sip: */ /* Get referred by header if it exists */ p_referred_by = get_header(req, "Referred-By"); @@ -10219,11 +10530,13 @@ static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoi } referred_by_uri = get_in_brackets(h_referred_by); - if(strncasecmp(referred_by_uri, "sip:", 4)) { + if (!strncasecmp(referred_by_uri, "sip:", 4)) { + referred_by_uri += 4; /* Skip sip: */ + } else if (!strncasecmp(referred_by_uri, "sips:", 5)) { + referred_by_uri += 5; /* Skip sips: */ + } else { ast_log(LOG_WARNING, "Huh? Not a sip: header (Referred-by: %s). Skipping.\n", referred_by_uri); referred_by_uri = NULL; - } else { - referred_by_uri += 4; /* Skip sip: */ } } @@ -10348,12 +10661,16 @@ static int get_also_info(struct sip_pvt *p, struct sip_request *oreq) if (pedanticsipchecking) ast_uri_decode(c); - - if (strncasecmp(c, "sip:", 4)) { + + if (!strncasecmp(c, "sip:", 4)) { + c += 4; + } else if (!strncasecmp(c, "sips:", 5)) { + c += 5; + } else { ast_log(LOG_WARNING, "Huh? Not a SIP header in Also: transfer (%s)?\n", c); return -1; } - c += 4; + if ((a = strchr(c, ';'))) /* Remove arguments */ *a = '\0'; @@ -10457,7 +10774,7 @@ static void check_via(struct sip_pvt *p, struct sip_request *req) if (c) { *c = '\0'; c = ast_skip_blanks(c+1); - if (strcasecmp(via, "SIP/2.0/UDP")) { + if (strcasecmp(via, "SIP/2.0/UDP") && strcasecmp(via, "SIP/2.0/TCP") && strcasecmp(via, "SIP/2.0/TLS")) { ast_log(LOG_WARNING, "Don't know how to respond via '%s'\n", via); return; } @@ -10840,7 +11157,7 @@ static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_requ char from[256]; char *dummy; /* dummy return value for parse_uri */ char *domain; /* dummy return value for parse_uri */ - char *of; + char *of, *of2; char rpid_num[50]; const char *rpid; enum check_auth_result res; @@ -10868,6 +11185,8 @@ static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_requ char *t = uri2; if (!strncasecmp(t, "sip:", 4)) t+= 4; + else if (!strncasecmp(t, "sips:", 5)) + t += 5; ast_string_field_set(p, exten, t); t = strchr(p->exten, '@'); if (t) @@ -10878,10 +11197,19 @@ static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_requ /* save the URI part of the From header */ ast_string_field_set(p, from, of); + of2 = ast_strdupa(of); + /* ignore all fields but name */ - if (parse_uri(of, "sip:", &of, &dummy, &domain, &dummy, &dummy)) { - ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); + if (p->socket.type == SIP_TRANSPORT_TLS) { + if (parse_uri(of, "sips:", &of, &dummy, &domain, &dummy, &dummy)) { + if (parse_uri(of2, "sip:", &of, &dummy, &domain, &dummy, &dummy)) + ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); + } + } else { + if (parse_uri(of, "sip:", &of, &dummy, &domain, &dummy, &dummy)) + ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); } + if (ast_strlen_zero(of)) { /* XXX note: the original code considered a missing @host * as a username-only URI. The SIP RFC (19.1.1) says that @@ -11026,7 +11354,7 @@ static void receive_message(struct sip_pvt *p, struct sip_request *req) /*! \brief CLI Command to show calls within limits set by call_limit */ static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { -#define FORMAT "%-25.25s %-15.15s %-15.15s \n" +#define FORMAT "%-25.25s %-15.15s %-15.15s \n" #define FORMAT2 "%-25.25s %-15.15s %-15.15s \n" char ilimits[40]; char iused[40]; @@ -11082,6 +11410,7 @@ static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_arg #undef FORMAT2 } + /*! \brief Convert transfer mode to text string */ static char *transfermode2str(enum transfermodes mode) { @@ -11181,6 +11510,43 @@ static const char *cli_yesno(int x) return x ? "Yes" : "No"; } +/*! \brief Show active TCP connections */ +static char *sip_show_tcp(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct sip_threadinfo *th; + +#define FORMAT2 "%-30.30s %3.6s %9.9s %6.6s\n" +#define FORMAT "%-30.30s %-6d %-9.9s %-6.6s\n" + + switch (cmd) { + case CLI_INIT: + e->command = "sip show tcp"; + e->usage = + "Usage: sip show tcp\n" + " Lists all active TCP/TLS sessions.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) + return CLI_SHOWUSAGE; + + ast_cli(a->fd, FORMAT2, "Host", "Port", "Transport", "Type"); + AST_LIST_LOCK(&threadl); + AST_LIST_TRAVERSE(&threadl, th, list) { + ast_cli(a->fd, FORMAT, ast_inet_ntoa(th->ser->requestor.sin_addr), + ntohs(th->ser->requestor.sin_port), + get_transport(th->type), + (th->ser->client ? "Client" : "Server")); + + } + AST_LIST_UNLOCK(&threadl); + return CLI_SUCCESS; +#undef FORMAT +#undef FORMAT2 +} + /*! \brief CLI Command 'SIP Show Users' */ static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { @@ -11960,6 +12326,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct ast_cli(fd, " ToHost : %s\n", peer->tohost); ast_cli(fd, " Addr->IP : %s Port %d\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)", ntohs(peer->addr.sin_port)); ast_cli(fd, " Defaddr->IP : %s Port %d\n", ast_inet_ntoa(peer->defaddr.sin_addr), ntohs(peer->defaddr.sin_port)); + ast_cli(fd, " Transport : %s\n", get_transport(peer->socket.type)); if (!ast_strlen_zero(global_regcontext)) ast_cli(fd, " Reg. exten : %s\n", peer->regexten); ast_cli(fd, " Def. Username: %s\n", peer->username); @@ -13755,6 +14122,8 @@ static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req) if (ast_test_flag(&p->flags[0], SIP_PROMISCREDIR)) { if (!strncasecmp(s, "sip:", 4)) s += 4; + else if (!strncasecmp(s, "sips:", 5)) + s += 5; e = strchr(s, '/'); if (e) *e = '\0'; @@ -13776,6 +14145,8 @@ static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req) if (!strncasecmp(s, "sip:", 4)) s += 4; + else if (!strncasecmp(s, "sips:", 5)) + s += 5; e = strchr(s, ';'); /* And username ; parameters? */ if (e) *e = '\0'; @@ -15106,7 +15477,7 @@ static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target ast_softhangup_nolock(transferer->chan1, AST_SOFTHANGUP_DEV); if (target->chan1) ast_softhangup_nolock(target->chan1, AST_SOFTHANGUP_DEV); - return -2; + return -1; } return 0; } @@ -16203,7 +16574,6 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual * /* Failed transfer */ transmit_notify_with_sipfrag(transferer, seqno, "486 Busy Here", TRUE); append_history(transferer, "Xfer", "Refer failed"); - transferer->refer->status = REFER_FAILED; if (targetcall_pvt->owner) ast_channel_unlock(targetcall_pvt->owner); /* Right now, we have to hangup, sorry. Bridge is destroyed */ @@ -17362,15 +17732,11 @@ static int sipsock_read(int *id, int fd, short events, void *ignore) { struct sip_request req; struct sockaddr_in sin = { 0, }; - struct sip_pvt *p; int res; socklen_t len = sizeof(sin); - int nounlock; - int recount = 0; - int lockretry; memset(&req, 0, sizeof(req)); - res = recvfrom(sipsock, req.data, sizeof(req.data) - 1, 0, (struct sockaddr *)&sin, &len); + res = recvfrom(fd, req.data, sizeof(req.data) - 1, 0, (struct sockaddr *)&sin, &len); if (res < 0) { #if !defined(__FreeBSD__) if (errno == EAGAIN) @@ -17387,20 +17753,42 @@ static int sipsock_read(int *id, int fd, short events, void *ignore) } else req.data[res] = '\0'; req.len = res; - if(sip_debug_test_addr(&sin)) /* Set the debug flag early on packet level */ - req.debug = 1; + + req.socket.fd = sipsock; + req.socket.type = SIP_TRANSPORT_UDP; + req.socket.ser = NULL; + req.socket.port = htons(bindaddr.sin_port); + req.socket.lock = NULL; + + handle_request_do(&req, &sin); + + return 1; +} + +static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin) +{ + struct sip_pvt *p; + int recount = 0; + int nounlock = 0; + int lockretry; + + if (sip_debug_test_addr(sin)) /* Set the debug flag early on packet level */ + req->debug = 1; if (pedanticsipchecking) - req.len = lws2sws(req.data, req.len); /* Fix multiline headers */ - if (req.debug) - ast_verbose("\n<--- SIP read from %s:%d --->\n%s\n<------------->\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), req.data); + req->len = lws2sws(req->data, req->len); /* Fix multiline headers */ + if (req->debug) { + ast_verbose("\n<--- SIP read from %s://%s:%d --->\n%s\n<------------->\n", + get_transport(req->socket.type), ast_inet_ntoa(sin->sin_addr), + ntohs(sin->sin_port), req->data); + } - parse_request(&req); - req.method = find_sip_method(req.rlPart1); + parse_request(req); + req->method = find_sip_method(req->rlPart1); - if (req.debug) - ast_verbose("--- (%d headers %d lines)%s ---\n", req.headers, req.lines, (req.headers + req.lines == 0) ? " Nat keepalive" : ""); + if (req->debug) + ast_verbose("--- (%d headers %d lines)%s ---\n", req->headers, req->lines, (req->headers + req->lines == 0) ? " Nat keepalive" : ""); - if (req.headers < 2) /* Must have at least two headers */ + if (req->headers < 2) /* Must have at least two headers */ return 1; /* Process request, with netlock held, and with usual deadlock avoidance */ @@ -17408,12 +17796,15 @@ static int sipsock_read(int *id, int fd, short events, void *ignore) ast_mutex_lock(&netlock); /* Find the active SIP dialog or create a new one */ - p = find_call(&req, &sin, req.method); /* returns p locked */ + p = find_call(req, sin, req->method); /* returns p locked */ if (p == NULL) { - ast_debug(1, "Invalid SIP message - rejected , no callid, len %d\n", req.len); + ast_debug(1, "Invalid SIP message - rejected , no callid, len %d\n", req->len); ast_mutex_unlock(&netlock); return 1; } + + p->socket = req->socket; + /* Go ahead and lock the owner if it has one -- we may need it */ /* becaues this is deadlock-prone, we need to try and unlock if failed */ if (!p->owner || !ast_channel_trylock(p->owner)) @@ -17424,37 +17815,119 @@ static int sipsock_read(int *id, int fd, short events, void *ignore) /* Sleep for a very short amount of time */ usleep(1); } - p->recv = sin; + p->recv = *sin; if (p->do_history) /* This is a request or response, note what it was for */ - append_history(p, "Rx", "%s / %s / %s", req.data, get_header(&req, "CSeq"), req.rlPart2); + append_history(p, "Rx", "%s / %s / %s", req->data, get_header(req, "CSeq"), req->rlPart2); if (!lockretry) { if (p->owner) ast_log(LOG_ERROR, "We could NOT get the channel lock for %s! \n", S_OR(p->owner->name, "- no channel name ??? - ")); ast_log(LOG_ERROR, "SIP transaction failed: %s \n", p->callid); - if (req.method != SIP_ACK) - transmit_response(p, "503 Server error", &req); /* We must respond according to RFC 3261 sec 12.2 */ + if (req->method != SIP_ACK) + transmit_response(p, "503 Server error", req); /* We must respond according to RFC 3261 sec 12.2 */ /* XXX We could add retry-after to make sure they come back */ append_history(p, "LockFail", "Owner lock failed, transaction failed."); return 1; } + nounlock = 0; - if (handle_incoming(p, &req, &sin, &recount, &nounlock) == -1) { + if (handle_incoming(p, req, sin, &recount, &nounlock) == -1) { /* Request failed */ ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : ""); } + if (recount) + ast_update_use_count(); + if (p->owner && !nounlock) ast_channel_unlock(p->owner); sip_pvt_unlock(p); ast_mutex_unlock(&netlock); - if (recount) - ast_update_use_count(); return 1; } +static int sip_standard_port(struct sip_socket s) +{ + if (s.type & SIP_TRANSPORT_TLS) + return s.port == STANDARD_TLS_PORT; + else + return s.port == STANDARD_SIP_PORT; +} + +static struct server_instance *sip_tcp_locate(struct sockaddr_in *s) +{ + struct sip_threadinfo *th; + + AST_LIST_LOCK(&threadl); + AST_LIST_TRAVERSE(&threadl, th, list) { + if ((s->sin_family == th->ser->requestor.sin_family) && + (s->sin_addr.s_addr == th->ser->requestor.sin_addr.s_addr) && + (s->sin_port == th->ser->requestor.sin_port)) + return th->ser; + } + AST_LIST_UNLOCK(&threadl); + return NULL; +} + +static int sip_prepare_socket(struct sip_pvt *p) +{ + struct sip_socket *s = &p->socket; + static const char name[] = "SIP socket"; + struct server_instance *ser; + struct server_args ca = { + .name = name, + .accept_fd = -1, + }; + + if (s->fd != -1) + return s->fd; + + if (s->type & SIP_TRANSPORT_UDP) { + s->fd = sipsock; + return s->fd; + } + + ca.sin = *(sip_real_dst(p)); + + if ((ser = sip_tcp_locate(&ca.sin))) { + s->fd = ser->fd; + s->ser = ser; + return s->fd; + } + + if (s->ser && s->ser->parent->tls_cfg) + ca.tls_cfg = s->ser->parent->tls_cfg; + else { + if (s->type & SIP_TRANSPORT_TLS) { + ca.tls_cfg = ast_calloc(1, sizeof(*ca.tls_cfg)); + if (!ca.tls_cfg) + return -1; + memcpy(ca.tls_cfg, &default_tls_cfg, sizeof(*ca.tls_cfg)); + if (!ast_strlen_zero(p->tohost)) + ast_copy_string(ca.hostname, p->tohost, sizeof(ca.hostname)); + } + } + s->ser = (!s->ser) ? client_start(&ca) : s->ser; + + if (!s->ser) { + if (ca.tls_cfg) + ast_free(ca.tls_cfg); + return -1; + } + + s->fd = ca.accept_fd; + + if (ast_pthread_create_background(&ca.master, NULL, sip_tcp_helper_thread, p)) { + ast_log(LOG_DEBUG, "Unable to launch '%s'.", ca.name); + close(ca.accept_fd); + s->fd = ca.accept_fd = -1; + } + + return s->fd; +} + /*! * \brief Get cached MWI info * \retval 0 At least one message is waiting @@ -18089,6 +18562,7 @@ static int sip_poke_peer(struct sip_peer *peer) p->sa = peer->addr; p->recv = peer->addr; + p->socket = peer->socket; ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); @@ -18691,7 +19165,6 @@ static struct sip_user *build_user(const char *name, struct ast_variable *v, int for (; v; v = v->next) { if (handle_common_options(&userflags[0], &mask[0], v)) continue; - if (!strcasecmp(v->name, "context")) { ast_copy_string(user->context, v->value, sizeof(user->context)); } else if (!strcasecmp(v->name, "subscribecontext")) { @@ -18846,11 +19319,12 @@ static void set_peer_defaults(struct sip_peer *peer) peer->pickupgroup = 0; peer->maxms = default_qualify; peer->prefs = default_prefs; + peer->socket.type = SIP_TRANSPORT_UDP; + peer->socket.fd = -1; peer->stimer.st_mode_oper = global_st_mode; /* Session-Timers */ peer->stimer.st_ref = global_st_refresher; peer->stimer.st_min_se = global_min_se; peer->stimer.st_max_se = global_max_se; - peer->timer_t1 = global_t1; peer->timer_b = global_timer_b; clear_peer_mailboxes(peer); @@ -18914,6 +19388,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str struct ast_flags peerflags[2] = {{(0)}}; struct ast_flags mask[2] = {{(0)}}; char callback[256] = ""; + const char *srvlookup = NULL; if (!realtime) /* Note we do NOT use find_peer here, to avoid realtime recursion */ @@ -18963,7 +19438,14 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str for (; v || ((v = alt) && !(alt=NULL)); v = v->next) { if (handle_common_options(&peerflags[0], &mask[0], v)) continue; - if (realtime && !strcasecmp(v->name, "regseconds")) { + if (!strcasecmp(v->name, "transport")) { + if (!strcasecmp(v->value, "udp")) + peer->socket.type = SIP_TRANSPORT_UDP; + else if (!strcasecmp(v->value, "tcp")) + peer->socket.type = SIP_TRANSPORT_TCP; + else if (!strcasecmp(v->value, "tls")) + peer->socket.type = SIP_TRANSPORT_TLS; + } else if (realtime && !strcasecmp(v->name, "regseconds")) { ast_get_time_t(v->value, ®seconds, 0, NULL); } else if (realtime && !strcasecmp(v->name, "ipaddr") && !ast_strlen_zero(v->value) ) { inet_aton(v->value, &(peer->addr.sin_addr)); @@ -19029,14 +19511,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str ast_sched_del(sched, peer->expire); peer->expire = -1; peer->host_dynamic = FALSE; - if (ast_get_ip_or_srv(&peer->addr, v->value, global_srvlookup ? "_sip._udp" : NULL)) { - unref_peer(peer); - return NULL; - } - - ast_copy_string(peer->tohost, v->value, sizeof(peer->tohost)); - if (!peer->addr.sin_port) - peer->addr.sin_port = htons(STANDARD_SIP_PORT); + srvlookup = v->value; } } else if (!strcasecmp(v->name, "defaultip")) { if (ast_get_ip(&peer->defaddr, v->value)) { @@ -19193,6 +19668,27 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str } } } + + if (srvlookup) { + if (ast_get_ip_or_srv(&peer->addr, srvlookup, + global_srvlookup ? + ((peer->socket.type & SIP_TRANSPORT_UDP) ? "_sip._udp" : + (peer->socket.type & SIP_TRANSPORT_TCP) ? "_sip._tcp" : + "_sip._tls") + : NULL)) { + unref_peer(peer); + return NULL; + } + + ast_copy_string(peer->tohost, srvlookup, sizeof(peer->tohost)); + } + + if (!peer->addr.sin_port) + peer->addr.sin_port = htons(((peer->socket.type & SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT)); + + if (!peer->socket.port) + peer->socket.port = htons(((peer->socket.type & SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT)); + if (!sip_cfg.ignore_regexpire && peer->host_dynamic && realtime) { time_t nowtime = time(NULL); @@ -19273,6 +19769,15 @@ static int reload_config(enum channelreloadreason reason) ucfg = ast_config_load("users.conf", config_flags); } + /* Initialize tcp sockets */ + memset(&sip_tcp_desc.sin, 0, sizeof(sip_tcp_desc.sin)); + memset(&sip_tls_desc.sin, 0, sizeof(sip_tls_desc.sin)); + + sip_tcp_desc.sin.sin_family = AF_INET; + + sip_tcp_desc.sin.sin_port = htons(STANDARD_SIP_PORT); + sip_tls_desc.sin.sin_port = htons(STANDARD_TLS_PORT); + if (reason != CHANNEL_MODULE_LOAD) { ast_debug(4, "--------------- SIP reload started\n"); @@ -19301,6 +19806,11 @@ static int reload_config(enum channelreloadreason reason) ASTOBJ_CONTAINER_MARKALL(&peerl); } + default_tls_cfg.certfile = ast_strdup(AST_CERTFILE); /*XXX Not sure if this is useful */ + default_tls_cfg.cipher = ast_strdup(""); + default_tls_cfg.cafile = ast_strdup(""); + default_tls_cfg.capath = ast_strdup(""); + /* Initialize copy of current global_regcontext for later use in removing stale contexts */ ast_copy_string(oldcontexts, global_regcontext, sizeof(oldcontexts)); oldregcontext = oldcontexts; @@ -19323,6 +19833,8 @@ static int reload_config(enum channelreloadreason reason) memset(&global_outboundproxy, 0, sizeof(struct sip_proxy)); global_outboundproxy.ip.sin_port = htons(STANDARD_SIP_PORT); global_outboundproxy.ip.sin_family = AF_INET; /* Type of address: IPv4 */ + ourport_tcp = STANDARD_SIP_PORT; + ourport_tls = STANDARD_TLS_PORT; bindaddr.sin_port = htons(STANDARD_SIP_PORT); externip.sin_port = htons(STANDARD_SIP_PORT); global_srvlookup = DEFAULT_SRVLOOKUP; @@ -19456,6 +19968,33 @@ static int reload_config(enum channelreloadreason reason) sip_cfg.ignore_regexpire = ast_true(v->value); } else if (!strcasecmp(v->name, "t1min")) { global_t1min = atoi(v->value); + } else if (!strcasecmp(v->name, "tcpenable")) { + sip_tcp_desc.sin.sin_family = ast_false(v->value) ? 0 : AF_INET; + } else if (!strcasecmp(v->name, "tcpbindaddr")) { + if (ast_parse_arg(v->value, PARSE_INADDR, &sip_tcp_desc.sin)) + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config); + } else if (!strcasecmp(v->name, "tlsenable")) { + default_tls_cfg.enabled = ast_true(v->value) ? TRUE : FALSE; + sip_tls_desc.sin.sin_family = AF_INET; + } else if (!strcasecmp(v->name, "tlscertfile")) { + ast_free(default_tls_cfg.certfile); + default_tls_cfg.certfile = ast_strdup(v->value); + } else if (!strcasecmp(v->name, "tlscipher")) { + ast_free(default_tls_cfg.cipher); + default_tls_cfg.cipher = ast_strdup(v->value); + } else if (!strcasecmp(v->name, "tlscafile")) { + ast_free(default_tls_cfg.cafile); + default_tls_cfg.cafile = ast_strdup(v->value); + } else if (!strcasecmp(v->name, "tlscapath")) { + ast_free(default_tls_cfg.capath); + default_tls_cfg.capath = ast_strdup(v->value); + } else if (!strcasecmp(v->name, "tlsverifyclient")) { + ast_set2_flag(&default_tls_cfg.flags, ast_true(v->value), AST_SSL_VERIFY_CLIENT); + } else if (!strcasecmp(v->name, "tlsdontverifyserver")) { + ast_set2_flag(&default_tls_cfg.flags, ast_true(v->value), AST_SSL_DONT_VERIFY_SERVER); + } else if (!strcasecmp(v->name, "tlsbindaddr")) { + if (ast_parse_arg(v->value, PARSE_INADDR, &sip_tls_desc.sin)) + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config); } else if (!strcasecmp(v->name, "rtautoclear")) { int i = atoi(v->value); if (i > 0) @@ -19911,6 +20450,16 @@ static int reload_config(enum channelreloadreason reason) ast_config_destroy(notify_types); notify_types = ast_config_load(notify_config, config_flags); + memcpy(sip_tls_desc.tls_cfg, &default_tls_cfg, sizeof(default_tls_cfg)); + server_start(&sip_tcp_desc); + + if (ssl_setup(sip_tls_desc.tls_cfg)) + server_start(&sip_tls_desc); + else if (sip_tls_desc.tls_cfg->enabled) { + sip_tls_desc.tls_cfg = NULL; + ast_log(LOG_WARNING, "SIP TLS server did not load because of errors.\n"); + } + /* Done, tell the manager */ manager_event(EVENT_FLAG_SYSTEM, "ChannelReload", "ChannelType: SIP\r\nReloadReason: %s\r\nRegistry_Count: %d\r\nPeer_Count: %d\r\nUser_Count: %d\r\n", channelreloadreason2txt(reason), registry_count, peer_count, user_count); @@ -20165,16 +20714,9 @@ static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struc memset(&p->tredirip, 0, sizeof(p->tredirip)); changed = 1; } - if (codecs) { - if ((p->redircodecs != codecs)) { - p->redircodecs = codecs; - changed = 1; - } - if ((p->capability & codecs) != p->capability) { - p->jointcapability &= codecs; - p->capability &= codecs; - changed = 1; - } + if (codecs && (p->redircodecs != codecs)) { + p->redircodecs = codecs; + changed = 1; } if (changed && !ast_test_flag(&p->flags[0], SIP_GOTREFER) && !ast_test_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { if (chan->_state != AST_STATE_UP) { /* We are in early state */ @@ -20335,7 +20877,8 @@ static int sip_sipredirect(struct sip_pvt *p, const char *dest) ast_log(LOG_ERROR, "Cannot retrieve the 'To' header from the original SIP request!\n"); return 0; } - if ((localtmp = strcasestr(tmp, "sip:")) && (localtmp = strchr(localtmp, '@'))) { + if ( ( (localtmp = strcasestr(tmp, "sip:")) || (localtmp = strcasestr(tmp, "sips:")) ) + && (localtmp = strchr(localtmp, '@'))) { char lhost[80], lport[80]; memset(lhost, 0, sizeof(lhost)); @@ -20489,6 +21032,7 @@ static struct ast_cli_entry cli_sip[] = { AST_CLI_DEFINE(sip_do_history, "Enable SIP history"), AST_CLI_DEFINE(sip_no_history, "Disable SIP history"), AST_CLI_DEFINE(sip_reload, "Reload SIP configuration"), + AST_CLI_DEFINE(sip_show_tcp, "List TCP Connections") }; /*! \brief PBX load module - initialization */ @@ -20569,6 +21113,7 @@ static int load_module(void) static int unload_module(void) { struct sip_pvt *p, *pl; + struct sip_threadinfo *th; struct ast_context *con; /* First, take us out of the channel type list */ @@ -20598,6 +21143,25 @@ static int unload_module(void) ast_manager_unregister("SIPshowpeer"); ast_manager_unregister("SIPshowregistry"); + /* Kill TCP/TLS server threads */ + if (sip_tcp_desc.master) + server_stop(&sip_tcp_desc); + if (sip_tls_desc.master) + server_stop(&sip_tls_desc); + + /* Kill all existing TCP/TLS threads */ + AST_LIST_LOCK(&threadl); + AST_LIST_TRAVERSE_SAFE_BEGIN(&threadl, th, list) { + pthread_t thread = th->threadid; + th->stop = 1; + AST_LIST_UNLOCK(&threadl); + pthread_kill(thread, SIGURG); + pthread_join(thread, NULL); + AST_LIST_LOCK(&threadl); + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&threadl); + dialoglist_lock(); /* Hangup all dialogs if they have an owner */ for (p = dialoglist; p ; p = p->next) { @@ -20629,6 +21193,15 @@ static int unload_module(void) /* Free memory for local network address mask */ ast_free_ha(localaddr); + if (default_tls_cfg.certfile) + ast_free(default_tls_cfg.certfile); + if (default_tls_cfg.cipher) + ast_free(default_tls_cfg.cipher); + if (default_tls_cfg.cafile) + ast_free(default_tls_cfg.cafile); + if (default_tls_cfg.capath) + ast_free(default_tls_cfg.capath); + ASTOBJ_CONTAINER_DESTROYALL(&userl, sip_destroy_user); ASTOBJ_CONTAINER_DESTROY(&userl); ASTOBJ_CONTAINER_DESTROYALL(&peerl, sip_destroy_peer); diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index acca09c6c..62eee41bb 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -70,6 +70,16 @@ allowoverlap=no ; Disable overlap dialing support. (Default is yes) bindport=5060 ; UDP Port to bind to (SIP standard port is 5060) ; bindport is the local UDP port that Asterisk will listen on bindaddr=0.0.0.0 ; IP address to bind to (0.0.0.0 binds to all) + +tcpenable=yes ; Enable server for incoming TCP connections (default is yes) +tcpbindaddr=0.0.0.0 ; IP address for TCP server to bind to (0.0.0.0 binds to all interfaces) + ; Optionally add a port number, 192.168.1.1:5062 (default is port 5060) + +;tlsenable=no ; Enable server for incoming TLS (secure) connections (default is no) +;tlsbindaddr=0.0.0.0 ; IP address for TLS server to bind to (0.0.0.0) binds to all interfaces) + ; Optionally add a port number, 192.168.1.1:5063 (default is port 5061) +;tlscertfile=asterisk.pem ; Certificate file (*.pem only) to use for TLS connections + ; default is to look for "asterisk.pem" in current directory srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; Note: Asterisk only uses the first host ; in SRV records @@ -320,7 +330,9 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ;----------------------------------------- OUTBOUND SIP REGISTRATIONS ------------------------ ; Asterisk can register as a SIP user agent to a SIP proxy (provider) ; Format for the register statement is: -; register => user[:secret[:authuser]]@host[:port][/extension] +; register => [transport://]user[:secret[:authuser]]@host[:port][/extension] +; +; ; ; If no extension is given, the 's' extension is used. The extension needs to ; be defined in extensions.conf to be able to accept calls from this SIP proxy @@ -607,7 +619,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; User config options: Peer configuration: ; -------------------- ------------------- ; context context -; callingpres callingpres +; callingpres callingpres ; permit permit ; deny deny ; secret secret diff --git a/doc/siptls.txt b/doc/siptls.txt new file mode 100644 index 000000000..3a54bf095 --- /dev/null +++ b/doc/siptls.txt @@ -0,0 +1,94 @@ +Asterisk SIP/TLS Transport +========================== + +When using TLS the client will typically check the validity of the +certificate chain. So that means you either need a certificate that is +signed by one of the larger CAs, or if you use a self signed certificate +you must install a copy of your CA on the client. + +So far this code has been test with: +Asterisk as client and server (TLS and TCP) +Polycom Soundpoint IP Phones (TLS and TCP) + Polycom phones require that the host (ip or hostname) that is + configured match the 'common name' in the certificate +Minisip Softphone (TLS and TCP) +Cisco IOS Gateways (TCP only) +SNOM 360 (TLS only) +Zoiper Biz Softphone (TLS and TCP) + + +sip.conf options +---------------- +tlsenable=[yes|no] + Enable TLS server, default is no + +tlsbindaddr= + Specify IP address to bind TLS server to, default is 0.0.0.0 + +tlscertfile= + The server's certificate file. Should include the key and + certificate. This is mandatory if your going to run a TLS server. + +tlscafile= + If the server your connecting to uses a self signed certificate + you should have their certificate installed here so the code can + verify the authenticity of their certificate. + +tlscadir= + A directory full of CA certificates. The files must be named with + the CA subject name hash value. + (see man SSL_CTX_load_verify_locations for more info) + +tlsdontverifyserver=[yes|no] + If set to yes, don't verify the servers certificate when acting as + a client. If you don't have the server's CA certificate you can + set this and it will connect without requiring tlscafile to be set. + Default is no. + +tlscipher= + A string specifying which SSL ciphers to use or not use + + +Sample config +------------- + +Here are the relevant bits of config for setting up TLS between 2 +asterisk servers. With server_a registering to server_b + +On server_a: +[general] +tlsenable=yes +tlscertfgile=/etc/asterisk/asterisk.pem +tlscafile=/etc/ssl/ca.pem ; This is the CA file used to generate both certificates +register => tls://100:test@192.168.0.100:5061 + +[101] +type=friend +context=internal +host=192.168.0.100 ; The host should be either IP or hostname and should + ; match the 'common name' field in the servers certificate +secret=test +dtmfmode=rfc2833 +disallow=all +allow=ulaw +transport=tls +port=5061 + +On server_b: +[general] +tlsenable=yes +tlscertfgile=/etc/asterisk/asterisk.pem + +[100] +type=friend +context=internal +host=dynamic +secret=test +dtmfmode=rfc2833 +disallow=all +allow=ulaw +;You can specify transport= and port=5061 for TLS, but its not necessary in +;the server configuration, any type of SIP transport will work +;transport=tls +;port=5061 + diff --git a/include/asterisk/http.h b/include/asterisk/http.h index b0215221e..4cda5cacc 100644 --- a/include/asterisk/http.h +++ b/include/asterisk/http.h @@ -20,6 +20,8 @@ #define _ASTERISK_HTTP_H #include "asterisk/config.h" +#include "asterisk/tcptls.h" +#include "asterisk/linkedlists.h" /*! * \file http.h @@ -50,90 +52,6 @@ * be run earlier in the startup process so modules have it available. */ -#if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE)) -#define DO_SSL /* comment in/out if you want to support ssl */ -#endif - -#ifdef DO_SSL -#include -#include -#else -/* declare dummy types so we can define a pointer to them */ -typedef struct {} SSL; -typedef struct {} SSL_CTX; -#endif /* DO_SSL */ - -/*! SSL support */ -#define AST_CERTFILE "asterisk.pem" - -struct tls_config { - int enabled; - char *certfile; - char *cipher; - SSL_CTX *ssl_ctx; -}; - -/*! - * The following code implements a generic mechanism for starting - * services on a TCP or TLS socket. - * The service is configured in the struct server_args, and - * then started by calling server_start(desc) on the descriptor. - * server_start() first verifies if an instance of the service is active, - * and in case shuts it down. Then, if the service must be started, creates - * a socket and a thread in charge of doing the accept(). - * - * The body of the thread is desc->accept_fn(desc), which the user can define - * freely. We supply a sample implementation, server_root(), structured as an - * infinite loop. At the beginning of each iteration it runs periodic_fn() - * if defined (e.g. to perform some cleanup etc.) then issues a poll() - * or equivalent with a timeout of 'poll_timeout' milliseconds, and if the - * following accept() is successful it creates a thread in charge of - * running the session, whose body is desc->worker_fn(). The argument of - * worker_fn() is a struct server_instance, which contains the address - * of the other party, a pointer to desc, the file descriptors (fd) on which - * we can do a select/poll (but NOT IO/, and a FILE * on which we can do I/O. - * We have both because we want to support plain and SSL sockets, and - * going through a FILE * lets us provide the encryption/decryption - * on the stream without using an auxiliary thread. - * - * NOTE: in order to let other parts of asterisk use these services, - * we need to do the following: - * + move struct server_instance and struct server_args to - * a common header file, together with prototypes for - * server_start() and server_root(). - * + - */ - -/*! - * describes a server instance - */ -struct server_instance { - FILE *f; /* fopen/funopen result */ - int fd; /* the socket returned by accept() */ - SSL *ssl; /* ssl state */ - struct sockaddr_in requestor; - struct server_args *parent; -}; - -/*! - * arguments for the accepting thread - */ -struct server_args { - struct sockaddr_in sin; - struct sockaddr_in oldsin; - struct tls_config *tls_cfg; /* points to the SSL configuration if any */ - int accept_fd; - int poll_timeout; - pthread_t master; - void *(*accept_fn)(void *); /* the function in charge of doing the accept */ - void (*periodic_fn)(void *); /* something we may want to run before after select on the accept socket */ - void *(*worker_fn)(void *); /* the function in charge of doing the actual work */ - const char *name; -}; - -void *server_root(void *); -void server_start(struct server_args *desc); -int ssl_setup(struct tls_config *cfg); /*! \brief HTTP Callbacks take the socket diff --git a/include/asterisk/tcptls.h b/include/asterisk/tcptls.h new file mode 100644 index 000000000..fe4743f33 --- /dev/null +++ b/include/asterisk/tcptls.h @@ -0,0 +1,166 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file server.h + * + * \brief Generic support for tcp/tls servers in Asterisk. + * \note In order to have TLS/SSL support, we need the openssl libraries. + * Still we can decide whether or not to use them by commenting + * in or out the DO_SSL macro. + * TLS/SSL support is basically implemented by reading from a config file + * (currently http.conf) the names of the certificate and cipher to use, + * and then run ssl_setup() to create an appropriate SSL_CTX (ssl_ctx) + * If we support multiple domains, presumably we need to read multiple + * certificates. + * When we are requested to open a TLS socket, we run make_file_from_fd() + * on the socket, to do the necessary setup. At the moment the context's name + * is hardwired in the function, but we can certainly make it into an extra + * parameter to the function. + * We declare most of ssl support variables unconditionally, + * because their number is small and this simplifies the code. + * + * \note: the ssl-support variables (ssl_ctx, do_ssl, certfile, cipher) + * and their setup should be moved to a more central place, e.g. asterisk.conf + * and the source files that processes it. Similarly, ssl_setup() should + * be run earlier in the startup process so modules have it available. + * + */ + + +#ifndef _ASTERISK_SERVER_H +#define _ASTERISK_SERVER_H + +#include "asterisk/utils.h" + +#if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE)) +#define DO_SSL /* comment in/out if you want to support ssl */ +#endif + +#ifdef DO_SSL +#include +#include +#else +/* declare dummy types so we can define a pointer to them */ +typedef struct {} SSL; +typedef struct {} SSL_CTX; +#endif /* DO_SSL */ + +/*! SSL support */ +#define AST_CERTFILE "asterisk.pem" + +enum ast_ssl_flags { + /*! Verify certificate when acting as server */ + AST_SSL_VERIFY_CLIENT = (1 << 0), + /*! Don't verify certificate when connecting to a server */ + AST_SSL_DONT_VERIFY_SERVER = (1 << 1), + /*! Don't compare "Common Name" against IP or hostname */ + AST_SSL_IGNORE_COMMON_NAME = (1 << 2) +}; + +struct ast_tls_config { + int enabled; + char *certfile; + char *cipher; + char *cafile; + char *capath; + struct ast_flags flags; + SSL_CTX *ssl_ctx; +}; + +/*! + * The following code implements a generic mechanism for starting + * services on a TCP or TLS socket. + * The service is configured in the struct server_args, and + * then started by calling server_start(desc) on the descriptor. + * server_start() first verifies if an instance of the service is active, + * and in case shuts it down. Then, if the service must be started, creates + * a socket and a thread in charge of doing the accept(). + * + * The body of the thread is desc->accept_fn(desc), which the user can define + * freely. We supply a sample implementation, server_root(), structured as an + * infinite loop. At the beginning of each iteration it runs periodic_fn() + * if defined (e.g. to perform some cleanup etc.) then issues a poll() + * or equivalent with a timeout of 'poll_timeout' milliseconds, and if the + * following accept() is successful it creates a thread in charge of + * running the session, whose body is desc->worker_fn(). The argument of + * worker_fn() is a struct server_instance, which contains the address + * of the other party, a pointer to desc, the file descriptors (fd) on which + * we can do a select/poll (but NOT IO/, and a FILE *on which we can do I/O. + * We have both because we want to support plain and SSL sockets, and + * going through a FILE *lets us provide the encryption/decryption + * on the stream without using an auxiliary thread. + * + * NOTE: in order to let other parts of asterisk use these services, + * we need to do the following: + * + move struct server_instance and struct server_args to + * a common header file, together with prototypes for + * server_start() and server_root(). + */ + +/*! + * describes a server instance + */ +struct server_instance { + FILE *f; /* fopen/funopen result */ + int fd; /* the socket returned by accept() */ + SSL *ssl; /* ssl state */ +// iint (*ssl_setup)(SSL *); + int client; + struct sockaddr_in requestor; + struct server_args *parent; +}; + +/*! + * arguments for the accepting thread + */ +struct server_args { + struct sockaddr_in sin; + struct sockaddr_in oldsin; + char hostname[MAXHOSTNAMELEN]; /* only necessary for SSL clients so we can compare to common name */ + struct ast_tls_config *tls_cfg; /* points to the SSL configuration if any */ + int accept_fd; + int poll_timeout; + pthread_t master; + void *(*accept_fn)(void *); /* the function in charge of doing the accept */ + void (*periodic_fn)(void *);/* something we may want to run before after select on the accept socket */ + void *(*worker_fn)(void *); /* the function in charge of doing the actual work */ + const char *name; +}; + +#if defined(HAVE_FUNOPEN) +#define HOOK_T int +#define LEN_T int +#else +#define HOOK_T ssize_t +#define LEN_T size_t +#endif + +struct server_instance *client_start(struct server_args *desc); + +void *server_root(void *); +void server_start(struct server_args *desc); +void server_stop(struct server_args *desc); +int ssl_setup(struct ast_tls_config *cfg); + +void *ast_make_file_from_fd(void *data); + +HOOK_T server_read(struct server_instance *ser, void *buf, size_t count); +HOOK_T server_write(struct server_instance *ser, void *buf, size_t count); + +#endif /* _ASTERISK_SERVER_H */ diff --git a/main/Makefile b/main/Makefile index 9389ee3f6..3504b547b 100644 --- a/main/Makefile +++ b/main/Makefile @@ -19,7 +19,7 @@ include $(ASTTOPDIR)/Makefile.moddir_rules RESAMPLE_OBJS:=libresample/src/resample.o libresample/src/resamplesubs.o libresample/src/filterkit.o -OBJS= io.o sched.o logger.o frame.o loader.o config.o channel.o \ +OBJS= tcptls.o io.o sched.o logger.o frame.o loader.o config.o channel.o \ translate.o file.o pbx.o cli.o md5.o term.o \ ulaw.o alaw.o callerid.o fskmodem.o image.o app.o \ cdr.o tdd.o acl.o rtp.o udptl.o manager.o asterisk.o \ diff --git a/main/http.c b/main/http.c index c423e56e0..6e8021fa2 100644 --- a/main/http.c +++ b/main/http.c @@ -43,6 +43,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "minimime/mm.h" #include "asterisk/cli.h" +#include "asterisk/tcptls.h" #include "asterisk/http.h" #include "asterisk/utils.h" #include "asterisk/strings.h" @@ -59,7 +60,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define DO_SSL /* comment in/out if you want to support ssl */ #endif -static struct tls_config http_tls_cfg; +static struct ast_tls_config http_tls_cfg; static void *httpd_helper_thread(void *arg); @@ -647,7 +648,7 @@ cleanup: * We use wrappers rather than SSL_read/SSL_write directly so * we can put in some debugging. */ -static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len) +/*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len) { int i = SSL_read(cookie, buf, len-1); #if 0 @@ -675,55 +676,9 @@ static int ssl_close(void *cookie) SSL_shutdown(cookie); SSL_free(cookie); return 0; -} +}*/ #endif /* DO_SSL */ -/*! - * creates a FILE * from the fd passed by the accept thread. - * This operation is potentially expensive (certificate verification), - * so we do it in the child thread context. - */ -static void *make_file_from_fd(void *data) -{ - struct server_instance *ser = data; - - /* - * open a FILE * as appropriate. - */ - if (!ser->parent->tls_cfg) - ser->f = fdopen(ser->fd, "w+"); -#ifdef DO_SSL - else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) { - SSL_set_fd(ser->ssl, ser->fd); - if (SSL_accept(ser->ssl) == 0) - ast_verbose(" error setting up ssl connection"); - else { -#if defined(HAVE_FUNOPEN) /* the BSD interface */ - ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close); - -#elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */ - static const cookie_io_functions_t cookie_funcs = { - ssl_read, ssl_write, NULL, ssl_close - }; - ser->f = fopencookie(ser->ssl, "w+", cookie_funcs); -#else - /* could add other methods here */ -#endif - } - if (!ser->f) /* no success opening descriptor stacking */ - SSL_free(ser->ssl); - } -#endif /* DO_SSL */ - - if (!ser->f) { - close(ser->fd); - ast_log(LOG_WARNING, "FILE * open failed!\n"); - ast_free(ser); - return NULL; - } - return ser->parent->worker_fn(ser); -} - static void *httpd_helper_thread(void *data) { char buf[4096]; @@ -876,153 +831,6 @@ done: return NULL; } -void *server_root(void *data) -{ - struct server_args *desc = data; - int fd; - struct sockaddr_in sin; - socklen_t sinlen; - struct server_instance *ser; - pthread_t launched; - - for (;;) { - int i, flags; - - if (desc->periodic_fn) - desc->periodic_fn(desc); - i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout); - if (i <= 0) - continue; - sinlen = sizeof(sin); - fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen); - if (fd < 0) { - if ((errno != EAGAIN) && (errno != EINTR)) - ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno)); - continue; - } - ser = ast_calloc(1, sizeof(*ser)); - if (!ser) { - ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno)); - close(fd); - continue; - } - flags = fcntl(fd, F_GETFL); - fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); - ser->fd = fd; - ser->parent = desc; - memcpy(&ser->requestor, &sin, sizeof(ser->requestor)); - - if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) { - ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno)); - close(ser->fd); - ast_free(ser); - } - - } - return NULL; -} - -int ssl_setup(struct tls_config *cfg) -{ -#ifndef DO_SSL - cfg->enabled = 0; - return 0; -#else - if (!cfg->enabled) - return 0; - SSL_load_error_strings(); - SSLeay_add_ssl_algorithms(); - cfg->ssl_ctx = SSL_CTX_new( SSLv23_server_method() ); - if (!ast_strlen_zero(cfg->certfile)) { - if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 || - SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 || - SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) { - ast_verbose("ssl cert error <%s>", cfg->certfile); - sleep(2); - cfg->enabled = 0; - return 0; - } - } - if (!ast_strlen_zero(cfg->cipher)) { - if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) { - ast_verbose("ssl cipher error <%s>", cfg->cipher); - sleep(2); - cfg->enabled = 0; - return 0; - } - } - ast_verbose("ssl cert ok"); - return 1; -#endif -} - -/*! - * This is a generic (re)start routine for a TCP server, - * which does the socket/bind/listen and starts a thread for handling - * accept(). - */ -void server_start(struct server_args *desc) -{ - int flags; - int x = 1; - - /* Do nothing if nothing has changed */ - if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) { - ast_debug(1, "Nothing changed in %s\n", desc->name); - return; - } - - desc->oldsin = desc->sin; - - /* Shutdown a running server if there is one */ - if (desc->master != AST_PTHREADT_NULL) { - pthread_cancel(desc->master); - pthread_kill(desc->master, SIGURG); - pthread_join(desc->master, NULL); - } - - if (desc->accept_fd != -1) - close(desc->accept_fd); - - /* If there's no new server, stop here */ - if (desc->sin.sin_family == 0) - return; - - desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0); - if (desc->accept_fd < 0) { - ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n", - desc->name, strerror(errno)); - return; - } - - setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); - if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) { - ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n", - desc->name, - ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port), - strerror(errno)); - goto error; - } - if (listen(desc->accept_fd, 10)) { - ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name); - goto error; - } - flags = fcntl(desc->accept_fd, F_GETFL); - fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK); - if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) { - ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n", - desc->name, - ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port), - strerror(errno)); - goto error; - } - return; - -error: - close(desc->accept_fd); - desc->accept_fd = -1; -} - /*! * \brief Add a new URI redirect * The entries in the redirect list are sorted by length, just like the list diff --git a/main/manager.c b/main/manager.c index 8e87af713..c9e69eb5b 100644 --- a/main/manager.c +++ b/main/manager.c @@ -65,6 +65,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/md5.h" #include "asterisk/acl.h" #include "asterisk/utils.h" +#include "asterisk/tcptls.h" #include "asterisk/http.h" #include "asterisk/version.h" #include "asterisk/threadstorage.h" @@ -3425,7 +3426,7 @@ static void purge_old_stuff(void *data) purge_events(); } -struct tls_config ami_tls_cfg; +struct ast_tls_config ami_tls_cfg; static struct server_args ami_desc = { .accept_fd = -1, .master = AST_PTHREADT_NULL, diff --git a/main/tcptls.c b/main/tcptls.c new file mode 100644 index 000000000..27faa0c18 --- /dev/null +++ b/main/tcptls.c @@ -0,0 +1,452 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007 - 2008, Digium, Inc. + * + * Luigi Rizzo (TCP and TLS server code) + * Brett Bryant (updated for client support) + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Code to support TCP and TLS server/client + * + * \author Luigi Rizzo + * \author Brett Bryant + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include + +#include "asterisk/compat.h" +#include "asterisk/tcptls.h" +#include "asterisk/http.h" +#include "asterisk/utils.h" +#include "asterisk/strings.h" +#include "asterisk/options.h" +#include "asterisk/manager.h" + +/*! + * replacement read/write functions for SSL support. + * We use wrappers rather than SSL_read/SSL_write directly so + * we can put in some debugging. + */ + +#ifdef DO_SSL +static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len) +{ + int i = SSL_read(cookie, buf, len-1); +#if 0 + if (i >= 0) + buf[i] = '\0'; + ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf); +#endif + return i; +} + +static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len) +{ +#if 0 + char *s = alloca(len+1); + strncpy(s, buf, len); + s[len] = '\0'; + ast_verbose("ssl write size %d <%s>\n", (int)len, s); +#endif + return SSL_write(cookie, buf, len); +} + +static int ssl_close(void *cookie) +{ + close(SSL_get_fd(cookie)); + SSL_shutdown(cookie); + SSL_free(cookie); + return 0; +} +#endif /* DO_SSL */ + +HOOK_T server_read(struct server_instance *ser, void *buf, size_t count) +{ + if (!ser->ssl) + return read(ser->fd, buf, count); +#ifdef DO_SSL + else + return ssl_read(ser->ssl, buf, count); +#endif +} + +HOOK_T server_write(struct server_instance *ser, void *buf, size_t count) +{ + if (!ser->ssl) + return write(ser->fd, buf, count); +#ifdef DO_SSL + else + return ssl_write(ser->ssl, buf, count); +#endif +} + +void *server_root(void *data) +{ + struct server_args *desc = data; + int fd; + struct sockaddr_in sin; + socklen_t sinlen; + struct server_instance *ser; + pthread_t launched; + + for (;;) { + int i, flags; + + if (desc->periodic_fn) + desc->periodic_fn(desc); + i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout); + if (i <= 0) + continue; + sinlen = sizeof(sin); + fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen); + if (fd < 0) { + if ((errno != EAGAIN) && (errno != EINTR)) + ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno)); + continue; + } + ser = ast_calloc(1, sizeof(*ser)); + if (!ser) { + ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno)); + close(fd); + continue; + } + flags = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + ser->fd = fd; + ser->parent = desc; + memcpy(&ser->requestor, &sin, sizeof(ser->requestor)); + + ser->client = 0; + + if (ast_pthread_create_detached_background(&launched, NULL, ast_make_file_from_fd, ser)) { + ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno)); + close(ser->fd); + ast_free(ser); + } + } + return NULL; +} + +static int __ssl_setup(struct ast_tls_config *cfg, int client) +{ +#ifndef DO_SSL + cfg->enabled = 0; + return 0; +#else + if (!cfg->enabled) + return 0; + + SSL_load_error_strings(); + SSLeay_add_ssl_algorithms(); + + if (!(cfg->ssl_ctx = SSL_CTX_new( client ? SSLv23_client_method() : SSLv23_server_method() ))) { + ast_log(LOG_DEBUG, "Sorry, SSL_CTX_new call returned null...\n"); + cfg->enabled = 0; + return 0; + } + if (!ast_strlen_zero(cfg->certfile)) { + if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 || + SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 || + SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) { + if (!client) { + /* Clients don't need a certificate, but if its setup we can use it */ + ast_verbose("ssl cert error <%s>", cfg->certfile); + sleep(2); + cfg->enabled = 0; + return 0; + } + } + } + if (!ast_strlen_zero(cfg->cipher)) { + if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) { + if (!client) { + ast_verbose("ssl cipher error <%s>", cfg->cipher); + sleep(2); + cfg->enabled = 0; + return 0; + } + } + } + if (!ast_strlen_zero(cfg->cafile) || !ast_strlen_zero(cfg->capath)) { + if (SSL_CTX_load_verify_locations(cfg->ssl_ctx, S_OR(cfg->cafile, NULL), S_OR(cfg->capath,NULL)) == 0) + ast_verbose("ssl CA file(%s)/path(%s) error\n", cfg->cafile, cfg->capath); + } + + ast_verbose("ssl cert ok\n"); + return 1; +#endif +} + +int ssl_setup(struct ast_tls_config *cfg) +{ + return __ssl_setup(cfg, 0); +} + +/*! A generic client routine for a TCP client + * and starts a thread for handling accept() + */ +struct server_instance *client_start(struct server_args *desc) +{ + int flags; + struct server_instance *ser = NULL; + + /* Do nothing if nothing has changed */ + if(!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) { + if (option_debug) + ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name); + return NULL; + } + + desc->oldsin = desc->sin; + + if (desc->accept_fd != -1) + close(desc->accept_fd); + + desc->accept_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (desc->accept_fd < 0) { + ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n", + desc->name, strerror(errno)); + return NULL; + } + + if (connect(desc->accept_fd, (const struct sockaddr *)&desc->sin, sizeof(desc->sin))) { + ast_log(LOG_NOTICE, "Unable to connect %s to %s:%d: %s\n", + desc->name, + ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port), + strerror(errno)); + goto error; + } + + if (!(ser = ast_calloc(1, sizeof(*ser)))) + goto error; + + flags = fcntl(desc->accept_fd, F_GETFL); + fcntl(desc->accept_fd, F_SETFL, flags & ~O_NONBLOCK); + + ser->fd = desc->accept_fd; + ser->parent = desc; + ser->parent->worker_fn = NULL; + memcpy(&ser->requestor, &desc->sin, sizeof(ser->requestor)); + + ser->client = 1; + + if (desc->tls_cfg) { + desc->tls_cfg->enabled = 1; + __ssl_setup(desc->tls_cfg, 1); + } + + if (!ast_make_file_from_fd(ser)) + goto error; + + return ser; + +error: + close(desc->accept_fd); + desc->accept_fd = -1; + if (ser) + ast_free(ser); + return NULL; +} + +/*! + * This is a generic (re)start routine for a TCP server, + * which does the socket/bind/listen and starts a thread for handling + * accept(). + */ + +void server_start(struct server_args *desc) +{ + int flags; + int x = 1; + + /* Do nothing if nothing has changed */ + if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) { + if (option_debug) + ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name); + return; + } + + desc->oldsin = desc->sin; + + /* Shutdown a running server if there is one */ + if (desc->master != AST_PTHREADT_NULL) { + pthread_cancel(desc->master); + pthread_kill(desc->master, SIGURG); + pthread_join(desc->master, NULL); + } + + if (desc->accept_fd != -1) + close(desc->accept_fd); + + /* If there's no new server, stop here */ + if (desc->sin.sin_family == 0) + return; + + desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0); + if (desc->accept_fd < 0) { + ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n", + desc->name, strerror(errno)); + return; + } + + setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); + if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) { + ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n", + desc->name, + ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port), + strerror(errno)); + goto error; + } + if (listen(desc->accept_fd, 10)) { + ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name); + goto error; + } + flags = fcntl(desc->accept_fd, F_GETFL); + fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK); + if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) { + ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n", + desc->name, + ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port), + strerror(errno)); + goto error; + } + return; + +error: + close(desc->accept_fd); + desc->accept_fd = -1; +} + +void server_stop(struct server_args *desc) +{ + /* Shutdown a running server if there is one */ + if (desc->master != AST_PTHREADT_NULL) { + pthread_cancel(desc->master); + pthread_kill(desc->master, SIGURG); + pthread_join(desc->master, NULL); + } + if (desc->accept_fd != -1) + close(desc->accept_fd); + desc->accept_fd = -1; +} + +/*! +* creates a FILE * from the fd passed by the accept thread. +* This operation is potentially expensive (certificate verification), +* so we do it in the child thread context. +*/ +void *ast_make_file_from_fd(void *data) +{ + struct server_instance *ser = data; + int (*ssl_setup)(SSL *) = (ser->client) ? SSL_connect : SSL_accept; + int ret; + char err[256]; + + /* + * open a FILE * as appropriate. + */ + if (!ser->parent->tls_cfg) + ser->f = fdopen(ser->fd, "w+"); +#ifdef DO_SSL + else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) { + SSL_set_fd(ser->ssl, ser->fd); + if ((ret = ssl_setup(ser->ssl)) <= 0) { + if(option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Problem setting up ssl connection: %s\n", ERR_error_string(ERR_get_error(), err)); + } else { +#if defined(HAVE_FUNOPEN) /* the BSD interface */ + ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close); + +#elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */ + static const cookie_io_functions_t cookie_funcs = { + ssl_read, ssl_write, NULL, ssl_close + }; + ser->f = fopencookie(ser->ssl, "w+", cookie_funcs); +#else + /* could add other methods here */ + ast_log(LOG_WARNING, "no ser->f methods attempted!"); +#endif + if ((ser->client && !ast_test_flag(&ser->parent->tls_cfg->flags, AST_SSL_DONT_VERIFY_SERVER)) + || (!ser->client && ast_test_flag(&ser->parent->tls_cfg->flags, AST_SSL_VERIFY_CLIENT))) { + X509 *peer; + long res; + peer = SSL_get_peer_certificate(ser->ssl); + if (!peer) + ast_log(LOG_WARNING, "No peer certificate\n"); + res = SSL_get_verify_result(ser->ssl); + if (res != X509_V_OK) + ast_log(LOG_WARNING, "Certificate did not verify: %s\n", X509_verify_cert_error_string(res)); + if (!ast_test_flag(&ser->parent->tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME)) { + ASN1_STRING *str; + unsigned char *str2; + X509_NAME *name = X509_get_subject_name(peer); + int pos = -1; + int found = 0; + + for (;;) { + /* Walk the certificate to check all available "Common Name" */ + /* XXX Probably should do a gethostbyname on the hostname and compare that as well */ + pos = X509_NAME_get_index_by_NID(name, NID_commonName, pos); + if (pos < 0) + break; + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, pos)); + ASN1_STRING_to_UTF8(&str2, str); + if (str2) { + if (!strcasecmp(ser->parent->hostname, (char *) str2)) + found = 1; + ast_log(LOG_DEBUG, "SSL Common Name compare s1='%s' s2='%s'\n", ser->parent->hostname, str2); + OPENSSL_free(str2); + } + if (found) + break; + } + if (!found) { + ast_log(LOG_WARNING, "Certificate common name did not match (%s)\n", ser->parent->hostname); + if (peer) + X509_free(peer); + fclose(ser->f); + return NULL; + } + } + if (peer) + X509_free(peer); + } + } + if (!ser->f) /* no success opening descriptor stacking */ + SSL_free(ser->ssl); + } +#endif /* DO_SSL */ + + if (!ser->f) { + close(ser->fd); + ast_log(LOG_WARNING, "FILE * open failed!\n"); + ast_free(ser); + return NULL; + } + + if (ser && ser->parent->worker_fn) + return ser->parent->worker_fn(ser); + else + return ser; +}