dect
/
asterisk
Archived
13
0
Fork 0

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
This commit is contained in:
russell 2008-01-18 22:04:33 +00:00
parent cc1fcc7539
commit d6e19bdc91
11 changed files with 1424 additions and 390 deletions

View File

@ -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
------------

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

94
doc/siptls.txt Normal file
View File

@ -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=<ip address>
Specify IP address to bind TLS server to, default is 0.0.0.0
tlscertfile=</path/to/certificate>
The server's certificate file. Should include the key and
certificate. This is mandatory if your going to run a TLS server.
tlscafile=</path/to/certificate>
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=</path/to/ca/dir>
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=<SSL cipher string>
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

View File

@ -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 <openssl/ssl.h>
#include <openssl/err.h>
#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

166
include/asterisk/tcptls.h Normal file
View File

@ -0,0 +1,166 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2006, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
*
* 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 <openssl/ssl.h>
#include <openssl/err.h>
#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 */

View File

@ -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 \

View File

@ -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

View File

@ -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,

452
main/tcptls.c Normal file
View File

@ -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 <brettbryant@gmail.com> (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 <brettbryant@gmail.com>
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <sys/signal.h>
#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;
}