summaryrefslogtreecommitdiffstats
path: root/src/ssl.c
diff options
context:
space:
mode:
authorDavid Troy <dave@popvox.com>2006-04-08 02:59:37 +0000
committerDavid Troy <dave@popvox.com>2006-04-08 02:59:37 +0000
commitab4b54852ac9270880a85acc18a3718fd93e2de2 (patch)
tree4faa4f0b79a6e642849b5f3344beff4304e41b63 /src/ssl.c
parenta6bb95888b0262222975589d8c651798c1f1c773 (diff)
parentedf54a3c22f04b10a0ff8694cf03ed81fd7079c0 (diff)
downloadastmanproxy-ab4b54852ac9270880a85acc18a3718fd93e2de2.tar.gz
astmanproxy-ab4b54852ac9270880a85acc18a3718fd93e2de2.tar.xz
astmanproxy-ab4b54852ac9270880a85acc18a3718fd93e2de2.zip
Trunk, based on 1.20
git-svn-id: http://svncommunity.digium.com/svn/astmanproxy/trunk@101 f02b47b9-160a-0410-81a6-dc3441afb0ec
Diffstat (limited to 'src/ssl.c')
-rw-r--r--src/ssl.c438
1 files changed, 438 insertions, 0 deletions
diff --git a/src/ssl.c b/src/ssl.c
new file mode 100644
index 0000000..9f5b341
--- /dev/null
+++ b/src/ssl.c
@@ -0,0 +1,438 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Tello Corporation, Inc.
+ *
+ * Remco Treffkorn(Architect) and Mahesh Karoshi(Senior Software Developer)
+ *
+ * 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 SSL for The Asterisk Management Interface - AMI
+ *
+ * Channel Management and more
+ *
+ * \author Remco Treffkorn(Architect) and Mahesh Karoshi(Senior Software Developer)
+ * \ref amiconf
+ */
+
+/*! \addtogroup Group_AMI AMI functions
+*/
+/*! @{
+ Doxygen group */
+
+/*! \note We use negative file descriptors for secure channels. The file descriptor
+ -1 is reseved for errors. -2 to -... are secure file descriptors. 0 to ...
+ are regular file descriptors.
+
+ NOTE: Commonly error checks for routines returning fd's are done with (value<0).
+ You must check for (value==-1) instead, since all other negative fd's now
+ are valid fd's.
+*/
+#include <sys/types.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include "ssl.h"
+
+SSL_CTX *sctx;
+SSL_CTX *cctx;
+static long rec_bytes;
+static long sent_bytes;
+static int ssl_initialized;
+
+
+/*! \brief this has to be called before any other function dealing with ssl.
+ Initializes all the ssl related stuff here. */
+int init_secure(char *certfile)
+{
+ SSL_METHOD *meth;
+
+ SSLeay_add_ssl_algorithms();
+ SSL_load_error_strings();
+
+ /* server init */
+ meth = SSLv23_server_method();
+ sctx = SSL_CTX_new(meth);
+
+ if (!sctx) {
+ return errexit("Failed to create a server ssl context!");
+ }
+
+ if (SSL_CTX_use_certificate_file(sctx, certfile, SSL_FILETYPE_PEM) <= 0) {
+ return errexit("Failed to use the certificate file!");
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(sctx, certfile, SSL_FILETYPE_PEM) <= 0) {
+ return errexit("Failed to use the key file!\n");
+ }
+
+ if (!SSL_CTX_check_private_key(sctx)) {
+ return errexit("Private key does not match the certificate public key");
+ }
+ ssl_initialized = 1;
+ return 0;
+}
+
+
+/* Initializes all the client-side ssl related stuff here.
+*/
+int client_init_secure(void)
+{
+ SSL_METHOD *meth;
+
+ /* client init */
+ SSLeay_add_ssl_algorithms();
+ meth = SSLv23_client_method();
+ SSL_load_error_strings();
+ cctx = SSL_CTX_new (meth);
+
+ if (!cctx)
+ debugmsg("Failed to create a client ssl context!");
+ else
+ debugmsg("Client SSL Context Initialized");
+ return 0;
+}
+
+/*! \brief Takes the negative ssl fd and returns the positive fd recieved from the os.
+ * It goes through arrray of fixed maximum number of secured channels.
+*/
+int get_real_fd(int fd)
+{
+ if (fd<-1) {
+ fd = -fd - 2;
+ if (fd>=0 && fd <SEC_MAX)
+ fd = sec_channel[fd].fd;
+ else fd = -1;
+
+ }
+ return fd;
+}
+
+/*! \brief Returns the SSL pointer from the fd. This structure is filled when we accept
+ * the ssl connection and used
+ * for reading and writing through ssl.
+*/
+SSL *get_ssl(int fd)
+{
+ SSL *ssl = NULL;
+
+ fd = -fd - 2;
+
+ if (fd>=0 && fd <SEC_MAX)
+ ssl = sec_channel[fd].ssl;
+
+ return ssl;
+}
+
+/*! \brief Returns the empty ssl slot. Used to save ssl information.
+*/
+int sec_getslot(void)
+{
+ int i;
+
+ for (i=0; i<SEC_MAX; i++) {
+ if(sec_channel[i].ssl==NULL)
+ break;
+ }
+
+ if (i==SEC_MAX)
+ return -1;
+ return i;
+}
+
+/*! \brief Accepts the ssl connection. Returns the negative fd. negative fd's are
+ * chosen to differentiate between ssl and non-ssl connections. Positive
+ * fd's are used for non-ssl connections and negative fd's are used for ssl
+ * connections. So we purposefully calculate and return negative fds.
+ * You can always get positive fd by calling get_real_fd(negative fd).
+ * The positive fd's are required for system calls.
+ *
+*/
+int saccept(int s)
+{
+ int fd, err;
+ SSL* ssl;
+
+ if (!ssl_initialized)
+ return s;
+
+ if (((fd=sec_getslot())!=-1)) {
+ ssl=SSL_new(sctx);
+ SSL_set_fd(ssl, s);
+ sec_channel[fd].ssl = ssl; /* remember ssl */
+ sec_channel[fd].fd = s; /* remember the real fd */
+ do {
+ err = SSL_accept(ssl);
+ err = SSL_get_error(ssl, err);
+ } while( err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+
+ SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
+ debugmsg("ssl_addon: Connection accepted");
+
+ err=1;
+
+ fd = -(fd+2);
+
+ if (err!=1 || !ssl) {
+ /* it did not work */
+ sec_channel[fd].ssl = NULL; /* free the slot */
+ fd = -1;
+ }
+ }
+ return fd;
+}
+
+/*!
+ * \brief Writes through secured ssl connection
+*/
+int m_send(int fd, const void *data, size_t len)
+{
+ sent_bytes += len;
+
+ if (fd < -1) {
+ SSL* ssl = get_ssl(fd);
+ return SSL_write(ssl, data, len);
+ }
+ return write(fd, data, len);
+}
+
+/*!
+ * \brief Receives data from the SSL connection.
+*/
+int m_recv(int s, void *buf, size_t len, int flags)
+{
+ int ret = 0;
+
+ if (s<-1) {
+ SSL* ssl = get_ssl(s);
+ ret = SSL_read (ssl, buf, len);
+ } else
+ ret = recv(s, buf, len, flags);
+
+ if (ret > 0)
+ rec_bytes += ret;
+
+ if (debug && s<-1)
+ debugmsg("Received %d bytes from SSL socket", ret);
+ return ret;
+}
+
+
+/*! \brief
+ Needs to be called instead of close() to close a socket.
+ It also closes the SSL meta connection.
+*/
+
+int close_sock(int socket)
+{
+ int ret=0;
+ SSL* ssl = NULL;
+
+ if (socket < -1) {
+ socket = - socket - 2;
+
+ ssl = sec_channel[socket].ssl;
+ sec_channel[socket].ssl = NULL;
+ socket = sec_channel[socket].fd;
+ }
+
+ ret= close(socket);
+
+ if (ssl)
+ SSL_free (ssl);
+
+ return(ret);
+}
+
+/*! \brief This process cannot continue without fixing this error.
+*/
+int errexit(char s[])
+{
+ debugmsg("SSL critical error: %s", s);
+ return -1;
+}
+
+/*! \brief Checks whether the client is requesting an ssl encrypted connection or not. If its encrypted
+ * request we expect "Client Hello" in the beginning of the message and ssl version 2.
+ * This can be verified by checking buf[0x02], buf[0x03] and buf[0x04]. If the contents are
+ * 0x01, 0x00, 0x02, then its an ssl packet with content "Client Hello", "SSL version 2".
+ * For SSL version 3, we might need to check for 0x01, 0x00, 0x03.
+ *
+*/
+int is_encrypt_request(int sslclhellotimeout, int fd)
+{
+ fd_set listeners;
+ struct timeval tv;
+ char buf[1024];
+ int ready_fdescriptors;
+ int ret;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = sslclhellotimeout * 1000;
+
+ FD_ZERO(&listeners);
+ FD_SET(fd, &listeners);
+
+ ready_fdescriptors = select (fd + 1, &listeners, NULL, NULL, &tv);
+
+ if (ready_fdescriptors < 0 ) {
+ debugmsg("is_encrypt_request: select returned error, This should not happen:");
+ return 0;
+ } else if (ready_fdescriptors == 0) {
+ return 0;
+ }
+ ret = recv(fd, buf, 100, MSG_PEEK);
+ if(ret > 0) {
+ /* check for sslv3 or tls*/
+ if ((buf[0x00] == 0x16) && (buf[0x01] == 0x03) &&
+ /* for tls buf[0x02] = 0x01 and ssl v3 buf[0x02] = 0x02 */
+ ((buf[0x02] == 0x00) || (buf[0x02] == 0x01))) {
+ if (debug)
+ debugmsg("Received a SSL request");
+ return 1;
+ /* check for sslv23_client_method */
+ } else if ((buf[0x02] == 0x01) && (buf[0x03] == 0x03) && (buf[0x04] == 0x01)) {
+ if (debug)
+ debugmsg("Received a SSL request for SSLv23_client_method()");
+ return 1;
+ }
+ /* check for sslv2 and return -1 */
+ else if ((buf[0x02] == 0x01) && (buf[0x03] == 0x00) && (buf[0x04] == 0x02)) {
+ if (debug)
+ debugmsg("Received a SSLv2 request()");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/* Connects to an asterisk server either plain or SSL as appropriate
+*/
+int ast_connect(struct mansession *a) {
+ int s, err=-1, fd;
+ SSL* ssl;
+
+ fd = connect_nonb(a);
+ if ( fd < 0 )
+ return -1;
+
+ if (a->server->use_ssl) {
+ debugmsg("initiating ssl connection");
+ if ((s=sec_getslot())!=-1) { /* find a slot for the ssl handle */
+ sec_channel[s].fd = fd; /* remember the real fd */
+
+ if((ssl=SSL_new(cctx))) { /* get a new ssl */
+ sec_channel[s].ssl = ssl;
+ SSL_set_fd(ssl, fd); /* and attach the real fd */
+ err = SSL_connect(ssl); /* now try and connect */
+ } else
+ debugmsg("couldn't create ssl client context");
+ fd = -(s+2); /* offset by two and negate */
+ /* this tells us it is a ssl fd */
+ } else
+ debugmsg("couldn't get SSL slot!");
+
+ if (err==-1) {
+ close_sock(fd); /* that frees the ssl too */
+ fd = -1;
+ }
+ }
+
+ debugmsg("returning ast_connect with %d", fd);
+ pthread_mutex_lock(&a->lock);
+ a->fd = fd;
+ pthread_mutex_unlock(&a->lock);
+
+ return fd;
+}
+
+int connect_nonb(struct mansession *a)
+{
+ int flags, n, error;
+ socklen_t len;
+ fd_set rset, wset;
+ struct timeval tval;
+ int nsec = 1, sockfd;
+
+ sockfd = get_real_fd(a->fd);
+
+ flags = fcntl(sockfd, F_GETFL, 0);
+ fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
+
+ error = 0;
+ if ( (n = connect(sockfd, (struct sockaddr *) &a->sin, sizeof(a->sin)) ) < 0 ) {
+ /* TODO: This seems like the nine pound hammer to me... */
+ /* perhaps something a bit more elegant; errno seems to change too */
+ if (errno == EISCONN || errno == 103 || errno==111) {
+ debugmsg("connect_nonb: error %d, closing old fd and grabbing a new one...", errno);
+ /* looks like our old socket died, let's round up a new one and try again */
+ close_sock(a->fd);
+ pthread_mutex_lock(&a->lock);
+ a->fd = socket(AF_INET, SOCK_STREAM, 0);
+ pthread_mutex_unlock(&a->lock);
+ return(-1);
+ }
+ if (errno != EINPROGRESS)
+ return(-1);
+ }
+
+ /* Do whatever we want while the connect is taking place. */
+
+ if (n == 0)
+ goto done; /* connect completed immediately */
+
+ FD_ZERO(&rset);
+ FD_SET(sockfd, &rset);
+ wset = rset;
+ tval.tv_sec = nsec;
+ tval.tv_usec = 0;
+
+ if ( (n = select(sockfd+1, &rset, &wset, NULL,
+ nsec ? &tval : NULL)) == 0) {
+ /*close(sockfd);*/ /* we want to retry */
+ errno = ETIMEDOUT;
+ return(-1);
+ }
+
+ if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
+ len = sizeof(error);
+ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+ return(-1); /* Solaris pending error */
+ } else {
+ /*err_quit("select error: sockfd not set");*/
+ logmsg("select error: sockfd not set");
+ return(-1);
+ }
+
+done:
+ fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
+
+ if (error) {
+ /* close(sockfd); */ /* disable for now, we want to retry... */
+ errno = error;
+ return(-1);
+ }
+ return(sockfd);
+}