Codename Pineapple

Home page | Mailing list | Docs

Last updated: Sat Feb 3 05:00:55 2007

Asterisk developer's documentation :: Codename Pineapple


sip3_auth.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2007, Digium, Inc.
00005  * and Edvina AB, Sollentuna, Sweden (chan_sip3 changes/additions)
00006  *
00007  * Mark Spencer <markster@digium.com>
00008  *
00009  * See http://www.asterisk.org for more information about
00010  * the Asterisk project. Please do not directly contact
00011  * any of the maintainers of this project for assistance;
00012  * the project provides a web site, mailing lists and IRC
00013  * channels for your use.
00014  *
00015  * This program is free software, distributed under the terms of
00016  * the GNU General Public License Version 2. See the LICENSE file
00017  * at the top of the source tree.
00018  */
00019 
00020 /*!
00021  * \file
00022  * \brief Various SIP authentication functions
00023  * Version 3 of chan_sip
00024  *
00025  * \author Mark Spencer <markster@digium.com>
00026  * \author Olle E. Johansson <oej@edvina.net> (all the chan_sip3 changes)
00027  *
00028  * See Also:
00029  * \arg \ref AstCREDITS
00030  *
00031  */
00032 
00033 /*!
00034 \page chan_sip3_auth  chan_sip3: Authentication
00035 
00036    \par New authentication scheme
00037 
00038  Lets try to scetch a new
00039    CHAN_SIP3 Authentication scheme
00040 
00041    1) If a user claims to be FROM a domain we host
00042       - authenticate, always!
00043    2) If the request is to a URI published in the
00044       context specified in the [general] section
00045       or, lacking that, "default"
00046       - Let them in if the domain belongs
00047         to us
00048    3) If the call is to a service not published
00049       in the "free context"
00050       - Authenticate, regardless of domain
00051 
00052 We need to separate authentication from From: user names
00053 - account is matched on authentication user in the digest
00054 
00055 If policyfromauth = yes, then the From: username part needs
00056 to be equal to the auth user (default yes today)
00057 
00058 Each account can have a list of valid From: uri's
00059    (username@domain)
00060 
00061 - Will this work for subscriptions?
00062 
00063 This is for devices. Trunks will have separate settings.
00064 */
00065 
00066    
00067 
00068 #include "asterisk.h"
00069 
00070 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 53128 $")
00071 
00072 #include <stdio.h>
00073 #include <ctype.h>
00074 #include <string.h>
00075 #include <unistd.h>
00076 #include <sys/socket.h>
00077 #include <sys/ioctl.h>
00078 #include <net/if.h>
00079 #include <errno.h>
00080 #include <stdlib.h>
00081 #include <fcntl.h>
00082 #include <netdb.h>
00083 #include <signal.h>
00084 #include <sys/signal.h>
00085 #include <netinet/in.h>
00086 #include <netinet/in_systm.h>
00087 #include <arpa/inet.h>
00088 #include <netinet/ip.h>
00089 #include <regex.h>
00090 
00091 #include "asterisk/lock.h"
00092 #include "asterisk/channel.h"
00093 #include "asterisk/config.h"
00094 #include "asterisk/logger.h"
00095 #include "asterisk/module.h"
00096 #include "asterisk/pbx.h"
00097 #include "asterisk/options.h"
00098 #include "asterisk/sched.h"
00099 #include "asterisk/io.h"
00100 #include "asterisk/rtp.h"
00101 #include "asterisk/udptl.h"
00102 #include "asterisk/acl.h"
00103 #include "asterisk/manager.h"
00104 #include "asterisk/callerid.h"
00105 #include "asterisk/cli.h"
00106 #include "asterisk/app.h"
00107 #include "asterisk/musiconhold.h"
00108 #include "asterisk/dsp.h"
00109 #include "asterisk/features.h"
00110 #include "asterisk/srv.h"
00111 #include "asterisk/astdb.h"
00112 #include "asterisk/causes.h"
00113 #include "asterisk/utils.h"
00114 #include "asterisk/file.h"
00115 #include "asterisk/astobj.h"
00116 #include "asterisk/dnsmgr.h"
00117 #include "asterisk/devicestate.h"
00118 #include "asterisk/linkedlists.h"
00119 #include "asterisk/stringfields.h"
00120 #include "asterisk/monitor.h"
00121 #include "asterisk/localtime.h"
00122 #include "asterisk/abstract_jb.h"
00123 #include "asterisk/compiler.h"
00124 #include "sip3.h"
00125 
00126 #include "sip3funcs.h"     /* Moved functions */
00127 
00128 /*! \todo Move the sip_auth list to AST_LIST */
00129 
00130 struct sip_auth *authl = NULL;      /*!< Authentication list for realm authentication */
00131 
00132 /*! \brief return the request and response heade for a 401 or 407 code */
00133 void auth_headers(enum sip_auth_type code, char **header, char **respheader)
00134 {
00135    if (code == WWW_AUTH) {       /* 401 */
00136       *header = "WWW-Authenticate";
00137       *respheader = "Authorization";
00138    } else if (code == PROXY_AUTH) { /* 407 */
00139       *header = "Proxy-Authenticate";
00140       *respheader = "Proxy-Authorization";
00141    } else {
00142       ast_verbose("-- wrong response code %d\n", code);
00143       *header = *respheader = "Invalid";
00144    }
00145 }
00146 
00147 /*! \brief  Check user authorization from peer definition 
00148    Some actions, like REGISTER and INVITEs from peers require
00149    authentication (if peer have secret set) 
00150     \return 0 on success, non-zero on error
00151 */
00152 enum check_auth_result check_auth(struct sip_dialog *p, struct sip_request *req, const char *username,
00153                 const char *secret, const char *md5secret, int sipmethod,
00154                 char *uri, enum xmittype reliable, int ignore)
00155 {
00156    const char *response = "407 Proxy Authentication Required";
00157    char *reqheader;
00158    char *respheader;
00159    const char *authtoken;
00160    char a1_hash[256];
00161    char resp_hash[256]="";
00162    char tmp[BUFSIZ * 2];                /* Make a large enough buffer */
00163    char *c;
00164    int  wrongnonce = FALSE;
00165    int  good_response;
00166    const char *usednonce = p->randdata;
00167 
00168    /* table of recognised keywords, and their value in the digest */
00169    enum keys { K_RESP, K_URI, K_USER, K_NONCE, K_LAST };
00170    struct x {
00171       const char *key;
00172       const char *s;
00173    } *i, keys[] = {
00174       [K_RESP] = { "response=", "" },
00175       [K_URI] = { "uri=", "" },
00176       [K_USER] = { "username=", "" },
00177       [K_NONCE] = { "nonce=", "" },
00178       [K_LAST] = { NULL, NULL}
00179    };
00180 
00181    /* Always OK if no secret */
00182    if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret))
00183       return AUTH_SUCCESSFUL;
00184    response = "401 Unauthorized";
00185    auth_headers(WWW_AUTH, &respheader, &reqheader);   
00186    authtoken =  get_header(req, reqheader);  
00187    if (ignore && !ast_strlen_zero(p->randdata) && ast_strlen_zero(authtoken)) {
00188       /* This is a retransmitted invite/register/etc, don't reconstruct authentication
00189          information */
00190       if (!reliable) {
00191          /* Resend message if this was NOT a reliable delivery.   Otherwise the
00192             retransmission should get it */
00193          transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
00194          /* Schedule auto destroy in 32 seconds (according to RFC 3261) */
00195          sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
00196       }
00197       return AUTH_CHALLENGE_SENT;
00198    } else if (ast_strlen_zero(p->randdata) || ast_strlen_zero(authtoken)) {
00199       /* We have no auth, so issue challenge and request authentication */
00200       ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */
00201       transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
00202       /* Schedule auto destroy in 32 seconds */
00203       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
00204       return AUTH_CHALLENGE_SENT;
00205    } 
00206 
00207    /* --- We have auth, so check it */
00208 
00209    /* Whoever came up with the authentication section of SIP can suck my %&#$&* for not putting
00210          an example in the spec of just what it is you're doing a hash on. */
00211 
00212 
00213    /* Make a copy of the response and parse it */
00214    ast_copy_string(tmp, authtoken, sizeof(tmp));
00215    c = tmp;
00216 
00217    while(c && *(c = ast_skip_blanks(c)) ) { /* lookup for keys */
00218       for (i = keys; i->key != NULL; i++) {
00219          const char *separator = ",";  /* default */
00220 
00221          if (strncasecmp(c, i->key, strlen(i->key)) != 0)
00222             continue;
00223          /* Found. Skip keyword, take text in quotes or up to the separator. */
00224          c += strlen(i->key);
00225          if (*c == '"') { /* in quotes. Skip first and look for last */
00226             c++;
00227             separator = "\"";
00228          }
00229          i->s = c;
00230          strsep(&c, separator);
00231          break;
00232       }
00233       if (i->key == NULL) /* not found, jump after space or comma */
00234          strsep(&c, " ,");
00235    }
00236 
00237    /* Verify that digest username matches  the username we auth as */
00238    if (strcmp(username, keys[K_USER].s)) {
00239       ast_log(LOG_WARNING, "username mismatch, have <%s>, digest has <%s>\n",
00240          username, keys[K_USER].s);
00241       /* Oops, we're trying something here */
00242       return AUTH_USERNAME_MISMATCH;
00243    }
00244 
00245    /* Verify nonce from request matches our nonce.  If not, send 401 with new nonce */
00246    if (strcasecmp(p->randdata, keys[K_NONCE].s)) { /* XXX it was 'n'casecmp ? */
00247       wrongnonce = TRUE;
00248       usednonce = keys[K_NONCE].s;
00249    }
00250 
00251    if (!ast_strlen_zero(md5secret))
00252       ast_copy_string(a1_hash, md5secret, sizeof(a1_hash));
00253    else {
00254       char a1[256];
00255       snprintf(a1, sizeof(a1), "%s:%s:%s", username, global.realm, secret);
00256       ast_md5_hash(a1_hash, a1);
00257    }
00258 
00259    /* compute the expected response to compare with what we received */
00260    {
00261       char a2[256];
00262       char a2_hash[256];
00263       char resp[256];
00264 
00265       snprintf(a2, sizeof(a2), "%s:%s", sip_method2txt(sipmethod), S_OR(keys[K_URI].s, uri));
00266       ast_md5_hash(a2_hash, a2);
00267       snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, usednonce, a2_hash);
00268       ast_md5_hash(resp_hash, resp);
00269    }
00270 
00271    good_response = keys[K_RESP].s &&
00272          !strncasecmp(keys[K_RESP].s, resp_hash, strlen(resp_hash));
00273    if (wrongnonce) {
00274       ast_string_field_build(p, randdata, "%08lx", ast_random());
00275       if (good_response) {
00276          if (sipdebug)
00277             ast_log(LOG_NOTICE, "Correct auth, but based on stale nonce received from '%s'\n", get_header(req, "To"));
00278          /* We got working auth token, based on stale nonce . */
00279          transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 1);
00280       } else {
00281          /* Everything was wrong, so give the device one more try with a new challenge */
00282          if (sipdebug)
00283             ast_log(LOG_NOTICE, "Bad authentication received from '%s'\n", get_header(req, "To"));
00284          transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
00285       }
00286 
00287       /* Schedule auto destroy in 32 seconds */
00288       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
00289       return AUTH_CHALLENGE_SENT;
00290    } 
00291    if (good_response)
00292       return AUTH_SUCCESSFUL;
00293 
00294    /* Ok, we have a bad username/secret pair */
00295    /* Challenge again, and again, and again */
00296    transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
00297    sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
00298 
00299    return AUTH_CHALLENGE_SENT;
00300 }
00301 
00302 /*! \brief Authenticate for outbound registration */
00303 int do_register_auth(struct sip_dialog *p, struct sip_request *req, enum sip_auth_type code)
00304 {
00305    char *header, *respheader;
00306    char digest[1024];
00307 
00308    p->authtries++;
00309    auth_headers(code, &header, &respheader);
00310    memset(digest,0,sizeof(digest));
00311    if (reply_digest(p, req, header, SIP_REGISTER, digest, sizeof(digest))) {
00312       /* There's nothing to use for authentication */
00313       /* No digest challenge in request */
00314       if (sip_debug_test_pvt(p) && p->registry)
00315          ast_verbose("No authentication challenge, sending blank registration to domain/host name %s\n", p->registry->hostname);
00316          /* No old challenge */
00317       return -1;
00318    }
00319    if (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY))
00320       append_history(p, "RegistryAuth", "Try: %d", p->authtries);
00321    if (sip_debug_test_pvt(p) && p->registry)
00322       ast_verbose("Responding to challenge, registration to domain/host name %s\n", p->registry->hostname);
00323    return transmit_register(p->registry, SIP_REGISTER, digest, respheader); 
00324 }
00325 
00326 /*! \brief Add authentication on outbound SIP packet */
00327 int do_proxy_auth(struct sip_dialog *p, struct sip_request *req, enum sip_auth_type code, int sipmethod, int init)
00328 {
00329    char *header, *respheader;
00330    char digest[1024];
00331 
00332    if (!p->inviteoptions && !(p->inviteoptions = ast_calloc(1, sizeof(*p->inviteoptions))))
00333       return -2;
00334 
00335    p->authtries++;
00336    auth_headers(code, &header, &respheader);
00337    if (option_debug > 1)
00338       ast_log(LOG_DEBUG, "Auth attempt %d on %s\n", p->authtries, sip_method2txt(sipmethod));
00339    memset(digest, 0, sizeof(digest));
00340    if (reply_digest(p, req, header, sipmethod, digest, sizeof(digest) )) {
00341       /* No way to authenticate */
00342       return -1;
00343    }
00344    /* Now we have a reply digest */
00345    p->inviteoptions->auth = digest;
00346    p->inviteoptions->authheader = respheader;
00347    return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init); 
00348 }
00349 
00350 /*! \brief  reply to authentication for outbound registrations
00351 \return  Returns -1 if we have no auth 
00352 \note This is used for register= servers in sip.conf, SIP proxies we register
00353    with  for receiving calls from.  */
00354 int reply_digest(struct sip_dialog *p, struct sip_request *req, char *header, int sipmethod,  char *digest, int digest_len)
00355 {
00356    char tmp[512];
00357    char *c;
00358    char oldnonce[256];
00359 
00360    /* table of recognised keywords, and places where they should be copied */
00361    const struct keystruct {
00362       const char *key;
00363       int field_index;
00364    } *i, keys[] = {
00365       { "realm=", ast_string_field_index(p, realm) },
00366       { "nonce=", ast_string_field_index(p, nonce) },
00367       { "opaque=", ast_string_field_index(p, opaque) },
00368       { "qop=", ast_string_field_index(p, qop) },
00369       { "domain=", ast_string_field_index(p, domain) },
00370       { NULL, 0 },
00371    };
00372 
00373    ast_copy_string(tmp, get_header(req, header), sizeof(tmp));
00374    if (ast_strlen_zero(tmp)) 
00375       return -1;
00376    if (strncasecmp(tmp, "Digest ", strlen("Digest "))) {
00377       ast_log(LOG_WARNING, "missing Digest.\n");
00378       return -1;
00379    }
00380    c = tmp + strlen("Digest ");
00381    ast_copy_string(oldnonce, p->nonce, sizeof(oldnonce));
00382    while (c && *(c = ast_skip_blanks(c))) {  /* lookup for keys */
00383       for (i = keys; i->key != NULL; i++) {
00384          char *src, *separator;
00385          if (strncasecmp(c, i->key, strlen(i->key)) != 0)
00386             continue;
00387          /* Found. Skip keyword, take text in quotes or up to the separator. */
00388          c += strlen(i->key);
00389          if (*c == '"') {
00390             src = ++c;
00391             separator = "\"";
00392          } else {
00393             src = c;
00394             separator = ",";
00395          }
00396          strsep(&c, separator); /* clear separator and move ptr */
00397          ast_string_field_index_set(p, i->field_index, src);
00398          break;
00399       }
00400       if (i->key == NULL) /* not found, try ',' */
00401          strsep(&c, ",");
00402    }
00403    /* Reset nonce count */
00404    if (strcmp(p->nonce, oldnonce)) 
00405       p->noncecount = 0;
00406 
00407    /* Save auth data for following registrations */
00408    if (p->registry) {
00409       struct sip_registry *r = p->registry;
00410 
00411       if (strcmp(r->nonce, p->nonce)) {
00412          ast_string_field_set(r, realm, p->realm);
00413          ast_string_field_set(r, nonce, p->nonce);
00414          ast_string_field_set(r, domain, p->domain);
00415          ast_string_field_set(r, opaque, p->opaque);
00416          ast_string_field_set(r, qop, p->qop);
00417          r->noncecount = 0;
00418       }
00419    }
00420    return build_reply_digest(p, sipmethod, digest, digest_len); 
00421 }
00422 
00423 /*! \brief  Build reply digest 
00424 \return  Returns -1 if we have no auth 
00425 \note Build digest challenge for authentication of peers (for registration) 
00426    and users (for calls). Also used for authentication of CANCEL and BYE 
00427 */
00428 int build_reply_digest(struct sip_dialog *p, int method, char* digest, int digest_len)
00429 {
00430    char a1[256];
00431    char a2[256];
00432    char a1_hash[256];
00433    char a2_hash[256];
00434    char resp[256];
00435    char resp_hash[256];
00436    char uri[256];
00437    char cnonce[80];
00438    const char *username;
00439    const char *secret;
00440    const char *md5secret;
00441    struct sip_auth *auth = NULL; /* Realm authentication */
00442 
00443    if (!ast_strlen_zero(p->domain))
00444       ast_copy_string(uri, p->domain, sizeof(uri));
00445    else if (!ast_strlen_zero(p->uri))
00446       ast_copy_string(uri, p->uri, sizeof(uri));
00447    else
00448       snprintf(uri, sizeof(uri), "sip:%s@%s",p->peername, ast_inet_ntoa(p->sa.sin_addr));
00449 
00450    snprintf(cnonce, sizeof(cnonce), "%08lx", ast_random());
00451 
00452    /* Check if we have separate auth credentials */
00453    if ((auth = find_realm_authentication(authl, p->realm))) {
00454       ast_log(LOG_WARNING, "use realm [%s] from peer [%s]\n",
00455          auth->username, p->peername);
00456       username = auth->username;
00457       secret = auth->secret;
00458       md5secret = auth->md5secret;
00459       if (sipdebug)
00460          ast_log(LOG_DEBUG,"Using realm %s authentication for call %s\n", p->realm, p->callid);
00461    } else {
00462       /* No authentication, use peer or register= config */
00463       username = p->authname;
00464       secret =  p->peersecret;
00465       md5secret = p->peermd5secret;
00466    }
00467    if (ast_strlen_zero(username))   /* We have no authentication */
00468       return -1;
00469 
00470    /* Calculate SIP digest response */
00471    snprintf(a1,sizeof(a1), "%s:%s:%s", username, p->realm, secret);
00472    snprintf(a2,sizeof(a2), "%s:%s", sip_method2txt(method), uri);
00473    if (!ast_strlen_zero(md5secret))
00474       ast_copy_string(a1_hash, md5secret, sizeof(a1_hash));
00475    else
00476       ast_md5_hash(a1_hash, a1);
00477    ast_md5_hash(a2_hash,a2);
00478 
00479    p->noncecount++;
00480    if (!ast_strlen_zero(p->qop))
00481       snprintf(resp,sizeof(resp),"%s:%s:%08x:%s:%s:%s", a1_hash, p->nonce, p->noncecount, cnonce, "auth", a2_hash);
00482    else
00483       snprintf(resp,sizeof(resp),"%s:%s:%s", a1_hash, p->nonce, a2_hash);
00484    ast_md5_hash(resp_hash, resp);
00485    /* XXX We hard code our qop to "auth" for now.  XXX */
00486    if (!ast_strlen_zero(p->qop))
00487       snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\", qop=auth, cnonce=\"%s\", nc=%08x", username, p->realm, uri, p->nonce, resp_hash, p->opaque, cnonce, p->noncecount);
00488    else
00489       snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\"", username, p->realm, uri, p->nonce, resp_hash, p->opaque);
00490 
00491    return 0;
00492 }
00493 
00494 /*! \brief Add realm authentication in list */
00495 struct sip_auth *add_realm_authentication(struct sip_auth *authlist, char *configuration, int lineno)
00496 {
00497    char authcopy[256];
00498    char *username=NULL, *realm=NULL, *secret=NULL, *md5secret=NULL;
00499    char *stringp;
00500    struct sip_auth *a, *b, *auth;
00501 
00502    if (ast_strlen_zero(configuration))
00503       return authlist;
00504 
00505    if (option_debug)
00506       ast_log(LOG_DEBUG, "Auth config ::  %s\n", configuration);
00507 
00508    ast_copy_string(authcopy, configuration, sizeof(authcopy));
00509    stringp = authcopy;
00510 
00511    username = stringp;
00512    realm = strrchr(stringp, '@');
00513    if (realm)
00514       *realm++ = '\0';
00515    if (ast_strlen_zero(username) || ast_strlen_zero(realm)) {
00516       ast_log(LOG_WARNING, "Format for authentication entry is user[:secret]@realm at line %d\n", lineno);
00517       return authlist;
00518    }
00519    stringp = username;
00520    username = strsep(&stringp, ":");
00521    if (username) {
00522       secret = strsep(&stringp, ":");
00523       if (!secret) {
00524          stringp = username;
00525          md5secret = strsep(&stringp,"#");
00526       }
00527    }
00528    if (!(auth = ast_calloc(1, sizeof(*auth))))
00529       return authlist;
00530 
00531    ast_copy_string(auth->realm, realm, sizeof(auth->realm));
00532    ast_copy_string(auth->username, username, sizeof(auth->username));
00533    if (secret)
00534       ast_copy_string(auth->secret, secret, sizeof(auth->secret));
00535    if (md5secret)
00536       ast_copy_string(auth->md5secret, md5secret, sizeof(auth->md5secret));
00537 
00538    /* find the end of the list */
00539    for (b = NULL, a = authlist; a ; b = a, a = a->next)
00540       ;
00541    if (b)
00542       b->next = auth;   /* Add structure add end of list */
00543    else
00544       authlist = auth;
00545 
00546    if (option_verbose > 2)
00547       ast_verbose("Added authentication for realm %s\n", realm);
00548 
00549    return authlist;
00550 
00551 }
00552 
00553 /*! \brief Clear realm authentication list (at reload) */
00554 int clear_realm_authentication(struct sip_auth *authlist)
00555 {
00556    struct sip_auth *a = authlist;
00557    struct sip_auth *b;
00558 
00559    while (a) {
00560       b = a;
00561       a = a->next;
00562       free(b);
00563    }
00564 
00565    return 1;
00566 }
00567 
00568 /*! \brief Find authentication for a specific realm */
00569 struct sip_auth *find_realm_authentication(struct sip_auth *authlist, const char *realm)
00570 {
00571    struct sip_auth *a;
00572 
00573    for (a = authlist; a; a = a->next) {
00574       if (!strcasecmp(a->realm, realm))
00575          break;
00576    }
00577 
00578    return a;
00579 }
00580 

Asterisk is a trademark for Digium, inc.. | Edvina.net | Asterisk.org | This documentation was generated with Doxygen