Fri Jul 24 00:41:05 2009

Asterisk developer's documentation


tcptls.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2007 - 2008, Digium, Inc.
00005  *
00006  * Luigi Rizzo (TCP and TLS server code)
00007  * Brett Bryant <brettbryant@gmail.com> (updated for client support)
00008  *
00009  * See http://www.asterisk.org for more information about
00010  * the Asterisk project. Please do not directly contact
00011  * any of the maintainers of this project for assistance;
00012  * the project provides a web site, mailing lists and IRC
00013  * channels for your use.
00014  *
00015  * This program is free software, distributed under the terms of
00016  * the GNU General Public License Version 2. See the LICENSE file
00017  * at the top of the source tree.
00018  */
00019 
00020 /*!
00021  * \file
00022  * \brief Code to support TCP and TLS server/client
00023  *
00024  * \author Luigi Rizzo
00025  * \author Brett Bryant <brettbryant@gmail.com>
00026  */
00027 
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 180740 $")
00031 
00032 #ifdef HAVE_FCNTL_H
00033 #include <fcntl.h>
00034 #endif
00035 
00036 #include <sys/signal.h>
00037 
00038 #include "asterisk/compat.h"
00039 #include "asterisk/tcptls.h"
00040 #include "asterisk/http.h"
00041 #include "asterisk/utils.h"
00042 #include "asterisk/strings.h"
00043 #include "asterisk/options.h"
00044 #include "asterisk/manager.h"
00045 #include "asterisk/astobj2.h"
00046 
00047 /*! \brief
00048  * replacement read/write functions for SSL support.
00049  * We use wrappers rather than SSL_read/SSL_write directly so
00050  * we can put in some debugging.
00051  */
00052 
00053 #ifdef DO_SSL
00054 static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
00055 {
00056    int i = SSL_read(cookie, buf, len-1);
00057 #if 0
00058    if (i >= 0)
00059       buf[i] = '\0';
00060    ast_verb(0, "ssl read size %d returns %d <%s>\n", (int)len, i, buf);
00061 #endif
00062    return i;
00063 }
00064 
00065 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
00066 {
00067 #if 0
00068    char *s = alloca(len+1);
00069    strncpy(s, buf, len);
00070    s[len] = '\0';
00071    ast_verb(0, "ssl write size %d <%s>\n", (int)len, s);
00072 #endif
00073    return SSL_write(cookie, buf, len);
00074 }
00075 
00076 static int ssl_close(void *cookie)
00077 {
00078    close(SSL_get_fd(cookie));
00079    SSL_shutdown(cookie);
00080    SSL_free(cookie);
00081    return 0;
00082 }
00083 #endif   /* DO_SSL */
00084 
00085 HOOK_T ast_tcptls_server_read(struct ast_tcptls_session_instance *tcptls_session, void *buf, size_t count)
00086 {
00087    if (tcptls_session->fd == -1) {
00088       ast_log(LOG_ERROR, "server_read called with an fd of -1\n");
00089       errno = EIO;
00090       return -1;
00091    }
00092 
00093 #ifdef DO_SSL
00094    if (tcptls_session->ssl)
00095       return ssl_read(tcptls_session->ssl, buf, count);
00096 #endif
00097    return read(tcptls_session->fd, buf, count);
00098 }
00099 
00100 HOOK_T ast_tcptls_server_write(struct ast_tcptls_session_instance *tcptls_session, void *buf, size_t count)
00101 {
00102    if (tcptls_session->fd == -1) {
00103       ast_log(LOG_ERROR, "server_write called with an fd of -1\n");
00104       errno = EIO;
00105       return -1;
00106    }
00107 
00108 #ifdef DO_SSL
00109    if (tcptls_session->ssl)
00110       return ssl_write(tcptls_session->ssl, buf, count);
00111 #endif
00112    return write(tcptls_session->fd, buf, count);
00113 }
00114 
00115 static void session_instance_destructor(void *obj)
00116 {
00117    struct ast_tcptls_session_instance *i = obj;
00118    ast_mutex_destroy(&i->lock);
00119 }
00120 
00121 /*! \brief
00122 * creates a FILE * from the fd passed by the accept thread.
00123 * This operation is potentially expensive (certificate verification),
00124 * so we do it in the child thread context.
00125 */
00126 static void *handle_tls_connection(void *data)
00127 {
00128    struct ast_tcptls_session_instance *tcptls_session = data;
00129 #ifdef DO_SSL
00130    int (*ssl_setup)(SSL *) = (tcptls_session->client) ? SSL_connect : SSL_accept;
00131    int ret;
00132    char err[256];
00133 #endif
00134 
00135    /*
00136    * open a FILE * as appropriate.
00137    */
00138    if (!tcptls_session->parent->tls_cfg) {
00139       tcptls_session->f = fdopen(tcptls_session->fd, "w+");
00140       setvbuf(tcptls_session->f, NULL, _IONBF, 0);
00141    }
00142 #ifdef DO_SSL
00143    else if ( (tcptls_session->ssl = SSL_new(tcptls_session->parent->tls_cfg->ssl_ctx)) ) {
00144       SSL_set_fd(tcptls_session->ssl, tcptls_session->fd);
00145       if ((ret = ssl_setup(tcptls_session->ssl)) <= 0) {
00146          ast_verb(2, "Problem setting up ssl connection: %s\n", ERR_error_string(ERR_get_error(), err));
00147       } else {
00148 #if defined(HAVE_FUNOPEN)  /* the BSD interface */
00149          tcptls_session->f = funopen(tcptls_session->ssl, ssl_read, ssl_write, NULL, ssl_close);
00150 
00151 #elif defined(HAVE_FOPENCOOKIE)  /* the glibc/linux interface */
00152          static const cookie_io_functions_t cookie_funcs = {
00153             ssl_read, ssl_write, NULL, ssl_close
00154          };
00155          tcptls_session->f = fopencookie(tcptls_session->ssl, "w+", cookie_funcs);
00156 #else
00157          /* could add other methods here */
00158          ast_debug(2, "no tcptls_session->f methods attempted!");
00159 #endif
00160          if ((tcptls_session->client && !ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_DONT_VERIFY_SERVER))
00161             || (!tcptls_session->client && ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_VERIFY_CLIENT))) {
00162             X509 *peer;
00163             long res;
00164             peer = SSL_get_peer_certificate(tcptls_session->ssl);
00165             if (!peer)
00166                ast_log(LOG_WARNING, "No peer SSL certificate\n");
00167             res = SSL_get_verify_result(tcptls_session->ssl);
00168             if (res != X509_V_OK)
00169                ast_log(LOG_ERROR, "Certificate did not verify: %s\n", X509_verify_cert_error_string(res));
00170             if (!ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME)) {
00171                ASN1_STRING *str;
00172                unsigned char *str2;
00173                X509_NAME *name = X509_get_subject_name(peer);
00174                int pos = -1;
00175                int found = 0;
00176             
00177                for (;;) {
00178                   /* Walk the certificate to check all available "Common Name" */
00179                   /* XXX Probably should do a gethostbyname on the hostname and compare that as well */
00180                   pos = X509_NAME_get_index_by_NID(name, NID_commonName, pos);
00181                   if (pos < 0)
00182                      break;
00183                   str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, pos));
00184                   ASN1_STRING_to_UTF8(&str2, str);
00185                   if (str2) {
00186                      if (!strcasecmp(tcptls_session->parent->hostname, (char *) str2))
00187                         found = 1;
00188                      ast_debug(3, "SSL Common Name compare s1='%s' s2='%s'\n", tcptls_session->parent->hostname, str2);
00189                      OPENSSL_free(str2);
00190                   }
00191                   if (found)
00192                      break;
00193                }
00194                if (!found) {
00195                   ast_log(LOG_ERROR, "Certificate common name did not match (%s)\n", tcptls_session->parent->hostname);
00196                   if (peer)
00197                      X509_free(peer);
00198                   fclose(tcptls_session->f);
00199                   return NULL;
00200                }
00201             }
00202             if (peer)
00203                X509_free(peer);
00204          }
00205       }
00206       if (!tcptls_session->f) /* no success opening descriptor stacking */
00207          SSL_free(tcptls_session->ssl);
00208    }
00209 #endif /* DO_SSL */
00210 
00211    if (!tcptls_session->f) {
00212       close(tcptls_session->fd);
00213       ast_log(LOG_WARNING, "FILE * open failed!\n");
00214       ao2_ref(tcptls_session, -1);
00215       return NULL;
00216    }
00217 
00218    if (tcptls_session && tcptls_session->parent->worker_fn)
00219       return tcptls_session->parent->worker_fn(tcptls_session);
00220    else
00221       return tcptls_session;
00222 }
00223 
00224 void *ast_tcptls_server_root(void *data)
00225 {
00226    struct ast_tcptls_session_args *desc = data;
00227    int fd;
00228    struct sockaddr_in sin;
00229    socklen_t sinlen;
00230    struct ast_tcptls_session_instance *tcptls_session;
00231    pthread_t launched;
00232    
00233    for (;;) {
00234       int i, flags;
00235 
00236       if (desc->periodic_fn)
00237          desc->periodic_fn(desc);
00238       i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
00239       if (i <= 0)
00240          continue;
00241       sinlen = sizeof(sin);
00242       fd = accept(desc->accept_fd, (struct sockaddr *) &sin, &sinlen);
00243       if (fd < 0) {
00244          if ((errno != EAGAIN) && (errno != EINTR))
00245             ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
00246          continue;
00247       }
00248       tcptls_session = ao2_alloc(sizeof(*tcptls_session), session_instance_destructor);
00249       if (!tcptls_session) {
00250          ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
00251          close(fd);
00252          continue;
00253       }
00254 
00255       ast_mutex_init(&tcptls_session->lock);
00256 
00257       flags = fcntl(fd, F_GETFL);
00258       fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
00259       tcptls_session->fd = fd;
00260       tcptls_session->parent = desc;
00261       memcpy(&tcptls_session->remote_address, &sin, sizeof(tcptls_session->remote_address));
00262 
00263       tcptls_session->client = 0;
00264          
00265       if (ast_pthread_create_detached_background(&launched, NULL, handle_tls_connection, tcptls_session)) {
00266          ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
00267          close(tcptls_session->fd);
00268          ao2_ref(tcptls_session, -1);
00269       }
00270    }
00271    return NULL;
00272 }
00273 
00274 static int __ssl_setup(struct ast_tls_config *cfg, int client)
00275 {
00276 #ifndef DO_SSL
00277    cfg->enabled = 0;
00278    return 0;
00279 #else
00280    if (!cfg->enabled)
00281       return 0;
00282 
00283    SSL_load_error_strings();
00284    SSLeay_add_ssl_algorithms();
00285 
00286    if (!(cfg->ssl_ctx = SSL_CTX_new( client ? SSLv23_client_method() : SSLv23_server_method() ))) {
00287       ast_debug(1, "Sorry, SSL_CTX_new call returned null...\n");
00288       cfg->enabled = 0;
00289       return 0;
00290    }
00291    if (!ast_strlen_zero(cfg->certfile)) {
00292       if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
00293           SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
00294           SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) {
00295          if (!client) {
00296             /* Clients don't need a certificate, but if its setup we can use it */
00297             ast_verb(0, "SSL cert error <%s>", cfg->certfile);
00298             sleep(2);
00299             cfg->enabled = 0;
00300             return 0;
00301          }
00302       }
00303    }
00304    if (!ast_strlen_zero(cfg->cipher)) {
00305       if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {
00306          if (!client) {
00307             ast_verb(0, "SSL cipher error <%s>", cfg->cipher);
00308             sleep(2);
00309             cfg->enabled = 0;
00310             return 0;
00311          }
00312       }
00313    }
00314    if (!ast_strlen_zero(cfg->cafile) || !ast_strlen_zero(cfg->capath)) {
00315       if (SSL_CTX_load_verify_locations(cfg->ssl_ctx, S_OR(cfg->cafile, NULL), S_OR(cfg->capath,NULL)) == 0)
00316          ast_verb(0, "SSL CA file(%s)/path(%s) error\n", cfg->cafile, cfg->capath);
00317    }
00318 
00319    ast_verb(0, "SSL certificate ok\n");
00320    return 1;
00321 #endif
00322 }
00323 
00324 int ast_ssl_setup(struct ast_tls_config *cfg)
00325 {
00326    return __ssl_setup(cfg, 0);
00327 }
00328 
00329 struct ast_tcptls_session_instance *ast_tcptls_client_start(struct ast_tcptls_session_args *desc)
00330 {
00331    int flags;
00332    int x = 1;
00333    struct ast_tcptls_session_instance *tcptls_session = NULL;
00334 
00335    /* Do nothing if nothing has changed */
00336    if (!memcmp(&desc->old_address, &desc->remote_address, sizeof(desc->old_address))) {
00337       ast_debug(1, "Nothing changed in %s\n", desc->name);
00338       return NULL;
00339    }
00340 
00341    desc->old_address = desc->remote_address;
00342 
00343    if (desc->accept_fd != -1)
00344       close(desc->accept_fd);
00345 
00346    desc->accept_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
00347    if (desc->accept_fd < 0) {
00348       ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
00349          desc->name, strerror(errno));
00350       return NULL;
00351    }
00352 
00353    /* if a local address was specified, bind to it so the connection will
00354       originate from the desired address */
00355    if (desc->local_address.sin_family != 0) {
00356       setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
00357       if (bind(desc->accept_fd, (struct sockaddr *) &desc->local_address, sizeof(desc->local_address))) {
00358          ast_log(LOG_ERROR, "Unable to bind %s to %s:%d: %s\n",
00359          desc->name,
00360             ast_inet_ntoa(desc->local_address.sin_addr), ntohs(desc->local_address.sin_port),
00361             strerror(errno));
00362          goto error;
00363       }
00364    }
00365 
00366    if (connect(desc->accept_fd, (const struct sockaddr *) &desc->remote_address, sizeof(desc->remote_address))) {
00367       ast_log(LOG_ERROR, "Unable to connect %s to %s:%d: %s\n",
00368          desc->name,
00369          ast_inet_ntoa(desc->remote_address.sin_addr), ntohs(desc->remote_address.sin_port),
00370          strerror(errno));
00371       goto error;
00372    }
00373 
00374    if (!(tcptls_session = ao2_alloc(sizeof(*tcptls_session), session_instance_destructor)))
00375       goto error;
00376 
00377    ast_mutex_init(&tcptls_session->lock);
00378 
00379    flags = fcntl(desc->accept_fd, F_GETFL);
00380    fcntl(desc->accept_fd, F_SETFL, flags & ~O_NONBLOCK);
00381 
00382    tcptls_session->fd = desc->accept_fd;
00383    tcptls_session->parent = desc;
00384    tcptls_session->parent->worker_fn = NULL;
00385    memcpy(&tcptls_session->remote_address, &desc->remote_address, sizeof(tcptls_session->remote_address));
00386 
00387    tcptls_session->client = 1;
00388 
00389    if (desc->tls_cfg) {
00390       desc->tls_cfg->enabled = 1;
00391       __ssl_setup(desc->tls_cfg, 1);
00392    }
00393 
00394    ao2_ref(tcptls_session, +1);
00395    if (!handle_tls_connection(tcptls_session))
00396       goto error;
00397 
00398    return tcptls_session;
00399 
00400 error:
00401    close(desc->accept_fd);
00402    desc->accept_fd = -1;
00403    if (tcptls_session)
00404       ao2_ref(tcptls_session, -1);
00405    return NULL;
00406 }
00407 
00408 void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
00409 {
00410    int flags;
00411    int x = 1;
00412    
00413    /* Do nothing if nothing has changed */
00414    if (!memcmp(&desc->old_address, &desc->local_address, sizeof(desc->old_address))) {
00415       ast_debug(1, "Nothing changed in %s\n", desc->name);
00416       return;
00417    }
00418    
00419    desc->old_address = desc->local_address;
00420    
00421    /* Shutdown a running server if there is one */
00422    if (desc->master != AST_PTHREADT_NULL) {
00423       pthread_cancel(desc->master);
00424       pthread_kill(desc->master, SIGURG);
00425       pthread_join(desc->master, NULL);
00426    }
00427    
00428    if (desc->accept_fd != -1)
00429       close(desc->accept_fd);
00430 
00431    /* If there's no new server, stop here */
00432    if (desc->local_address.sin_family == 0) {
00433       return;
00434    }
00435 
00436    desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
00437    if (desc->accept_fd < 0) {
00438       ast_log(LOG_ERROR, "Unable to allocate socket for %s: %s\n",
00439          desc->name, strerror(errno));
00440       return;
00441    }
00442    
00443    setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
00444    if (bind(desc->accept_fd, (struct sockaddr *) &desc->local_address, sizeof(desc->local_address))) {
00445       ast_log(LOG_ERROR, "Unable to bind %s to %s:%d: %s\n",
00446          desc->name,
00447          ast_inet_ntoa(desc->local_address.sin_addr), ntohs(desc->local_address.sin_port),
00448          strerror(errno));
00449       goto error;
00450    }
00451    if (listen(desc->accept_fd, 10)) {
00452       ast_log(LOG_ERROR, "Unable to listen for %s!\n", desc->name);
00453       goto error;
00454    }
00455    flags = fcntl(desc->accept_fd, F_GETFL);
00456    fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
00457    if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
00458       ast_log(LOG_ERROR, "Unable to launch thread for %s on %s:%d: %s\n",
00459          desc->name,
00460          ast_inet_ntoa(desc->local_address.sin_addr), ntohs(desc->local_address.sin_port),
00461          strerror(errno));
00462       goto error;
00463    }
00464    return;
00465 
00466 error:
00467    close(desc->accept_fd);
00468    desc->accept_fd = -1;
00469 }
00470 
00471 void ast_tcptls_server_stop(struct ast_tcptls_session_args *desc)
00472 {
00473    if (desc->master != AST_PTHREADT_NULL) {
00474       pthread_cancel(desc->master);
00475       pthread_kill(desc->master, SIGURG);
00476       pthread_join(desc->master, NULL);
00477    }
00478    if (desc->accept_fd != -1)
00479       close(desc->accept_fd);
00480    desc->accept_fd = -1;
00481 }

Generated on Fri Jul 24 00:41:05 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7