diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 3a10a6d18a..133288146a 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -225,6 +225,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define DEFAULT_MAX_EXPIRY 3600 #define DEFAULT_REGISTRATION_TIMEOUT 20 #define DEFAULT_MAX_FORWARDS "70" +#define DEFAULT_AUTHLIMIT 100 +#define DEFAULT_AUTHTIMEOUT 30 + /* guard limit must be larger than guard secs */ /* guard min must be < 1000, and should be >= 250 */ @@ -272,6 +275,10 @@ static int default_expiry = DEFAULT_DEFAULT_EXPIRY; #define SDP_MAX_RTPMAP_CODECS 32 /*!< Maximum number of codecs allowed in received SDP */ +static int unauth_sessions = 0; +static int authlimit = DEFAULT_AUTHLIMIT; +static int authtimeout = DEFAULT_AUTHTIMEOUT; + /*! \brief Global jitterbuffer configuration - by default, jb is disabled */ static struct ast_jb_conf default_jbconf = { @@ -903,6 +910,7 @@ struct sip_request { char debug; /*!< print extra debugging if non zero */ char has_to_tag; /*!< non-zero if packet has To: tag */ char ignore; /*!< if non-zero This is a re-transmit, ignore it */ + char authenticated; /*!< non-zero if this request was authenticated */ /* Array of offsets into the request string of each SIP header*/ ptrdiff_t header[SIP_MAX_HEADERS]; /* Array of offsets into the request string of each SDP line*/ @@ -2540,19 +2548,46 @@ static void *sip_tcp_worker_fn(void *data) return _sip_tcp_helper_thread(NULL, tcptls_session); } +/*! \brief Check if the authtimeout has expired. + * \param start the time when the session started + * + * \retval 0 the timeout has expired + * \retval -1 error + * \return the number of milliseconds until the timeout will expire + */ +static int sip_check_authtimeout(time_t start) +{ + int timeout; + time_t now; + if(time(&now) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno)); + return -1; + } + + timeout = (authtimeout - (now - start)) * 1000; + if (timeout < 0) { + /* we have timed out */ + return 0; + } + + return timeout; +} + /*! \brief SIP TCP thread management function */ static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session) { - int res, cl; + int res, cl, timeout = -1, authenticated = 0, flags; + time_t start; struct sip_request req = { 0, } , reqcpy = { 0, }; struct sip_threadinfo *me = NULL; char buf[1024] = ""; struct pollfd fds[2] = { { 0 }, { 0 }, }; struct ast_tcptls_session_args *ca = NULL; - /* If this is a server session, then the connection has already been setup, - * simply create the threadinfo object so we can access this thread for writing. - * + /* If this is a server session, then the connection has already been + * setup. Check if the authlimit has been reached and if not create the + * threadinfo object so we can access this thread for writing. + * * if this is a client connection more work must be done. * 1. We own the parent session args for a client connection. This pointer needs * to be held on to so we can decrement it's ref count on thread destruction. @@ -2561,6 +2596,22 @@ static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_sessi * 3. Last, the tcptls_session must be started. */ if (!tcptls_session->client) { + if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) { + /* unauth_sessions is decremented in the cleanup code */ + goto cleanup; + } + + if ((flags = fcntl(tcptls_session->fd, F_GETFL)) == -1) { + ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno)); + goto cleanup; + } + + flags |= O_NONBLOCK; + if (fcntl(tcptls_session->fd, F_SETFL, flags) == -1) { + ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno)); + goto cleanup; + } + if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) { goto cleanup; } @@ -2590,13 +2641,41 @@ static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_sessi if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET))) goto cleanup; + if(time(&start) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno)); + goto cleanup; + } + for (;;) { struct ast_str *str_save; - res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */ + if (!tcptls_session->client && req.authenticated && !authenticated) { + authenticated = 1; + ast_atomic_fetchadd_int(&unauth_sessions, -1); + } + + /* calculate the timeout for unauthenticated server sessions */ + if (!tcptls_session->client && !authenticated ) { + if ((timeout = sip_check_authtimeout(start)) < 0) { + goto cleanup; + } + + if (timeout == 0) { + ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP"); + goto cleanup; + } + } else { + timeout = -1; + } + + res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */ if (res < 0) { ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res); goto cleanup; + } else if (res == 0) { + /* timeout */ + ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP"); + goto cleanup; } /* handle the socket event, check for both reads from the socket fd, @@ -2629,6 +2708,29 @@ static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_sessi /* Read in headers one line at a time */ while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) { + if (!tcptls_session->client && !authenticated ) { + if ((timeout = sip_check_authtimeout(start)) < 0) { + goto cleanup; + } + + if (timeout == 0) { + ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP"); + goto cleanup; + } + } else { + timeout = -1; + } + + res = ast_wait_for_input(tcptls_session->fd, timeout); + if (res < 0) { + ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res); + goto cleanup; + } else if (res == 0) { + /* timeout */ + ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP"); + goto cleanup; + } + ast_mutex_lock(&tcptls_session->lock); if (!fgets(buf, sizeof(buf), tcptls_session->f)) { ast_mutex_unlock(&tcptls_session->lock); @@ -2646,6 +2748,29 @@ static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_sessi if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) { while (cl > 0) { size_t bytes_read; + if (!tcptls_session->client && !authenticated ) { + if ((timeout = sip_check_authtimeout(start)) < 0) { + goto cleanup; + } + + if (timeout == 0) { + ast_debug(2, "SIP %s server timed out", tcptls_session->ssl ? "SSL": "TCP"); + goto cleanup; + } + } else { + timeout = -1; + } + + res = ast_wait_for_input(tcptls_session->fd, timeout); + if (res < 0) { + ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res); + goto cleanup; + } else if (res == 0) { + /* timeout */ + ast_debug(2, "SIP %s server timed out", tcptls_session->ssl ? "SSL": "TCP"); + goto cleanup; + } + ast_mutex_lock(&tcptls_session->lock); if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) { ast_mutex_unlock(&tcptls_session->lock); @@ -2702,6 +2827,10 @@ static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_sessi ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP"); cleanup: + if (!tcptls_session->client && !authenticated) { + ast_atomic_fetchadd_int(&unauth_sessions, -1); + } + if (me) { ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing"); ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref"); @@ -19111,6 +19240,8 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int set_t38_capabilities(p); } + req->authenticated = 1; + /* We have a succesful authentication, process the SDP portion if there is one */ if (find_sdp(req)) { if (process_sdp(p, req, SDP_T38_INITIATE)) { @@ -20633,8 +20764,10 @@ static int handle_request_register(struct sip_pvt *p, struct sip_request *req, s get_header(req, "To"), ast_inet_ntoa(sin->sin_addr), reason); append_history(p, "RegRequest", "Failed : Account %s : %s", get_header(req, "To"), reason); - } else + } else { + req->authenticated = 1; append_history(p, "RegRequest", "Succeeded : Account %s", get_header(req, "To")); + } if (res < 1) { /* Destroy the session, but keep us around for just a bit in case they don't @@ -21065,6 +21198,11 @@ static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin) } p->recv = *sin; + /* if we have an owner, then this request has been authenticated */ + if (p->owner) { + req->authenticated = 1; + } + if (p->do_history) /* This is a request or response, note what it was for */ append_history(p, "Rx", "%s / %s / %s", req->data->str, get_header(req, "CSeq"), REQ_OFFSET_TO_STR(req, rlPart2)); @@ -23514,6 +23652,8 @@ static int reload_config(enum channelreloadreason reason) global_qualifyfreq = DEFAULT_QUALIFYFREQ; global_t38_maxdatagram = -1; global_shrinkcallerid = 1; + authlimit = DEFAULT_AUTHLIMIT; + authtimeout = DEFAULT_AUTHTIMEOUT; global_matchexterniplocally = FALSE; @@ -23731,6 +23871,18 @@ static int reload_config(enum channelreloadreason reason) default_expiry = atoi(v->value); if (default_expiry < 1) default_expiry = DEFAULT_DEFAULT_EXPIRY; + } else if (!strcasecmp(v->name, "tcpauthtimeout")) { + if (ast_parse_arg(v->value, PARSE_INT32|PARSE_DEFAULT|PARSE_IN_RANGE, + &authtimeout, DEFAULT_AUTHTIMEOUT, 1, INT_MAX)) { + 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, "tcpauthlimit")) { + if (ast_parse_arg(v->value, PARSE_INT32|PARSE_DEFAULT|PARSE_IN_RANGE, + &authlimit, DEFAULT_AUTHLIMIT, 1, INT_MAX)) { + 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, "sipdebug")) { if (ast_true(v->value)) sipdebug |= sip_debug_config; diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c index 9bffdb7fc7..243dfb65ec 100644 --- a/channels/chan_skinny.c +++ b/channels/chan_skinny.c @@ -92,6 +92,8 @@ enum skinny_codecs { #define DEFAULT_SKINNY_PORT 2000 #define DEFAULT_SKINNY_BACKLOG 2 #define SKINNY_MAX_PACKET 1000 +#define DEFAULT_AUTH_TIMEOUT 30 +#define DEFAULT_AUTH_LIMIT 50 static struct { unsigned int tos; @@ -103,6 +105,9 @@ static struct { } qos = { 0, 0, 0, 0, 0, 0 }; static int keep_alive = 120; +static int auth_timeout = DEFAULT_AUTH_TIMEOUT; +static int auth_limit = DEFAULT_AUTH_LIMIT; +static int unauth_sessions = 0; static char vmexten[AST_MAX_EXTENSION]; /* Voicemail pilot number */ static char used_context[AST_MAX_EXTENSION]; /* placeholder to check if context are already used in regcontext */ static char regcontext[AST_MAX_CONTEXT]; /* Context for auto-extension */ @@ -1256,6 +1261,7 @@ static AST_LIST_HEAD_STATIC(devices, skinny_device); struct skinnysession { pthread_t t; ast_mutex_t lock; + time_t start; struct sockaddr_in sin; int fd; char inbuf[SKINNY_MAX_PACKET]; @@ -4215,6 +4221,8 @@ static int handle_register_message(struct skinny_req *req, struct skinnysession return 0; } + ast_atomic_fetchadd_int(&unauth_sessions, -1); + ast_verb(3, "Device '%s' successfully registered\n", name); d = s->device; @@ -5879,6 +5887,9 @@ static void destroy_session(struct skinnysession *s) if (s->fd > -1) close(s->fd); + if (!s->device) + ast_atomic_fetchadd_int(&unauth_sessions, -1); + ast_mutex_destroy(&s->lock); ast_free(s); @@ -5894,13 +5905,30 @@ static int get_input(struct skinnysession *s) { int res; int dlen = 0; + int timeout = keep_alive * 1100; + time_t now; int *bufaddr; struct pollfd fds[1]; + if (!s->device) { + if(time(&now) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno)); + return -1; + } + + timeout = (auth_timeout - (now - s->start)) * 1000; + if (timeout < 0) { + /* we have timed out */ + if (skinnydebug) + ast_verb(1, "Skinny Client failed to authenticate in %d seconds\n", auth_timeout); + return -1; + } + } + fds[0].fd = s->fd; fds[0].events = POLLIN; fds[0].revents = 0; - res = ast_poll(fds, 1, (keep_alive * 1100)); /* If nothing has happen, client is dead */ + res = ast_poll(fds, 1, timeout); /* If nothing has happen, client is dead */ /* we add 10% to the keep_alive to deal */ /* with network delays, etc */ if (res < 0) { @@ -5909,8 +5937,13 @@ static int get_input(struct skinnysession *s) return res; } } else if (res == 0) { - if (skinnydebug) - ast_verb(1, "Skinny Client was lost, unregistering\n"); + if (skinnydebug) { + if (s->device) { + ast_verb(1, "Skinny Client was lost, unregistering\n"); + } else { + ast_verb(1, "Skinny Client failed to authenticate in %d seconds\n", auth_timeout); + } + } skinny_unregister(NULL, s); return -1; } @@ -6044,18 +6077,35 @@ static void *accept_thread(void *ignore) ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno)); continue; } + + if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= auth_limit) { + close(as); + ast_atomic_fetchadd_int(&unauth_sessions, -1); + continue; + } + p = getprotobyname("tcp"); if(p) { if( setsockopt(as, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) { ast_log(LOG_WARNING, "Failed to set Skinny tcp connection to TCP_NODELAY mode: %s\n", strerror(errno)); } } - if (!(s = ast_calloc(1, sizeof(struct skinnysession)))) + if (!(s = ast_calloc(1, sizeof(struct skinnysession)))) { + close(as); + ast_atomic_fetchadd_int(&unauth_sessions, -1); continue; + } memcpy(&s->sin, &sin, sizeof(sin)); ast_mutex_init(&s->lock); s->fd = as; + + if(time(&s->start) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s; disconnecting client\n", strerror(errno)); + destroy_session(s); + continue; + } + AST_LIST_LOCK(&sessions); AST_LIST_INSERT_HEAD(&sessions, s, list); AST_LIST_UNLOCK(&sessions); @@ -6221,6 +6271,24 @@ static int reload_config(void) } } else if (!strcasecmp(v->name, "keepalive")) { keep_alive = atoi(v->value); + } else if (!strcasecmp(v->name, "authtimeout")) { + int timeout = atoi(v->value); + + if (timeout < 1) { + ast_log(LOG_WARNING, "Invalid authtimeout value '%s', using default value\n", v->value); + auth_timeout = DEFAULT_AUTH_TIMEOUT; + } else { + auth_timeout = timeout; + } + } else if (!strcasecmp(v->name, "authlimit")) { + int limit = atoi(v->value); + + if (limit < 1) { + ast_log(LOG_WARNING, "Invalid authlimit value '%s', using default value\n", v->value); + auth_limit = DEFAULT_AUTH_LIMIT; + } else { + auth_limit = limit; + } } else if (!strcasecmp(v->name, "vmexten")) { ast_copy_string(vmexten, v->value, sizeof(vmexten)); } else if (!strcasecmp(v->name, "regcontext")) { diff --git a/configs/http.conf.sample b/configs/http.conf.sample index f15c9cf727..6da4ca67b3 100644 --- a/configs/http.conf.sample +++ b/configs/http.conf.sample @@ -32,6 +32,11 @@ bindaddr=127.0.0.1 ; ;prefix=asterisk ; +; sessionlimit specifies the maximum number of httpsessions that will be +; allowed to exist at any given time. (default: 100) +; +;sessionlimit=100 +; ; Whether Asterisk should serve static content from http-static ; Default is no. ; diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index 77d464aed3..0eadae35d1 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -122,6 +122,16 @@ tcpbindaddr=0.0.0.0 ; IP address for TCP server to bind to (0.0.0.0 ; For details how to construct a certificate for SIP see ; http://tools.ietf.org/html/draft-ietf-sip-domain-certs +;tcpauthtimeout = 30 ; tcpauthtimeout specifies the maximum number + ; of seconds a client has to authenticate. If + ; the client does not authenticate beofre this + ; timeout expires, the client will be + ; disconnected. (default: 30 seconds) + +;tcpauthlimit = 100 ; tcpauthlimit specifies the maximum number of + ; unauthenticated sessions that will be allowed + ; to connect at any given time. (default: 100) + ;tlscertfile=asterisk.pem ; Certificate file (*.pem only) to use for TLS connections ; default is to look for "asterisk.pem" in current directory diff --git a/configs/skinny.conf.sample b/configs/skinny.conf.sample index 389e986fd4..525966f718 100644 --- a/configs/skinny.conf.sample +++ b/configs/skinny.conf.sample @@ -9,6 +9,15 @@ dateformat=M-D-Y ; M,D,Y in any order (6 chars max) ; Use M for month, D for day, Y for year, A for 12-hour time. keepalive=120 +;authtimeout = 30 ; authtimeout specifies the maximum number of seconds a + ; client has to authenticate. If the client does not + ; authenticate beofre this timeout expires, the client + ; will be disconnected. (default: 30 seconds) + +;authlimit = 50 ; authlimit specifies the maximum number of + ; unauthenticated sessions that will be allowed to + ; connect at any given time. (default: 50) + ;vmexten=8500 ; Systemwide voicemailmain pilot number ; It must be in the same context as the calling ; device/line diff --git a/main/manager.c b/main/manager.c index f774c7e8da..5b9a1e82cb 100644 --- a/main/manager.c +++ b/main/manager.c @@ -2472,6 +2472,24 @@ static int action_originate(struct mansession *s, const struct message *m) format = 0; ast_parse_allow_disallow(NULL, &format, codecs, 1); } + if (!ast_strlen_zero(app)) { + /* To run the System application (or anything else that goes to + * shell), you must have the additional System privilege */ + if (!(s->session->writeperm & EVENT_FLAG_SYSTEM) + && ( + strcasestr(app, "system") == 0 || /* System(rm -rf /) + TrySystem(rm -rf /) */ + strcasestr(app, "exec") || /* Exec(System(rm -rf /)) + TryExec(System(rm -rf /)) */ + strcasestr(app, "agi") || /* AGI(/bin/rm,-rf /) + EAGI(/bin/rm,-rf /) */ + strstr(appdata, "SHELL") || /* NoOp(${SHELL(rm -rf /)}) */ + strstr(appdata, "EVAL") /* NoOp(${EVAL(${some_var_containing_SHELL})}) */ + )) { + astman_send_error(s, m, "Originate with certain 'Application' arguments requires the additional System privilege, which you do not have."); + return 0; + } + } if (ast_true(async)) { struct fast_originate_helper *fast = ast_calloc(1, sizeof(*fast)); if (!fast) { @@ -2502,21 +2520,6 @@ static int action_originate(struct mansession *s, const struct message *m) } } } else if (!ast_strlen_zero(app)) { - /* To run the System application (or anything else that goes to shell), you must have the additional System privilege */ - if (!(s->session->writeperm & EVENT_FLAG_SYSTEM) - && ( - strcasestr(app, "system") == 0 || /* System(rm -rf /) - TrySystem(rm -rf /) */ - strcasestr(app, "exec") || /* Exec(System(rm -rf /)) - TryExec(System(rm -rf /)) */ - strcasestr(app, "agi") || /* AGI(/bin/rm,-rf /) - EAGI(/bin/rm,-rf /) */ - strstr(appdata, "SHELL") || /* NoOp(${SHELL(rm -rf /)}) */ - strstr(appdata, "EVAL") /* NoOp(${EVAL(${some_var_containing_SHELL})}) */ - )) { - astman_send_error(s, m, "Originate with certain 'Application' arguments requires the additional System privilege, which you do not have."); - return 0; - } res = ast_pbx_outgoing_app(tech, format, data, to, app, appdata, &reason, 1, l, n, vars, account, NULL); } else { if (exten && context && pi)