--- chan_sip.c.orig 2004-09-27 10:14:10.000000000 +0200 +++ chan_sip.c 2004-10-18 18:58:00.000000000 +0200 @@ -55,6 +55,8 @@ #include #include #include +#include +#include #include #include @@ -256,7 +258,6 @@ static struct sip_pvt { ast_mutex_t lock; /* Channel private lock */ char callid[80]; /* Global CallID */ - char randdata[80]; /* Random data */ unsigned int ocseq; /* Current outgoing seqno */ unsigned int icseq; /* Current incoming seqno */ unsigned int callgroup; /* Call group */ @@ -547,7 +548,7 @@ static struct ast_frame *sip_read(struct ast_channel *ast); static int transmit_response(struct sip_pvt *p, char *msg, struct sip_request *req); static int transmit_response_with_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans); -static int transmit_response_with_auth(struct sip_pvt *p, char *msg, struct sip_request *req, char *rand, int reliable, char *header); +static int transmit_response_with_auth(struct sip_pvt *p, char *msg, struct sip_request *req, int reliable, char *header, int stale); static int transmit_request(struct sip_pvt *p, char *msg, int inc, int reliable, int newbranch); static int transmit_request_with_auth(struct sip_pvt *p, char *msg, int inc, int reliable, int newbranch); static int transmit_invite(struct sip_pvt *p, char *msg, int sendsdp, char *auth, char *authheader, char *vxml_url,char *distinctive_ring, char *osptoken,int init); @@ -895,6 +896,102 @@ return res; } +/*--- md5_hash: Produce MD5 hash of value. Used for authentication ---*/ +static void md5_hash(char *output, char *input) +{ + struct MD5Context md5; + unsigned char digest[16]; + char *ptr; + int x; + MD5Init(&md5); + MD5Update(&md5, input, strlen(input)); + MD5Final(digest, &md5); + ptr = output; + for (x=0;x<16;x++) + ptr += sprintf(ptr, "%2.2x", digest[x]); +} + +static char nonce_secret[12]; +static char last_nonce[48]; +static time_t time_nonce = 0; +static int nonce_age = 5; +static ast_mutex_t nonce_lock; +static int nonce_stats[3]; + +/*--- init_nonce: Initializes the nonce generator ---*/ +static void init_nonce(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + srand(tv.tv_sec); + snprintf(nonce_secret, sizeof(nonce_secret), "%08x", (int)(rand() ^ tv.tv_usec)); + memset(last_nonce, 0, sizeof(last_nonce)); + memset(nonce_stats, 0, sizeof(nonce_stats)); +} + +/*--- build_nonce: Builds a hashed, timestamped nonce ---*/ +/* The nonce is dynamically allocated and must be free()d later + The format is like: MD5("$secret.$time").$time */ +static char *build_nonce(void) +{ + char *ret; + time_t t; + ast_mutex_lock(&nonce_lock); + t = time(NULL); + /* Regenerate nonce at most once every second */ + if (time_nonce != t) { + char buf[32]; + char *ptr; + time_nonce = t; + snprintf(buf, sizeof(buf), "%s.%ld", nonce_secret, (long)t); + md5_hash(last_nonce, buf); + ptr = last_nonce + strlen(last_nonce); + snprintf(ptr, sizeof(last_nonce) - strlen(last_nonce), ".%ld", (long)t); + nonce_stats[0]++; + } + ret = strdup(last_nonce); + ast_mutex_unlock(&nonce_lock); + return ret; +} + +/*--- check_nonce: Return the age (in seconds) of the provided nonce ---*/ +/* Returns negative if nonce is invalid or lies in the future */ +static long check_nonce(char *nonce) +{ + char buf[32], buf2[48]; + char *ptr; + char *p; + time_t t; + if (!(nonce && *nonce)) + return -1; + /* First quick check if it matches the last generated nonce + This saves us about 50% from going the long crypto way */ + ast_mutex_lock(&nonce_lock); + nonce_stats[1]++; + t = strcmp(nonce, last_nonce) ? -1 : time(NULL) - time_nonce; + ast_mutex_unlock(&nonce_lock); + if (t >= 0) + return t; + /* Now isolate the dot and make sure a valid long int follows */ + p = strrchr(nonce, '.'); + if (!p) + return -1; + t = strtol(p+1, &p, 10); + if (!p || *p) + return -1; + /* We have a possibly good nonce - recreate and compare */ + ast_mutex_lock(&nonce_lock); + nonce_stats[2]++; + ast_mutex_unlock(&nonce_lock); + snprintf(buf, sizeof(buf), "%s.%ld", nonce_secret, (long)t); + md5_hash(buf2, buf); + ptr = buf2 + strlen(buf2); + snprintf(ptr, sizeof(buf2) - strlen(buf2), ".%ld", (long)t); + if (strcmp(nonce, buf2) != 0) + return -1; + return time(NULL) - t; +} + /*--- send_request: Send SIP Request to the other part of the dialogue ---*/ static int send_request(struct sip_pvt *p, struct sip_request *req, int reliable, int seqno) { @@ -3292,16 +3389,19 @@ } /* transmit_response_with_auth: Respond with authorization request */ -static int transmit_response_with_auth(struct sip_pvt *p, char *msg, struct sip_request *req, char *randdata, int reliable, char *header) +static int transmit_response_with_auth(struct sip_pvt *p, char *msg, struct sip_request *req, int reliable, char *header, int stale) { struct sip_request resp; char tmp[256]; + char *nonce = 0; int seqno = 0; if (reliable && (sscanf(get_header(req, "CSeq"), "%i ", &seqno) != 1)) { ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", get_header(req, "CSeq")); return -1; } - snprintf(tmp, sizeof(tmp), "Digest realm=\"%s\", nonce=\"%s\"", global_realm, randdata); + nonce = build_nonce(); + snprintf(tmp, sizeof(tmp), "Digest realm=\"%s\", nonce=\"%s\", stale=%s", global_realm, nonce, stale ? "TRUE" : "FALSE"); + free(nonce); respprep(&resp, p, msg, req); add_header(&resp, header, tmp); add_header(&resp, "Content-Length", "0"); @@ -4621,25 +4721,10 @@ list_route(p->route); } -/*--- md5_hash: Produce MD5 hash of value. Used for authentication ---*/ -static void md5_hash(char *output, char *input) -{ - struct MD5Context md5; - unsigned char digest[16]; - char *ptr; - int x; - MD5Init(&md5); - MD5Update(&md5, input, strlen(input)); - MD5Final(digest, &md5); - ptr = output; - for (x=0;x<16;x++) - ptr += sprintf(ptr, "%2.2x", digest[x]); -} - /*--- check_auth: Check user authorization from peer definition ---*/ /* Some actions, like REGISTER and INVITEs from peers require authentication (if peer have secret set) */ -static int check_auth(struct sip_pvt *p, struct sip_request *req, char *randdata, int randlen, char *username, char *secret, char *md5secret, char *method, char *uri, int reliable, int ignore) +static int check_auth(struct sip_pvt *p, struct sip_request *req, char *username, char *secret, char *md5secret, char *method, char *uri, int reliable, int ignore) { int res = -1; char *response = "407 Proxy Authentication Required"; @@ -4686,22 +4771,19 @@ } #endif authtoken = get_header(req, reqheader); - if (ignore && !ast_strlen_zero(randdata) && ast_strlen_zero(authtoken)) { + if (ignore && ast_strlen_zero(authtoken)) { /* This is a retransmitted invite/register/etc, don't reconstruct authentication information */ - if (!ast_strlen_zero(randdata)) { - if (!reliable) { - /* Resend message if this was NOT a reliable delivery. Otherwise the - retransmission should get it */ - transmit_response_with_auth(p, response, req, randdata, reliable, respheader); - /* Schedule auto destroy in 15 seconds */ - sip_scheddestroy(p, 15000); - } + if (!reliable) { + /* Resend message if this was NOT a reliable delivery. Otherwise the + retransmission should get it */ + transmit_response_with_auth(p, response, req, reliable, respheader, 0); + /* Schedule auto destroy in 15 seconds */ + sip_scheddestroy(p, 15000); res = 1; } - } else if (ast_strlen_zero(randdata) || ast_strlen_zero(authtoken)) { - snprintf(randdata, randlen, "%08x", rand()); - transmit_response_with_auth(p, response, req, randdata, reliable, respheader); + } else if (ast_strlen_zero(authtoken)) { + transmit_response_with_auth(p, response, req, reliable, respheader, 0); /* Schedule auto destroy in 15 seconds */ sip_scheddestroy(p, 15000); res = 1; @@ -4717,8 +4799,10 @@ char tmp[256] = ""; char *c; char *z; - char *response =""; + char *resp_auth =""; char *resp_uri =""; + char *resp_nonce = ""; + long age; /* Find their response among the mess that we'r sent for comparison */ strncpy(tmp, authtoken, sizeof(tmp) - 1); @@ -4731,12 +4815,12 @@ if (!strncasecmp(c, "response=", strlen("response="))) { c+= strlen("response="); if ((*c == '\"')) { - response=++c; + resp_auth=++c; if((c = strchr(c,'\"'))) *c = '\0'; } else { - response=c; + resp_auth=c; if((c = strchr(c,','))) *c = '\0'; } @@ -4753,31 +4837,49 @@ *c = '\0'; } + } else if (!strncasecmp(c, "nonce=", strlen("nonce="))) { + c+= strlen("nonce="); + if ((*c == '\"')) { + resp_nonce=++c; + if((c = strchr(c,'\"'))) + *c = '\0'; + } else { + resp_nonce=c; + if((c = strchr(c,','))) + *c = '\0'; + } + } else if ((z = strchr(c,' ')) || (z = strchr(c,','))) c=z; if (c) c++; } - snprintf(a1, sizeof(a1), "%s:%s:%s", username, global_realm, secret); - if(!ast_strlen_zero(resp_uri)) - snprintf(a2, sizeof(a2), "%s:%s", method, resp_uri); - else - snprintf(a2, sizeof(a2), "%s:%s", method, uri); - if (!ast_strlen_zero(md5secret)) - snprintf(a1_hash, sizeof(a1_hash), "%s", md5secret); - else - md5_hash(a1_hash, a1); - md5_hash(a2_hash, a2); - snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, randdata, a2_hash); - md5_hash(resp_hash, resp); - - /* resp_hash now has the expected response, compare the two */ - - if (response && !strncasecmp(response, resp_hash, strlen(resp_hash))) { - /* Auth is OK */ - res = 0; - } - /* Assume success ;-) */ + age = check_nonce(resp_nonce); + if (age >= 0) { + snprintf(a1, sizeof(a1), "%s:%s:%s", username, global_realm, secret); + if(!ast_strlen_zero(resp_uri)) + snprintf(a2, sizeof(a2), "%s:%s", method, resp_uri); + else + snprintf(a2, sizeof(a2), "%s:%s", method, uri); + if (!ast_strlen_zero(md5secret)) + snprintf(a1_hash, sizeof(a1_hash), "%s", md5secret); + else + md5_hash(a1_hash, a1); + md5_hash(a2_hash, a2); + snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, resp_nonce, a2_hash); + md5_hash(resp_hash, resp); + + /* resp_hash now has the expected response, compare the two */ + + if (resp_auth && !strncasecmp(resp_auth, resp_hash, strlen(resp_hash))) { + /* Auth is OK, nonce may be expired */ + res = (age <= nonce_age) ? 0 : 2; + } + /* Assume success ;-) */ + } else + res = 1; + if (res > 0) + transmit_response_with_auth(p, response, req, reliable, respheader, res == 2); } return res; } @@ -4851,7 +4953,7 @@ } else { p->nat = peer->nat; transmit_response(p, "100 Trying", req); - if (!(res = check_auth(p, req, p->randdata, sizeof(p->randdata), peer->name, peer->secret, peer->md5secret, "REGISTER", uri, 0, ignore))) { + if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, "REGISTER", uri, 0, ignore))) { sip_cancel_destroy(p); if (parse_contact(p, peer, req)) { ast_log(LOG_WARNING, "Failed to parse contact info\n"); @@ -5380,7 +5482,7 @@ ast_log(LOG_DEBUG, "Setting NAT on VRTP to %d\n", (p->nat & SIP_NAT_ROUTE)); ast_rtp_setnat(p->vrtp, (p->nat & SIP_NAT_ROUTE)); } - if (!(res = check_auth(p, req, p->randdata, sizeof(p->randdata), user->name, user->secret, user->md5secret, cmd, uri, reliable, ignore))) { + if (!(res = check_auth(p, req, user->name, user->secret, user->md5secret, cmd, uri, reliable, ignore))) { sip_cancel_destroy(p); if (!ast_strlen_zero(user->context)) strncpy(p->context, user->context, sizeof(p->context) - 1); @@ -5464,7 +5566,7 @@ p->peersecret[0] = '\0'; p->peermd5secret[0] = '\0'; } - if (!(res = check_auth(p, req, p->randdata, sizeof(p->randdata), peer->name, p->peersecret, p->peermd5secret, cmd, uri, reliable, ignore))) { + if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, cmd, uri, reliable, ignore))) { p->canreinvite = peer->canreinvite; strncpy(p->peername, peer->name, sizeof(p->peername) - 1); strncpy(p->authname, peer->name, sizeof(p->authname) - 1); @@ -5592,6 +5694,18 @@ #undef FORMAT2 } +/*--- sip_show_nonce: CLI Command to display session nonce secret and age ---*/ +static int sip_show_nonce(int fd, int argc, char *argv[]) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_mutex_lock(&nonce_lock); + ast_cli(fd, "SIP Session nonce secret is %s, nonce valid for %ds\n", nonce_secret, nonce_age); + ast_cli(fd, "SIP nonce stats: generated %d, checked %d, cryptochecked %d\n", nonce_stats[0], nonce_stats[1], nonce_stats[2]); + ast_mutex_unlock(&nonce_lock); + return RESULT_SUCCESS; +} + static char *nat2str(int nat) { switch(nat) { @@ -6399,6 +6513,10 @@ "Usage: sip show history \n" " Provides detailed dialog history on a given SIP channel.\n"; +static char show_nonce_usage[] = +"Usage: sip show nonce\n" +" Displays the session secret used to generate challenge nonces.\n"; + static char show_peers_usage[] = "Usage: sip show peers\n" " Lists all known SIP peers.\n"; @@ -6452,6 +6570,8 @@ { { "sip", "show", "channel", NULL }, sip_show_channel, "Show detailed SIP channel info", show_channel_usage, complete_sipch }; static struct ast_cli_entry cli_show_history = { { "sip", "show", "history", NULL }, sip_show_history, "Show SIP dialog history", show_history_usage, complete_sipch }; +static struct ast_cli_entry cli_show_nonce = + { { "sip", "show", "nonce", NULL }, sip_show_nonce, "Show SIP Session nonce secret and age", show_nonce_usage }; static struct ast_cli_entry cli_debug_ip = { { "sip", "debug", "ip", NULL }, sip_do_debug, "Enable SIP debugging on IP", debug_usage }; static struct ast_cli_entry cli_debug_peer = @@ -7534,7 +7654,9 @@ } check_pendings(p); } - if (!p->lastinvite && ast_strlen_zero(p->randdata)) + /* CHECKME */ +/* if (!p->lastinvite && ast_strlen_zero(p->randdata)) */ + if (!p->lastinvite) p->needdestroy = 1; } else if (!strcasecmp(cmd, "SIP/2.0")) { extract_uri(p, req); @@ -8624,6 +8746,9 @@ } else { ast_log(LOG_WARNING, "Invalid port number '%s' at line %d of %s\n", v->value, v->lineno, config); } + } else if (!strcasecmp(v->name, "nonceage")) { + if ((sscanf(v->value, "%i", &nonce_age) != 1) || nonce_age <= 0 || nonce_age > 600) + nonce_age = 5; #ifdef MYSQL_FRIENDS } else if (!strcasecmp(v->name, "dbuser")) { strncpy(mydbuser, v->value, sizeof(mydbuser) - 1); @@ -8995,6 +9120,8 @@ ast_mutex_init(&userl.lock); ast_mutex_init(&peerl.lock); ast_mutex_init(®l.lock); + ast_mutex_init(&nonce_lock); + init_nonce(); sched = sched_context_create(); if (!sched) { ast_log(LOG_WARNING, "Unable to create schedule context\n"); @@ -9022,6 +9149,7 @@ ast_cli_register(&cli_show_peers_include); ast_cli_register(&cli_show_peers_exclude); ast_cli_register(&cli_show_registry); + ast_cli_register(&cli_show_nonce); ast_cli_register(&cli_debug); ast_cli_register(&cli_debug_ip); ast_cli_register(&cli_debug_peer); @@ -9065,6 +9193,7 @@ ast_cli_unregister(&cli_show_peers_exclude); ast_cli_unregister(&cli_show_peers_begin); ast_cli_unregister(&cli_show_registry); + ast_cli_unregister(&cli_show_nonce); ast_cli_unregister(&cli_show_subscriptions); ast_cli_unregister(&cli_debug); ast_cli_unregister(&cli_debug_ip); @@ -9126,6 +9255,7 @@ ast_mutex_destroy(&userl.lock); ast_mutex_destroy(&peerl.lock); ast_mutex_destroy(®l.lock); + ast_mutex_destroy(&nonce_lock); return 0; }