From 1c272a5d157a89195607d0359c782523f142074a Mon Sep 17 00:00:00 2001 From: Matthew Nicholson Date: Tue, 5 Apr 2011 14:13:07 +0000 Subject: [PATCH] Merged revisions 312761 via svnmerge from https://origsvn.digium.com/svn/asterisk/branches/1.4 ........ r312761 | mnicholson | 2011-04-05 09:10:34 -0500 (Tue, 05 Apr 2011) | 8 lines Limit the number of unauthenticated manager sessions and also limit the time they have to authenticate. AST-2011-005 (closes issue #18996) Reported by: tzafrir Tested by: mnicholson ........ git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.6.2@312764 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- configs/manager.conf.sample | 11 ++++++ main/manager.c | 76 +++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/configs/manager.conf.sample b/configs/manager.conf.sample index acb7da980b..30dcd64961 100644 --- a/configs/manager.conf.sample +++ b/configs/manager.conf.sample @@ -25,6 +25,17 @@ enabled = no ;webenabled = yes port = 5038 +; 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) + +;authtimeout = 30 + +; authlimit specifies the maximum number of unauthenticated sessions that will +; be allowed to connect at any given time. + +;authlimit = 50 + ;httptimeout = 60 ; a) httptimeout sets the Max-Age of the http cookie ; b) httptimeout is the amount of time the webserver waits diff --git a/main/manager.c b/main/manager.c index 2edf2588ba..400394c305 100644 --- a/main/manager.c +++ b/main/manager.c @@ -127,6 +127,8 @@ static const int DEFAULT_DISPLAYCONNECTS = 1; /*!< Default setting for displayin static const int DEFAULT_TIMESTAMPEVENTS = 0; /*!< Default setting for timestampevents */ static const int DEFAULT_HTTPTIMEOUT = 60; /*!< Default manager http timeout */ static const int DEFAULT_BROKENEVENTSACTION = 0; /*!< Default setting for brokeneventsaction */ +static const int DEFAULT_AUTHTIMEOUT = 30; /*!< Default setting for authtimeout */ +static const int DEFAULT_AUTHLIMIT = 50; /*!< Default setting for authlimit */ static int displayconnects; static int allowmultiplelogin = 1; @@ -135,9 +137,12 @@ static int httptimeout; static int broken_events_action; static int manager_enabled = 0; static int webmanager_enabled = 0; +static int authtimeout; +static int authlimit; static int block_sockets; static int num_sessions; +static int unauth_sessions = 0; static int manager_debug; /*!< enable some debugging code in the manager */ @@ -214,6 +219,7 @@ struct mansession_session { int send_events; /*!< XXX what ? */ struct eventqent *last_ev; /*!< last event processed. */ int writetimeout; /*!< Timeout for ast_carefulwrite() */ + time_t authstart; int pending_event; /*!< Pending events indicator in case when waiting_thread is NULL */ AST_LIST_HEAD_NOLOCK(mansession_datastores, ast_datastore) datastores; /*!< Data stores on the session */ AST_LIST_ENTRY(mansession_session) list; @@ -1792,6 +1798,7 @@ static int action_login(struct mansession *s, const struct message *m) return -1; } s->session->authenticated = 1; + ast_atomic_fetchadd_int(&unauth_sessions, -1); if (manager_displayconnects(s->session)) ast_verb(2, "%sManager '%s' logged on from %s\n", (s->session->managerid ? "HTTP " : ""), s->session->username, ast_inet_ntoa(s->session->sin.sin_addr)); ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->session->managerid ? "HTTP " : ""), s->session->username, ast_inet_ntoa(s->session->sin.sin_addr)); @@ -3130,6 +3137,8 @@ static int get_input(struct mansession *s, char *output) int res, x; int maxlen = sizeof(s->session->inbuf) - 1; char *src = s->session->inbuf; + int timeout = -1; + time_t now; /* * Look for \r\n within the buffer. If found, copy to the output @@ -3157,6 +3166,20 @@ static int get_input(struct mansession *s, char *output) } res = 0; while (res == 0) { + /* calculate a timeout if we are not authenticated */ + if (!s->session->authenticated) { + if(time(&now) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno)); + return -1; + } + + timeout = (authtimeout - (now - s->session->authstart)) * 1000; + if (timeout < 0) { + /* we have timed out */ + return 0; + } + } + /* XXX do we really need this locking ? */ ast_mutex_lock(&s->session->__lock); if (s->session->pending_event) { @@ -3167,7 +3190,7 @@ static int get_input(struct mansession *s, char *output) s->session->waiting_thread = pthread_self(); ast_mutex_unlock(&s->session->__lock); - res = ast_wait_for_input(s->session->fd, -1); /* return 0 on timeout ? */ + res = ast_wait_for_input(s->session->fd, timeout); ast_mutex_lock(&s->session->__lock); s->session->waiting_thread = AST_PTHREADT_NULL; @@ -3200,6 +3223,7 @@ static int do_message(struct mansession *s) struct message m = { 0 }; char header_buf[sizeof(s->session->inbuf)] = { '\0' }; int res; + time_t now; for (;;) { /* Check if any events are pending and do them if needed */ @@ -3207,6 +3231,17 @@ static int do_message(struct mansession *s) return -1; res = get_input(s, header_buf); if (res == 0) { + if (!s->session->authenticated) { + if(time(&now) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno)); + return -1; + } + + if (now - s->session->authstart > authtimeout) { + ast_log(LOG_EVENT, "Client from %s, failed to authenticate in %d seconds\n", ast_inet_ntoa(s->session->sin.sin_addr), authtimeout); + return -1; + } + } continue; } else if (res > 0) { if (ast_strlen_zero(header_buf)) @@ -3230,14 +3265,23 @@ static int do_message(struct mansession *s) static void *session_do(void *data) { struct ast_tcptls_session_instance *ser = data; - struct mansession_session *session = ast_calloc(1, sizeof(*session)); + struct mansession_session *session = NULL; struct mansession s = {.session = NULL, }; int flags; int res; struct protoent *p; - if (session == NULL) + if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) { + fclose(ser->f); + ast_atomic_fetchadd_int(&unauth_sessions, -1); goto done; + } + + if ((session = ast_calloc(1, sizeof(*session))) == NULL) { + fclose(ser->f); + ast_atomic_fetchadd_int(&unauth_sessions, -1); + goto done; + } /* here we set TCP_NODELAY on the socket to disable Nagle's algorithm. * This is necessary to prevent delays (caused by buffering) as we @@ -3280,6 +3324,13 @@ static void *session_do(void *data) ast_atomic_fetchadd_int(&num_sessions, 1); AST_LIST_UNLOCK(&sessions); + if(time(&session->authstart) == -1) { + ast_log(LOG_ERROR, "error executing time(): %s; disconnecting client\n", strerror(errno)); + ast_atomic_fetchadd_int(&unauth_sessions, -1); + destroy_session(session); + goto done; + } + astman_append(&s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */ for (;;) { if ((res = do_message(&s)) < 0 || s.write_error) @@ -3291,6 +3342,7 @@ static void *session_do(void *data) ast_verb(2, "Manager '%s' logged off from %s\n", session->username, ast_inet_ntoa(session->sin.sin_addr)); ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", session->username, ast_inet_ntoa(session->sin.sin_addr)); } else { + ast_atomic_fetchadd_int(&unauth_sessions, -1); if (displayconnects) ast_verb(2, "Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(session->sin.sin_addr)); ast_log(LOG_EVENT, "Failed attempt from %s\n", ast_inet_ntoa(session->sin.sin_addr)); @@ -4186,6 +4238,8 @@ static int __init_manager(int reload) block_sockets = DEFAULT_BLOCKSOCKETS; timestampevents = DEFAULT_TIMESTAMPEVENTS; httptimeout = DEFAULT_HTTPTIMEOUT; + authtimeout = DEFAULT_AUTHTIMEOUT; + authlimit = DEFAULT_AUTHLIMIT; if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_NOTICE, "Unable to open AMI configuration manager.conf, or configuration is invalid. Asterisk management interface (AMI) disabled.\n"); @@ -4250,6 +4304,22 @@ static int __init_manager(int reload) manager_debug = ast_true(val); } else if (!strcasecmp(var->name, "httptimeout")) { newhttptimeout = atoi(val); + } else if (!strcasecmp(var->name, "authtimeout")) { + int timeout = atoi(var->value); + + if (timeout < 1) { + ast_log(LOG_WARNING, "Invalid authtimeout value '%s', using default value\n", var->value); + } else { + authtimeout = timeout; + } + } else if (!strcasecmp(var->name, "authlimit")) { + int limit = atoi(var->value); + + if (limit < 1) { + ast_log(LOG_WARNING, "Invalid authlimit value '%s', using default value\n", var->value); + } else { + authlimit = limit; + } } else { ast_log(LOG_NOTICE, "Invalid keyword <%s> = <%s> in manager.conf [general]\n", var->name, val);