From f722ade2a59872cb8c62e81809ef4d83d11434e5 Mon Sep 17 00:00:00 2001 From: David Troy Date: Mon, 23 Jun 2008 11:37:23 -0400 Subject: added files to project --- src/astmanproxy.c | 720 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 720 insertions(+) create mode 100644 src/astmanproxy.c (limited to 'src/astmanproxy.c') diff --git a/src/astmanproxy.c b/src/astmanproxy.c new file mode 100644 index 0000000..b2a7107 --- /dev/null +++ b/src/astmanproxy.c @@ -0,0 +1,720 @@ +/* Asterisk Manager Proxy + Copyright (c) 2005-2006 David C. Troy + + This program is free software, distributed under the terms of + the GNU General Public License. + + astmanproxy.c + contains the proxy server core, initialization, thread launching, + loops, and exit routines +*/ + +#include "astmanproxy.h" + +extern int LoadHandlers( void ); +extern void ReadConfig( void ); +extern void ReadPerms( void ); +extern FILE *OpenLogfile( void ); +extern int SetProcUID( void ); + +extern void *proxyaction_do(char *proxyaction, struct message *m, struct mansession *s); +extern void *ProxyLogin(struct mansession *s, struct message *m); +extern void *ProxyLogoff(struct mansession *s); +extern int ValidateAction(struct message *m, struct mansession *s, int inbound); + +int ConnectAsterisk(struct mansession *s); + +struct proxyconfig pc; +struct mansession *sessions = NULL; +struct iohandler *iohandlers = NULL; + +pthread_mutex_t sessionlock; +pthread_mutex_t serverlock; +pthread_mutex_t userslock; +pthread_mutex_t loglock; +pthread_mutex_t debuglock; +static int asock = -1; +FILE *proxylog; +int debug = 0; + +void hup(int sig) { + if (proxylog) { + fflush(proxylog); + fclose(proxylog); + } + proxylog = OpenLogfile(); + logmsg("Received HUP -- reopened log"); + ReadPerms(); + logmsg("Received HUP -- reread permissions"); +} + +void leave(int sig) { + struct mansession *c; + struct message sm, cm; + struct iohandler *io; + struct ast_server *srv; + char iabuf[INET_ADDRSTRLEN]; + + /* Message to send to servers */ + memset(&sm, 0, sizeof(struct message)); + AddHeader(&sm, "Action: Logoff"); + + /* Message to send to clients */ + memset(&cm, 0, sizeof(struct message)); + AddHeader(&cm, PROXY_SHUTDOWN); + + if (debug) + debugmsg("Notifying and closing sessions"); + pthread_mutex_lock (&sessionlock); + while (sessions) { + c = sessions; + sessions = sessions->next; + + if (c->server) { + if (debug) + debugmsg("asterisk@%s: closing session", ast_inet_ntoa(iabuf, sizeof(iabuf), c->sin.sin_addr)); + c->output->write(c, &sm); + logmsg("Shutdown, closed server %s", ast_inet_ntoa(iabuf, sizeof(iabuf), c->sin.sin_addr)); + } else { + if (debug) + debugmsg("client@%s: closing session", ast_inet_ntoa(iabuf, sizeof(iabuf), c->sin.sin_addr)); + c->output->write(c, &cm); + logmsg("Shutdown, closed client %s", ast_inet_ntoa(iabuf, sizeof(iabuf), c->sin.sin_addr)); + } + close_sock(c->fd); /* close tcp & ssl socket */ + pthread_mutex_destroy(&c->lock); + free(c); + } + pthread_mutex_unlock (&sessionlock); + + /* unload server list */ + while (pc.serverlist) { + srv = pc.serverlist; + pc.serverlist = srv->next; + if (debug) + debugmsg("asterisk@%s: forgetting", srv->ast_host); + free(srv); + } + + if (debug) + debugmsg("Closing listener socket"); + close_sock(asock); /* close tcp & ssl socket */ + + /* unload io handlers */ + while (iohandlers) { + io = iohandlers; + iohandlers = iohandlers->next; + if (debug) + debugmsg("unloading: %s", io->formatname); + dlclose(io->dlhandle); + free(io); + } + + if(debug) + debugmsg("Done!\n"); + logmsg("Proxy stopped; shutting down."); + + fclose(proxylog); + pthread_mutex_destroy(&sessionlock); + pthread_mutex_destroy(&loglock); + pthread_mutex_destroy(&debuglock); + exit(sig); +} + +void Version( void ) +{ + printf("astmanproxy: Version %s, (C) David C. Troy 2005-2006\n", PROXY_VERSION); + return; +} + +void Usage( void ) +{ + printf("Usage: astmanproxy [-d|-h|-v]\n"); + printf(" -d : Start in Debug Mode\n"); + printf(" -h : Displays this message\n"); + printf(" -v : Displays version information\n"); + printf("Start with no options to run as daemon\n"); + return; +} + +void destroy_session(struct mansession *s) +{ + struct mansession *cur, *prev = NULL; + char iabuf[INET_ADDRSTRLEN]; + + pthread_mutex_lock(&sessionlock); + cur = sessions; + while(cur) { + if (cur == s) + break; + prev = cur; + cur = cur->next; + } + if (cur) { + if (prev) + prev->next = cur->next; + else + sessions = cur->next; + debugmsg("Connection closed: %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + close_sock(s->fd); /* close tcp/ssl socket */ + pthread_mutex_destroy(&s->lock); + free(s); + } else if (debug) + debugmsg("Trying to delete non-existent session %p?\n", s); + pthread_mutex_unlock(&sessionlock); + + /* If there are no servers and no clients, why are we here? */ + if (!sessions) { + logmsg("Cannot connect to any servers! Leaving!"); + leave(0); + } +} + +int WriteClients(struct message *m) { + struct mansession *c; + char *actionid; + + c = sessions; + while (c) { + if ( !c->server && m->hdrcount>1 && ValidateAction(m, c, 1) ) { + if (c->autofilter && c->actionid) { + actionid = astman_get_header(m, ACTION_ID); + if ( !strcmp(actionid, c->actionid) ) + c->output->write(c, m); + } else + c->output->write(c, m); + + if (c->inputcomplete) { + pthread_mutex_lock(&c->lock); + c->outputcomplete = 1; + pthread_mutex_unlock(&c->lock); + } + } + c = c->next; + } + return 1; +} + +int WriteAsterisk(struct message *m) { + int i; + char outstring[MAX_LEN], *dest; + struct mansession *s, *first; + + first = NULL; + dest = NULL; + + s = sessions; + + dest = astman_get_header(m, "Server"); + if (debug && *dest) debugmsg("set destination: %s", dest); + while ( s ) { + if ( s->server && (s->connected > 0) ) { + if ( !first ) + first = s; + if (*dest && !strcasecmp(dest, s->server->ast_host) ) + break; + } + s = s->next; + } + + if (!s) + s = first; + + /* Check for no servers and empty block -- Don't pester Asterisk if it is one*/ + if (!s || !s->server || (!m->hdrcount && !m->headers[0][0]) ) + return 1; + + debugmsg("writing block to %s", s->server->ast_host); + + pthread_mutex_lock(&s->lock); + for (i=0; ihdrcount; i++) { + if (strcasecmp(m->headers[i], "Server:") ) { + sprintf(outstring, "%s\r\n", m->headers[i]); + ast_carefulwrite(s->fd, outstring, strlen(outstring), s->writetimeout ); + } + } + ast_carefulwrite(s->fd, "\r\n", 2, s->writetimeout); + pthread_mutex_unlock(&s->lock); + return 1; +} + +void *setactionid(char *actionid, struct message *m, struct mansession *s) +{ + pthread_mutex_lock(&s->lock); + strncpy(s->actionid, actionid, MAX_LEN); + pthread_mutex_unlock(&s->lock); + + return 0; +} + +/* Handles proxy client sessions; closely based on session_do from asterisk's manager.c */ +void *session_do(struct mansession *s) +{ + struct message m; + int res; + char *proxyaction, *actionid, *action, *key; + + if (s->input->onconnect) + s->input->onconnect(s, &m); + + for (;;) { + /* Get a complete message block from input handler */ + memset(&m, 0, sizeof(struct message) ); + if (debug > 3) + debugmsg("calling %s_read...", s->input->formatname); + res = s->input->read(s, &m); + if (debug > 3) + debugmsg("%s_read result = %d", s->input->formatname, res); + m.session = s; + + if (res > 0) { + /* Check for anything that requires proxy-side processing */ + if (pc.key[0] != '\0' && !s->authenticated) { + key = astman_get_header(&m, "ProxyKey"); + if (!strcmp(key, pc.key) ) { + pthread_mutex_lock(&s->lock); + s->authenticated = 1; + pthread_mutex_unlock(&s->lock); + } else + break; + } + + proxyaction = astman_get_header(&m, "ProxyAction"); + actionid = astman_get_header(&m, ACTION_ID); + action = astman_get_header(&m, "Action"); + if ( !strcasecmp(action, "Login") ) + if (!s->authenticated) + ProxyLogin(s, &m); + else + break; + else if ( !strcasecmp(action, "Logoff") ) + ProxyLogoff(s); + else if ( !strcasecmp(action, "Challenge") ) + ProxyChallenge(s, &m); + else if ( !(*proxyaction == '\0') ) + proxyaction_do(proxyaction, &m, s); + else if ( ValidateAction(&m, s, 0) ) { + if ( !(*actionid == '\0') ) + setactionid(actionid, &m, s); + if ( !WriteAsterisk(&m) ) + break; + } else { + SendError(s, "Action Filtered"); + } + } else if (res < 0) + break; + } + + destroy_session(s); + if (debug) + debugmsg("--- exiting session_do thread ---"); + pthread_exit(NULL); + return NULL; +} + +void *HandleAsterisk(struct mansession *s) +{ + struct message *m; + int res,i; + char iabuf[INET_ADDRSTRLEN]; + + if (ConnectAsterisk(s)) + goto leave; + if (! (m = malloc(sizeof(struct message))) ) + goto leave; + + for (;;) { + if (debug) + debugmsg("asterisk@%s: attempting read...", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + memset(m, 0, sizeof(struct message) ); + res = s->input->read(s, m); + m->session = s; + + if (res > 0) { + if (debug) { + for(i=0; ihdrcount; i++) { + debugmsg("asterisk@%s got: %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), m->headers[i]); + } + } + + if (!s->connected) { + if ( !strcmp("Authentication accepted", astman_get_header(m, "Message")) ) { + s->connected = 1; + if (debug) + debugmsg("asterisk@%s: connected successfully!", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr) ); + } + if ( !strcmp("Authentication failed", astman_get_header(m, "Message")) ) { + s->connected = -1; + } + } + + m->session = s; + AddHeader(m, "Server: %s", m->session->server->ast_host); + + if (!WriteClients(m)) + break; + } else if (res < 0) { + /* TODO: do we need to do more than this here? or something different? */ + if ( debug ) + debugmsg("asterisk@%s: Not connected", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + if ( ConnectAsterisk(s) ) + break; + } + } + free(m); + +leave: + if (debug) + debugmsg("asterisk@%s: Giving up and exiting thread", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr) ); + destroy_session(s); + pthread_exit(NULL); + return NULL; +} + +int ConnectAsterisk(struct mansession *s) { + char iabuf[INET_ADDRSTRLEN]; + int r = 1, res = 0; + struct message m; + + /* Don't try to do this if auth has already failed! */ + if (s->connected < 0 ) + return 1; + else + s->connected = 0; + + if (debug) + debugmsg("asterisk@%s: Connecting (u=%s, p=%s, ssl=%s)", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), + s->server->ast_user, s->server->ast_pass, s->server->use_ssl ? "on" : "off"); + + /* Construct auth message just once */ + memset( &m, 0, sizeof(struct message) ); + AddHeader(&m, "Action: Login"); + AddHeader(&m, "Username: %s", s->server->ast_user); + AddHeader(&m, "Secret: %s", s->server->ast_pass); + AddHeader(&m, "Events: %s", s->server->ast_events); + + for ( ;; ) { + if ( ast_connect(s) == -1 ) { + if (debug) + debugmsg("asterisk@%s: Connect failed, Retrying (%d) %s [%d]", + ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), r, strerror(errno), errno ); + if (pc.maxretries && (++r>pc.maxretries) ) { + res = 1; + break; + } else + sleep(pc.retryinterval); + } else { + /* Send login */ + s->output->write(s, &m); + res = 0; + break; + } + } + + return res; +} + +int StartServer(struct ast_server *srv) { + + struct mansession *s; + struct hostent *ast_hostent; + + char iabuf[INET_ADDRSTRLEN]; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + ast_hostent = gethostbyname(srv->ast_host); + if (!ast_hostent) { + logmsg("Cannot resolve host %s, cannot add!", srv->ast_host); + debugmsg("Cannot resolve host %s, cannot add!", srv->ast_host); + return 1; + } + + s = malloc(sizeof(struct mansession)); + if ( !s ) { + logmsg("Failed to allocate server session: %s\n", strerror(errno)); + debugmsg("Failed to allocate server session: %s\n", strerror(errno)); + return 1; + } + + memset(s, 0, sizeof(struct mansession)); + SetIOHandlers(s, "standard", "standard"); + s->server = srv; + + bzero((char *) &s->sin,sizeof(s->sin)); + s->sin.sin_family = AF_INET; + memcpy( &s->sin.sin_addr.s_addr, ast_hostent->h_addr, ast_hostent->h_length ); + s->sin.sin_port = htons(atoi(s->server->ast_port)); + s->fd = socket(AF_INET, SOCK_STREAM, 0); + + pthread_mutex_lock(&sessionlock); + s->next = sessions; + sessions = s; + pthread_mutex_unlock(&sessionlock); + + logmsg("Allocated Asterisk server session for %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + if (debug) { + debugmsg("asterisk@%s: Allocated server session", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + debugmsg("Set %s input format to %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), s->input->formatname); + debugmsg("Set %s output format to %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), s->output->formatname); + } + + if (pthread_create(&s->t, &attr, (void *)HandleAsterisk, s)) + destroy_session(s); + else + debugmsg("launched ast %s thread!", s->server->ast_host); + + pthread_attr_destroy(&attr); + return 0; +} + +int LaunchAsteriskThreads() { + + struct ast_server *srv; + + srv = pc.serverlist; + while (srv) { + StartServer(srv); + srv = srv->next; + } + return 0; +} + +int SetIOHandlers(struct mansession *s, char *ifmt, char *ofmt) +{ + int res = 0; + struct iohandler *io; + + io = iohandlers; + pthread_mutex_lock(&s->lock); + while (io) { + if ( !strcasecmp(io->formatname, ifmt) ) + s->input = io; + + if ( !strcasecmp(io->formatname, ofmt) ) + s->output = io; + + io = io->next; + } + + /* set default handlers if non match was found */ + if (!s->output) { + s->output = iohandlers; + res = 1; + } + + if (!s->input) { + s->input = iohandlers; + res = 1; + } + pthread_mutex_unlock(&s->lock); + + return res; +} + +static void *accept_thread() +{ + int as; + struct sockaddr_in sin; + socklen_t sinlen; + struct mansession *s; + struct protoent *p; + int arg = 1; + int flags; + pthread_attr_t attr; + char iabuf[INET_ADDRSTRLEN]; + int is_encrypted; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + for (;;) { + sinlen = sizeof(sin); + as = accept(asock, (struct sockaddr *)&sin, &sinlen); + if (as < 0) { + logmsg("Accept returned -1: %s\n", strerror(errno)); + continue; + } + p = (struct protoent *)getprotobyname("tcp"); + if( p ) { + if( setsockopt(as, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) { + logmsg("Failed to set listener tcp connection to TCP_NODELAY mode: %s\n", strerror(errno)); + } + } + + /* SSL stuff below */ + is_encrypted = is_encrypt_request(pc.sslclhellotimeout, as); + debugmsg("is_encrypted: %d", is_encrypted); + if (is_encrypted > 0) { + if (!pc.acceptencryptedconnection) { + if( debug ) + debugmsg("Accepting encrypted connection disabled, closing the connection \n"); + close_sock(as); + continue; + } else { + if((as = saccept(as)) >= 0 ) { + if( debug ) + debugmsg("Can't accept the ssl connection, since SSL init has failed for certificate reason\n"); + close_sock(as); + continue; + } + } + } else if (is_encrypted == -1) { + logmsg("SSL version 2 is unsecure, we don't support it\n"); + close_sock(as); + continue; + } + if ( (! pc.acceptunencryptedconnection) && (as >= 0)) { + logmsg("Unencrypted connections are not accepted and we received an unencrypted connection request\n"); + close_sock(as); + continue; + } + /* SSL stuff end */ + + s = malloc(sizeof(struct mansession)); + if ( !s ) { + logmsg("Failed to allocate listener session: %s\n", strerror(errno)); + continue; + } + memset(s, 0, sizeof(struct mansession)); + memcpy(&s->sin, &sin, sizeof(sin)); + + /* For safety, make sure socket is non-blocking */ + flags = fcntl(get_real_fd(as), F_GETFL); + fcntl(get_real_fd(as), F_SETFL, flags | O_NONBLOCK); + + pthread_mutex_init(&s->lock, NULL); + s->fd = as; + SetIOHandlers(s, pc.inputformat, pc.outputformat); + s->autofilter = pc.autofilter; + s->server = NULL; + + pthread_mutex_lock(&sessionlock); + s->next = sessions; + sessions = s; + pthread_mutex_unlock(&sessionlock); + + logmsg("Connection received from %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + if (debug) { + debugmsg("Connection received from %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); + debugmsg("Set %s input format to %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), s->input->formatname); + debugmsg("Set %s output format to %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), s->output->formatname); + } + + if (pthread_create(&s->t, &attr, (void *)session_do, s)) + destroy_session(s); + } + pthread_attr_destroy(&attr); + return NULL; +} + +int main(int argc, char *argv[]) +{ + struct sockaddr_in serv_sock_addr, client_sock_addr; + int cli_addrlen; + struct linger lingerstruct; /* for socket reuse */ + int flag; /* for socket reuse */ + pid_t pid; + char i; + + /* Figure out if we are in debug mode, handle other switches */ + while (( i = getopt( argc, argv, "dhv" ) ) != EOF ) + { + switch( i ) { + case 'd': + debug++; + break; + case 'h': + Usage(); + exit(0); + case 'v': + Version(); + exit(0); + case '?': + Usage(); + exit(1); + } + } + + + ReadConfig(); + proxylog = OpenLogfile(); + debugmsg("loading handlers"); + LoadHandlers(); + debugmsg("loaded handlers"); + + if (SetProcUID()) { + fprintf(stderr,"Cannot set user/group! Check proc_user and proc_group config setting!\n"); + exit(1); + } + + /* If we are not in debug mode, then fork to background */ + if (!debug) { + if ( (pid = fork()) < 0) + exit( 1 ); + else if ( pid > 0) + exit( 0 ); + } + + /* Setup signal handlers */ + (void) signal(SIGINT,leave); + (void) signal(SIGHUP,hup); + (void) signal(SIGTERM,leave); + (void) signal(SIGPIPE, SIG_IGN); + + /* Initialize global mutexes */ + pthread_mutex_init(&sessionlock, NULL); + pthread_mutex_init(&userslock, NULL); + pthread_mutex_init(&loglock, NULL); + pthread_mutex_init(&debuglock, NULL); + + /* Read initial state for user permissions */ + ReadPerms(); + + /* Initialize SSL Client-Side Context */ + client_init_secure(); + + /* Initialize global client/server list */ + sessions = NULL; + LaunchAsteriskThreads(); + + /* Setup listener socket to setup new sessions... */ + if ((asock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr,"Cannot create listener socket!\n"); + exit(1); + } + bzero((char *) &serv_sock_addr, sizeof serv_sock_addr ); + serv_sock_addr.sin_family = AF_INET; + + if ( !strcmp(pc.listen_addr,"*") ) + serv_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); + else + serv_sock_addr.sin_addr.s_addr = inet_addr( pc.listen_addr); + + serv_sock_addr.sin_port = htons((short)pc.listen_port); + + /* Set listener socket re-use options */ + setsockopt(asock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag)); + lingerstruct.l_onoff = 1; + lingerstruct.l_linger = 5; + setsockopt(asock, SOL_SOCKET, SO_LINGER, (void *)&lingerstruct, sizeof(lingerstruct)); + + if (bind(asock, (struct sockaddr *) &serv_sock_addr, sizeof serv_sock_addr ) < 0) { + fprintf(stderr,"Cannot bind to listener socket!\n"); + exit(1); + } + + listen(asock, 5); + cli_addrlen = sizeof(client_sock_addr); + if (debug) + debugmsg("Listening for connections"); + logmsg("Proxy Started: Listening for connections"); + + /* Launch listener thread */ + accept_thread(); + + pthread_exit(NULL); + exit(0); +} -- cgit