Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 337324) +++ channels/chan_sip.c (working copy) @@ -1625,6 +1625,283 @@ */ struct ast_channel_tech sip_tech_info; +/*------- Generate Security Events -------- */ + +static enum ast_security_event_transport_type security_event_get_transport(const struct sip_pvt *p) +{ + int res = 0; + + switch (p->socket.type) { + case SIP_TRANSPORT_UDP: + return AST_SECURITY_EVENT_TRANSPORT_UDP; + case SIP_TRANSPORT_TCP: + return AST_SECURITY_EVENT_TRANSPORT_TCP; + case SIP_TRANSPORT_TLS: + return AST_SECURITY_EVENT_TRANSPORT_TLS; + } + + return res; +} + +static struct sockaddr_in *security_event_encode_sin_local(const struct sip_pvt *p, struct sockaddr_in *sin_local) +{ + ast_sockaddr_to_sin(&p->ourip, sin_local); + + return sin_local; +} + +static struct sockaddr_in *security_event_encode_sin_remote(const struct sip_pvt *p, struct sockaddr_in *sin_remote) +{ + ast_sockaddr_to_sin(&p->sa, sin_remote); + + return sin_remote; +} + +void sip_report_invalid_peer(const struct sip_pvt *p) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct ast_security_event_inval_acct_id inval_acct_id = { + .common.event_type = AST_SECURITY_EVENT_INVAL_ACCT_ID, + .common.version = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION, + .common.service = "SIP", + .common.account_id = p->exten, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + }; + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&inval_acct_id)); +} + +void sip_report_failed_acl(const struct sip_pvt *p, const char *aclname) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct ast_security_event_failed_acl failed_acl_event = { + .common.event_type = AST_SECURITY_EVENT_FAILED_ACL, + .common.version = AST_SECURITY_EVENT_FAILED_ACL_VERSION, + .common.service = "SIP", + .common.account_id = p->exten, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + .acl_name = aclname, + }; + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&failed_acl_event)); +} + +void sip_report_inval_password(const struct sip_pvt *p, const char *response_challenge, const char *response_hash) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct ast_security_event_inval_password inval_password = { + .common.event_type = AST_SECURITY_EVENT_INVAL_PASSWORD, + .common.version = AST_SECURITY_EVENT_INVAL_PASSWORD_VERSION, + .common.service = "SIP", + .common.account_id = p->exten, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + + .challenge = p->randdata, + .received_challenge = response_challenge, + .received_hash = response_hash, + }; + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&inval_password)); +} + +void sip_report_auth_success(const struct sip_pvt *p, uint32_t *using_password) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct ast_security_event_successful_auth successful_auth = { + .common.event_type = AST_SECURITY_EVENT_SUCCESSFUL_AUTH, + .common.version = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION, + .common.service = "SIP", + .common.account_id = p->exten, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + .using_password = using_password, + }; + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&successful_auth)); +} + +void sip_report_session_limit(const struct sip_pvt *p) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct ast_security_event_session_limit session_limit = { + .common.event_type = AST_SECURITY_EVENT_SESSION_LIMIT, + .common.version = AST_SECURITY_EVENT_SESSION_LIMIT_VERSION, + .common.service = "SIP", + .common.account_id = p->exten, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + }; + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&session_limit)); +} + +void sip_report_failed_challenge_response(const struct sip_pvt *p, const char *response, const char *expected_response) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + char account_id[256]; + + struct ast_security_event_chal_resp_failed chal_resp_failed = { + .common.event_type = AST_SECURITY_EVENT_CHAL_RESP_FAILED, + .common.version = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION, + .common.service = "SIP", + .common.account_id = account_id, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + + .challenge = p->randdata, + .response = response, + .expected_response = expected_response, + }; + + if (!ast_strlen_zero(p->from)) { /* When dialing, show account making call */ + ast_copy_string(account_id, p->from, sizeof(account_id)); + } else { + ast_copy_string(account_id, p->exten, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); +} + +void sip_report_chal_sent(const struct sip_pvt *p) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + char account_id[256]; + + struct ast_security_event_chal_sent chal_sent = { + .common.event_type = AST_SECURITY_EVENT_CHAL_SENT, + .common.version = AST_SECURITY_EVENT_CHAL_SENT_VERSION, + .common.service = "SIP", + .common.account_id = account_id, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + + .challenge = p->randdata, + }; + + if (!ast_strlen_zero(p->from)) { /* When dialing, show account making call */ + ast_copy_string(account_id, p->from, sizeof(account_id)); + } else { + ast_copy_string(account_id, p->exten, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&chal_sent)); +} + +void sip_report_inval_transport(const struct sip_pvt *p, const char *transport) +{ + char session_id[32]; + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct ast_security_event_inval_transport inval_transport = { + .common.event_type = AST_SECURITY_EVENT_INVAL_TRANSPORT, + .common.version = AST_SECURITY_EVENT_INVAL_TRANSPORT_VERSION, + .common.service = "SIP", + .common.account_id = p->exten, + .common.local_addr = { + .sin = security_event_encode_sin_local(p, &sin_local), + .transport = security_event_get_transport(p) + }, + .common.remote_addr = { + .sin = security_event_encode_sin_remote(p, &sin_remote), + .transport = security_event_get_transport(p) + }, + .common.session_id = session_id, + + .transport = transport, + }; + + snprintf(session_id, sizeof(session_id), "%p", p); + + ast_security_event_report(AST_SEC_EVT(&inval_transport)); +} + +/*------- CC Support -------- */ static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan); static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent); static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent); @@ -14145,6 +14422,34 @@ } } +/*! \brief Takes the digest response and parses it */ +void sip_digest_parser(char *c, struct digestkeys *keys) +{ + struct digestkeys *i = i; + + while(c && *(c = ast_skip_blanks(c)) ) { /* lookup for keys */ + for (i = keys; i->key != NULL; i++) { + const char *separator = ","; /* default */ + + if (strncasecmp(c, i->key, strlen(i->key)) != 0) { + continue; + } + /* Found. Skip keyword, take text in quotes or up to the separator. */ + c += strlen(i->key); + if (*c == '"') { /* in quotes. Skip first and look for last */ + c++; + separator = "\""; + } + i->s = c; + strsep(&c, separator); + break; + } + if (i->key == NULL) { /* not found, jump after space or comma */ + strsep(&c, " ,"); + } + } +} + AST_THREADSTORAGE(check_auth_buf); #define CHECK_AUTH_BUF_INITLEN 256 @@ -14170,11 +14475,7 @@ int res; /* table of recognised keywords, and their value in the digest */ - enum keys { K_RESP, K_URI, K_USER, K_NONCE, K_LAST }; - struct x { - const char *key; - const char *s; - } *i, keys[] = { + struct digestkeys keys[] = { [K_RESP] = { "response=", "" }, [K_URI] = { "uri=", "" }, [K_USER] = { "username=", "" }, @@ -14183,8 +14484,9 @@ }; /* Always OK if no secret */ - if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) + if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { return AUTH_SUCCESSFUL; + } /* Always auth with WWW-auth since we're NOT a proxy */ /* Using proxy-auth in a B2BUA may block proxy authorization in the same transaction */ @@ -14235,28 +14537,8 @@ c = buf->str; - while(c && *(c = ast_skip_blanks(c)) ) { /* lookup for keys */ - for (i = keys; i->key != NULL; i++) { - const char *separator = ","; /* default */ + sip_digest_parser(c, keys); - if (strncasecmp(c, i->key, strlen(i->key)) != 0) { - continue; - } - /* Found. Skip keyword, take text in quotes or up to the separator. */ - c += strlen(i->key); - if (*c == '"') { /* in quotes. Skip first and look for last */ - c++; - separator = "\""; - } - i->s = c; - strsep(&c, separator); - break; - } - if (i->key == NULL) { /* not found, jump after space or comma */ - strsep(&c, " ,"); - } - } - /* Verify that digest username matches the username we auth as */ if (strcmp(username, keys[K_USER].s)) { ast_log(LOG_WARNING, "username mismatch, have <%s>, digest has <%s>\n", @@ -22190,7 +22472,7 @@ */ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct ast_sockaddr *addr, int *recount, const char *e, int *nounlock) { - int res = 1; + int res = INV_REQ_SUCCESS; int gotdest; const char *p_replaces; char *replace_id = NULL; @@ -22242,7 +22524,7 @@ p->invitestate = INV_COMPLETED; if (!p->lastinvite) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } } @@ -22270,7 +22552,7 @@ transmit_response(p, "482 Loop Detected", req); p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - res = 0; + res = INV_REQ_FAILED; goto request_invite_cleanup; } else { /*! This is a spiral. What we need to do is to just change the outgoing INVITE @@ -22297,7 +22579,7 @@ */ ast_string_field_set(p->owner, call_forward, peerorhost); ast_queue_control(p->owner, AST_CONTROL_BUSY); - res = 0; + res = INV_REQ_FAILED; goto request_invite_cleanup; } } @@ -22336,7 +22618,7 @@ transmit_response_reliable(p, "491 Request Pending", req); ast_debug(1, "Got INVITE on call where we already have pending INVITE, deferring that - %s\n", p->callid); /* Don't destroy dialog here */ - res = 0; + res = INV_REQ_FAILED; goto request_invite_cleanup; } } @@ -22354,7 +22636,7 @@ ast_debug(3, "INVITE w Replaces on existing call? Refusing action. [%s]\n", p->callid); transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */ /* Do not destroy existing call */ - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } @@ -22369,7 +22651,7 @@ append_history(p, "Xfer", "INVITE/Replace Failed. Out of memory."); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); p->invitestate = INV_COMPLETED; - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } @@ -22470,7 +22752,7 @@ } refer_locked = 0; p->invitestate = INV_COMPLETED; - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } } @@ -22533,7 +22815,7 @@ } if (!p->lastinvite) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } ast_queue_control(p->owner, AST_CONTROL_SRCUPDATE); @@ -22564,7 +22846,6 @@ res = check_user_full(p, req, SIP_INVITE, e, XMIT_RELIABLE, addr, &authpeer); if (res == AUTH_CHALLENGE_SENT) { p->invitestate = INV_COMPLETED; /* Needs to restart in another INVITE transaction */ - res = 0; goto request_invite_cleanup; } if (res < 0) { /* Something failed in authentication */ @@ -22577,7 +22858,6 @@ } p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - res = 0; goto request_invite_cleanup; } @@ -22617,7 +22897,7 @@ p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_debug(1, "No compatible codecs for this SIP call.\n"); - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } } else { /* No SDP in invite, call control session */ @@ -22644,8 +22924,10 @@ transmit_response_reliable(p, "480 Temporarily Unavailable (Call limit) ", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); p->invitestate = INV_COMPLETED; + + res = AUTH_SESSION_LIMIT; } - res = 0; + goto request_invite_cleanup; } gotdest = get_destination(p, NULL, &cc_recall_core_id); /* Get destination right away */ @@ -22684,7 +22966,7 @@ p->invitestate = INV_COMPLETED; update_call_counter(p, DEC_CALL_LIMIT); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - res = 0; + res = INV_REQ_FAILED; goto request_invite_cleanup; } else { @@ -22756,7 +23038,7 @@ if (!p->lastinvite) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } @@ -22770,7 +23052,7 @@ if (!p->lastinvite) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } } @@ -22785,7 +23067,7 @@ if (!p->lastinvite) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } @@ -22816,7 +23098,7 @@ if (!p->lastinvite) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } - res = -1; + res = INV_REQ_ERROR; goto request_invite_cleanup; } break; @@ -22897,7 +23179,7 @@ ast_hangup(c); sip_pvt_lock(p); /* pvt is expected to remain locked on return, so re-lock it */ - res = 0; + res = INV_REQ_FAILED; goto request_invite_cleanup; } else { /* Go and take over the target call */ @@ -22948,6 +23230,7 @@ ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n"); p->invitestate = INV_COMPLETED; transmit_response_reliable(p, "480 Temporarily Unavailable", req); + res = AUTH_SESSION_LIMIT; break; case AST_PBX_SUCCESS: /* nothing to do */ @@ -24973,6 +25256,7 @@ if (sipdebug) ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid); check_via(p, req); + if ((res = register_verify(p, addr, req, e)) < 0) { const char *reason; @@ -24998,6 +25282,9 @@ case AUTH_BAD_TRANSPORT: reason = "Device not configured to use this transport type"; break; + case AUTH_RTP_FAILED: + reason = "RTP initialization failed"; + break; default: reason = "Unknown failure"; break; @@ -25011,14 +25298,104 @@ append_history(p, "RegRequest", "Succeeded : Account %s", get_header(req, "To")); } - if (res < 1) { + if (res != AUTH_CHALLENGE_SENT) { /* Destroy the session, but keep us around for just a bit in case they don't get our 200 OK */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } + return res; } +int sip_report_security_event(const struct sip_pvt *p, const struct sip_request *req, const int res) { + + struct sip_peer *peer_report; + enum check_auth_result res_report = res; + struct ast_str *buf; + char *c; + const char *authtoken; + char *reqheader, *respheader; + int result = 0; + char aclname[256]; + struct digestkeys keys[] = { + [K_RESP] = { "response=", "" }, + [K_URI] = { "uri=", "" }, + [K_USER] = { "username=", "" }, + [K_NONCE] = { "nonce=", "" }, + [K_LAST] = { NULL, NULL} + }; + + peer_report = find_peer(p->exten, NULL, TRUE, FINDPEERS, FALSE, 0); + + switch(res_report) { + case AUTH_DONT_KNOW: + break; + case AUTH_SUCCESSFUL: + if (peer_report) { + if (ast_strlen_zero(peer_report->secret) && ast_strlen_zero(peer_report->md5secret)) { + sip_report_auth_success(p, (uint32_t *) 0); + } else { + sip_report_auth_success(p, (uint32_t *) 1); + } + } + break; + case AUTH_CHALLENGE_SENT: + sip_report_chal_sent(p); + break; + case AUTH_SECRET_FAILED: + case AUTH_USERNAME_MISMATCH: + auth_headers(WWW_AUTH, &respheader, &reqheader); + authtoken = get_header(req, reqheader); + buf = ast_str_thread_get(&check_auth_buf, CHECK_AUTH_BUF_INITLEN); + ast_str_set(&buf, 0, "%s", authtoken); + c = buf->str; + + sip_digest_parser(c, keys); + + if (res_report == AUTH_SECRET_FAILED) { + sip_report_inval_password(p, keys[K_NONCE].s, keys[K_RESP].s); + } else { + if (peer_report) { + sip_report_failed_challenge_response(p, keys[K_USER].s, peer_report->username); + } + } + break; + case AUTH_NOT_FOUND: + /* with sip_cfg.alwaysauthreject on, generates 2 events */ + sip_report_invalid_peer(p); + break; + case AUTH_FAKE_AUTH: + break; + case AUTH_UNKNOWN_DOMAIN: + snprintf(aclname, sizeof(aclname), "domain_must_match"); + sip_report_failed_acl(p, aclname); + break; + case AUTH_PEER_NOT_DYNAMIC: + snprintf(aclname, sizeof(aclname), "peer_not_dynamic"); + sip_report_failed_acl(p, aclname); + break; + case AUTH_ACL_FAILED: + /* with sip_cfg.alwaysauthreject on, generates 2 events */ + snprintf(aclname, sizeof(aclname), "device_must_match_acl"); + sip_report_failed_acl(p, aclname); + break; + case AUTH_BAD_TRANSPORT: + sip_report_inval_transport(p, get_transport(req->socket.type)); + break; + case AUTH_RTP_FAILED: + break; + case AUTH_SESSION_LIMIT: + sip_report_session_limit(p); + break; + } + + if (peer_report) { + unref_peer(peer_report, "sip_report_security_event: unref_peer: from handle_incoming"); + } + + return result; +} + /*! * \brief Handle incoming SIP requests (methods) * \note @@ -25224,6 +25601,26 @@ break; case SIP_INVITE: res = handle_request_invite(p, req, debug, seqno, addr, recount, e, nounlock); + + if (res < 9) { + sip_report_security_event(p, req, res); + } + + switch (res) { + case INV_REQ_SUCCESS: + res = 1; + break; + case INV_REQ_FAILED: + res = 0; + break; + case INV_REQ_ERROR: + res = -1; + break; + default: + res = 0; + break; + } + break; case SIP_REFER: res = handle_request_refer(p, req, debug, seqno, nounlock); @@ -25245,6 +25642,7 @@ break; case SIP_REGISTER: res = handle_request_register(p, req, addr, e); + sip_report_security_event(p, req, res); break; case SIP_INFO: if (req->debug) Index: channels/sip/include/sip.h =================================================================== --- channels/sip/include/sip.h (revision 337324) +++ channels/sip/include/sip.h (working copy) @@ -32,6 +32,7 @@ #include "asterisk/channel.h" #include "asterisk/app.h" #include "asterisk/astobj.h" +#include "asterisk/security_events.h" #ifndef FALSE #define FALSE 0 @@ -380,6 +381,19 @@ AST_FAILURE = -1, /*!< Failure code */ }; +/*! \brief The results from handling an invite request + * + * \note Start at these values so we do not conflict with + * check_auth_results values when returning from + * handle_request_invite. check_auth_results only returned during + * authentication routines + * */ +enum inv_req_result { + INV_REQ_SUCCESS = 11, /*!< Success code */ + INV_REQ_FAILED = 10, /*!< Failure code */ + INV_REQ_ERROR = 9, /*!< Error code */ +}; + /*! \brief States for the INVITE transaction, not the dialog * \note this is for the INVITE that sets up the dialog */ @@ -471,7 +485,8 @@ AUTH_PEER_NOT_DYNAMIC = -6, AUTH_ACL_FAILED = -7, AUTH_BAD_TRANSPORT = -8, - AUTH_RTP_FAILED = 9, + AUTH_RTP_FAILED = -9, + AUTH_SESSION_LIMIT = -10, }; /*! \brief States for outbound registrations (with register= lines in sip.conf */ @@ -632,6 +647,13 @@ TCPTLS_ALERT_STOP, /*!< \brief A request to stop the tcp_handler thread */ }; +enum digest_keys { + K_RESP, + K_URI, + K_USER, + K_NONCE, + K_LAST +}; /*----------------------------------------------------------*/ /*---- STRUCTS ----*/ @@ -1794,4 +1816,29 @@ { SIP_OPT_TARGET_DIALOG,NOT_SUPPORTED, "tdialog" }, }; +struct digestkeys { + const char *key; + const char *s; +}; + +/*----------------------------------------------------------*/ +/*---- FUNCTIONS ----*/ +/*----------------------------------------------------------*/ + +/*! \brief Generate security events + * + * The following functions will generate security events + * using the Security Events Framework + */ + +void sip_report_invalid_peer(const struct sip_pvt *p); +void sip_report_failed_acl(const struct sip_pvt *p, const char *aclname); +void sip_report_inval_password(const struct sip_pvt *p, const char *responsechallenge, const char *responsehash); +void sip_report_auth_success(const struct sip_pvt *p, uint32_t *using_password); +void sip_report_session_limit(const struct sip_pvt *p); +void sip_report_failed_challenge_response(const struct sip_pvt *p, const char *response, const char *expected_response); +void sip_report_chal_sent(const struct sip_pvt *p); +void sip_report_inval_transport(const struct sip_pvt *p, const char *transport); +void sip_digest_parser(char *c, struct digestkeys *keys); +int sip_report_security_event(const struct sip_pvt *p, const struct sip_request *req, const int res); #endif Index: CHANGES =================================================================== --- CHANGES (revision 337324) +++ CHANGES (working copy) @@ -185,6 +185,7 @@ ----------- * Add T38 support for REJECTED state where T.38 Negotiation is explicitly rejected. * Add option encryption_taglen to set auth taglen only 32 and 80 are supported currently. + * SIP now generates security events using the Security Events Framework for REGISTER and INVITE. Queue changes ------------- Index: include/asterisk/security_events_defs.h =================================================================== --- include/asterisk/security_events_defs.h (revision 337324) +++ include/asterisk/security_events_defs.h (working copy) @@ -111,7 +111,17 @@ * \brief An attempt at basic password authentication failed */ AST_SECURITY_EVENT_INVAL_PASSWORD, - /* \brief This _must_ stay at the end. */ + /*! + * \brief Challenge was sent out, informational + */ + AST_SECURITY_EVENT_CHAL_SENT, + /*! + * \brief An attempt to contact a peer on an invalid transport. + */ + AST_SECURITY_EVENT_INVAL_TRANSPORT, + /*! + * \brief This _must_ stay at the end. + */ AST_SECURITY_EVENT_NUM_TYPES }; @@ -393,6 +403,11 @@ * \note Account ID required */ struct ast_security_event_common common; + /*! + * \brief Using password - if a password was used or not + * \note required, 0 = no, 1 = yes + */ + uint32_t *using_password; }; /*! @@ -455,14 +470,71 @@ * \brief Event descriptor version * \note This _must_ be changed if this event descriptor is changed. */ - #define AST_SECURITY_EVENT_INVAL_PASSWORD_VERSION 1 + #define AST_SECURITY_EVENT_INVAL_PASSWORD_VERSION 2 /*! * \brief Common security event descriptor elements * \note Account ID required */ struct ast_security_event_common common; + /*! + * \brief Challenge provided + * \note required + */ + const char *challenge; + /*! + * \brief Challenge received + * \note required + */ + const char *received_challenge; + /*! + * \brief Hash received + * \note required + */ + const char *received_hash; }; +/*! + * \brief A challenge was sent out + */ +struct ast_security_event_chal_sent { + /*! + * \brief Event descriptor version + * \note This _must_ be changed if this event descriptor is changed. + */ + #define AST_SECURITY_EVENT_CHAL_SENT_VERSION 1 + /*! + * \brief Common security event descriptor elements + * \note Account ID required + */ + struct ast_security_event_common common; + /*! + * \brief Challenge sent + * \note required + */ + const char *challenge; +}; + +/*! + * \brief Attempt to contact peer on invalid transport + */ +struct ast_security_event_inval_transport { + /*! + * \brief Event descriptor version + * \note This _must_ be changed if this event descriptor is changed. + */ + #define AST_SECURITY_EVENT_INVAL_TRANSPORT_VERSION 1 + /*! + * \brief Common security event descriptor elements + * \note Account ID required + */ + struct ast_security_event_common common; + /*! + * \brief Attempted transport + * \note required + */ + const char *transport; +}; + #if defined(__cplusplus) || defined(c_plusplus) } #endif Index: include/asterisk/event_defs.h =================================================================== --- include/asterisk/event_defs.h (revision 337324) +++ include/asterisk/event_defs.h (working copy) @@ -283,8 +283,13 @@ AST_EVENT_IE_CHALLENGE = 0x0032, AST_EVENT_IE_RESPONSE = 0x0033, AST_EVENT_IE_EXPECTED_RESPONSE = 0x0034, + AST_EVENT_IE_RECEIVED_CHALLENGE = 0x0035, + AST_EVENT_IE_RECEIVED_HASH = 0x0036, + AST_EVENT_IE_USING_PASSWORD = 0x0037, + AST_EVENT_IE_ATTEMPTED_TRANSPORT = 0x0038, + /*! \brief Must be the last IE value +1 */ - AST_EVENT_IE_TOTAL = 0x0035, + AST_EVENT_IE_TOTAL = 0x0039, }; /*! Index: main/event.c =================================================================== --- main/event.c (revision 337324) +++ main/event.c (working copy) @@ -264,6 +264,10 @@ [AST_EVENT_IE_CHALLENGE] = { AST_EVENT_IE_PLTYPE_STR, "Challenge" }, [AST_EVENT_IE_RESPONSE] = { AST_EVENT_IE_PLTYPE_STR, "Response" }, [AST_EVENT_IE_EXPECTED_RESPONSE] = { AST_EVENT_IE_PLTYPE_STR, "ExpectedResponse" }, + [AST_EVENT_IE_RECEIVED_CHALLENGE] = { AST_EVENT_IE_PLTYPE_STR, "ReceivedChallenge" }, + [AST_EVENT_IE_RECEIVED_HASH] = { AST_EVENT_IE_PLTYPE_STR, "ReceivedHash" }, + [AST_EVENT_IE_USING_PASSWORD] = { AST_EVENT_IE_PLTYPE_UINT, "UsingPassword" }, + }; const char *ast_event_get_type_name(const struct ast_event *event) Index: main/security_events.c =================================================================== --- main/security_events.c (revision 337324) +++ main/security_events.c (working copy) @@ -265,6 +265,7 @@ { AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) }, { AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) }, { AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) }, + { AST_EVENT_IE_USING_PASSWORD, SEC_EVT_FIELD(successful_auth, using_password) }, { AST_EVENT_IE_END, 0 } }, .optional_ies = { @@ -335,6 +336,9 @@ { AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) }, { AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) }, { AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) }, + { AST_EVENT_IE_CHALLENGE, SEC_EVT_FIELD(inval_password, challenge) }, + { AST_EVENT_IE_RECEIVED_CHALLENGE, SEC_EVT_FIELD(inval_password, received_challenge) }, + { AST_EVENT_IE_RECEIVED_HASH, SEC_EVT_FIELD(inval_password, received_hash) }, { AST_EVENT_IE_END, 0 } }, .optional_ies = { @@ -344,6 +348,52 @@ }, }, +[AST_SECURITY_EVENT_CHAL_SENT] = { + .name = "ChallengeSent", + .version = AST_SECURITY_EVENT_CHAL_SENT_VERSION, + .severity = AST_SECURITY_EVENT_SEVERITY_INFO, + .required_ies = { + { AST_EVENT_IE_EVENT_TV, 0 }, + { AST_EVENT_IE_SEVERITY, 0 }, + { AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) }, + { AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) }, + { AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) }, + { AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) }, + { AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) }, + { AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) }, + { AST_EVENT_IE_CHALLENGE, SEC_EVT_FIELD(chal_sent, challenge) }, + { AST_EVENT_IE_END, 0 } + }, + .optional_ies = { + { AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) }, + { AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) }, + { AST_EVENT_IE_END, 0 } + }, +}, + +[AST_SECURITY_EVENT_INVAL_TRANSPORT] = { + .name = "InvalidTransport", + .version = AST_SECURITY_EVENT_INVAL_TRANSPORT_VERSION, + .severity = AST_SECURITY_EVENT_SEVERITY_ERROR, + .required_ies = { + { AST_EVENT_IE_EVENT_TV, 0 }, + { AST_EVENT_IE_SEVERITY, 0 }, + { AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) }, + { AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) }, + { AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) }, + { AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) }, + { AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) }, + { AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) }, + { AST_EVENT_IE_ATTEMPTED_TRANSPORT, SEC_EVT_FIELD(inval_transport, transport) }, + { AST_EVENT_IE_END, 0 } + }, + .optional_ies = { + { AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) }, + { AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) }, + { AST_EVENT_IE_END, 0 } + }, +}, + #undef SEC_EVT_FIELD }; @@ -500,6 +550,9 @@ case AST_EVENT_IE_CHALLENGE: case AST_EVENT_IE_RESPONSE: case AST_EVENT_IE_EXPECTED_RESPONSE: + case AST_EVENT_IE_RECEIVED_CHALLENGE: + case AST_EVENT_IE_RECEIVED_HASH: + case AST_EVENT_IE_ATTEMPTED_TRANSPORT: { const char *str; @@ -519,6 +572,7 @@ break; } case AST_EVENT_IE_EVENT_VERSION: + case AST_EVENT_IE_USING_PASSWORD: { uint32_t val; val = *((const uint32_t *)(((const char *) sec) + ie_type->offset)); Index: configs/logger.conf.sample =================================================================== --- configs/logger.conf.sample (revision 337324) +++ configs/logger.conf.sample (working copy) @@ -76,6 +76,7 @@ ; verbose ; dtmf ; fax +; security ; ; Special filename "console" represents the system console ; @@ -104,6 +105,7 @@ ; you are in the process of debugging a specific issue. ; ;debug => debug +;security => security console => notice,warning,error ;console => notice,warning,error,debug messages => notice,warning,error