Add SIP real authentication (bug #3782)
[asterisk/asterisk.git] / channels / chan_sip.c
index 236b5ba..6b4a0b0 100755 (executable)
@@ -291,11 +291,21 @@ struct sip_route {
        char hop[0];
 };
 
+/* sip_history: Structure for saving transactions within a SIP dialog */
 struct sip_history {
        char event[80];
        struct sip_history *next;
 };
 
+/* sip_auth: Creadentials for authentication to other SIP services */
+struct sip_auth {
+       char realm[AST_MAX_EXTENSION];  /* Realm in which these credentials are valid */
+       char username[256];             /* Username */
+       char secret[256];               /* Secret */
+       char md5secret[256];            /* MD5Secret */
+       struct sip_auth *next;          /* Next auth structure in list */
+};
+
 #define SIP_ALREADYGONE                (1 << 0)        /* Whether or not we've already been destroyed by our peer */
 #define SIP_NEEDDESTROY                (1 << 1)        /* if we need to be destroyed */
 #define SIP_NOVIDEO            (1 << 2)        /* Didn't get video in invite, don't offer */
@@ -410,6 +420,7 @@ static struct sip_pvt {
        char okcontacturi[256];                 /* URI from the 200 OK on INVITE */
        char peersecret[256];                   /* Password */
        char peermd5secret[256];
+       struct sip_auth *peerauth;              /* Realm authentication */
        char cid_num[256];                      /* Caller*ID */
        char cid_name[256];                     /* Caller*ID */
        char via[256];                          /* Via: header */
@@ -505,6 +516,7 @@ struct sip_peer {
                                        /* peer->name is the unique name of this object */
        char secret[80];                /* Password */
        char md5secret[80];             /* Password in MD5 */
+       struct sip_auth *auth;          /* Realm authentication list */
        char context[80];               /* Default context for incoming calls */
        char username[80];              /* Temporary username until registration */ 
        char accountcode[20];           /* Account code */
@@ -630,6 +642,9 @@ static struct ast_ha *localaddr;
 /* The list of manual NOTIFY types we know how to send */
 struct ast_config *notify_types;
 
+static struct sip_auth *authl;          /* Authentication list */
+
+
 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);
@@ -667,6 +682,9 @@ static int sip_transfer(struct ast_channel *ast, char *dest);
 static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
 static int sip_senddigit(struct ast_channel *ast, char digit);
 static int sip_sendtext(struct ast_channel *ast, char *text);
+static int clear_realm_authentication(struct sip_auth *authlist);                            /* Clear realm authentication list (at reload) */
+static struct sip_auth *add_realm_authentication(struct sip_auth *authlist, char *configuration, int lineno);   /* Add realm authentication in list */
+static struct sip_auth *find_realm_authentication(struct sip_auth *authlist, char *realm);         /* Find authentication for a specific realm */
 
 /* Definition of this channel for channel registration */
 static const struct ast_channel_tech sip_tech = {
@@ -716,6 +734,7 @@ static inline int sip_debug_test_addr(struct sockaddr_in *addr)
        return 1;
 }
 
+/*--- sip_debug_test_pvt: Test PVT for debugging output */
 static inline int sip_debug_test_pvt(struct sip_pvt *p) 
 {
        if (sipdebug == 0)
@@ -1234,6 +1253,8 @@ static void sip_destroy_peer(struct sip_peer *peer)
                rpeerobjs--;
        else
                speerobjs--;
+       clear_realm_authentication(peer->auth);
+       peer->auth = (struct sip_auth *) NULL;
        free(peer);
 }
 
@@ -6513,6 +6534,7 @@ static int _sip_show_peer(int type, int fd, struct mansession *s, struct message
        char codec_buf[512];
        struct ast_codec_pref *pref;
        struct ast_variable *v;
+       struct sip_auth *auth;
        int x = 0, codec = 0, load_realtime = 0;
 
        if (argc < 4)
@@ -6536,6 +6558,12 @@ static int _sip_show_peer(int type, int fd, struct mansession *s, struct message
                ast_cli(fd, "  * Name       : %s\n", peer->name);
                ast_cli(fd, "  Secret       : %s\n", ast_strlen_zero(peer->secret)?"<Not set>":"<Set>");
                ast_cli(fd, "  MD5Secret    : %s\n", ast_strlen_zero(peer->md5secret)?"<Not set>":"<Set>");
+               auth = peer->auth;
+               while(auth) {
+                       ast_cli(fd, "  Realm-auth   : Realm %-15.15s User %-10.20s ", auth->realm, auth->username);
+                       ast_cli(fd, "%s\n", !ast_strlen_zero(auth->secret)?"<Secret set>":(!ast_strlen_zero(auth->md5secret)?"<MD5secret set>" : "<Not set>"));
+                       auth = auth->next;
+               }
                ast_cli(fd, "  Context      : %s\n", peer->context);
                ast_cli(fd, "  Language     : %s\n", peer->language);
                if (!ast_strlen_zero(peer->accountcode))
@@ -7458,6 +7486,10 @@ static int build_reply_digest(struct sip_pvt *p, int method, char* digest, int d
        char uri[256] = "";
        char cnonce[80];
        char iabuf[INET_ADDRSTRLEN];
+       char *username;
+       char *secret;
+       char *md5secret;
+       struct sip_auth *auth = (struct sip_auth *) NULL;       /* Realm authentication */
 
        if (!ast_strlen_zero(p->domain))
                strncpy(uri, p->domain, sizeof(uri) - 1);
@@ -7468,10 +7500,25 @@ static int build_reply_digest(struct sip_pvt *p, int method, char* digest, int d
 
        snprintf(cnonce, sizeof(cnonce), "%08x", rand());
 
-       snprintf(a1,sizeof(a1),"%s:%s:%s",p->authname,p->realm,p->peersecret);
+       /* Check if we have separate auth credentials */
+       if ((auth = find_realm_authentication(authl, p->realm))) {
+               username = auth->username;
+               secret = auth->secret;
+               md5secret = auth->md5secret;
+               ast_log(LOG_NOTICE,"Using realm %s authentication for this call\n", p->realm);
+       } else {
+               /* No authentication, use peer or register= config */
+               username = p->authname;
+               secret =  p->peersecret;
+               md5secret = p->peermd5secret;
+       }
+
+       /* Calculate SIP digest response */
+       snprintf(a1,sizeof(a1),"%s:%s:%s",username,p->realm,secret);
        snprintf(a2,sizeof(a2),"%s:%s", sip_methods[method].text, uri);
-       if (!ast_strlen_zero(p->peermd5secret))
-               strncpy(a1_hash, p->peermd5secret, sizeof(a1_hash) - 1);
+       if (!ast_strlen_zero(md5secret))
+               strncpy(a1_hash, md5secret, sizeof(a1_hash) - 1);
        else
                ast_md5_hash(a1_hash,a1);
        ast_md5_hash(a2_hash,a2);
@@ -7484,9 +7531,9 @@ static int build_reply_digest(struct sip_pvt *p, int method, char* digest, int d
        ast_md5_hash(resp_hash,resp);
        /* XXX We hard code our qop to "auth" for now.  XXX */
        if (!ast_strlen_zero(p->qop))
-               snprintf(digest,digest_len,"Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\", qop=\"%s\", cnonce=\"%s\", nc=%s",p->authname,p->realm,uri,p->nonce,resp_hash, p->opaque, "auth", cnonce, "00000001");
+               snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\", qop=\"%s\", cnonce=\"%s\", nc=%s", username, p->realm, uri, p->nonce, resp_hash, p->opaque, "auth", cnonce, "00000001");
        else
-               snprintf(digest,digest_len,"Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\"",p->authname,p->realm,uri,p->nonce,resp_hash, p->opaque);
+               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);
 
        return 0;
 }
@@ -9525,6 +9572,105 @@ static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask
        return res;
 }
 
+/*--- add_realm_authentication: Add realm authentication in list ---*/
+static struct sip_auth *add_realm_authentication(struct sip_auth *authlist, char *configuration, int lineno)
+{
+        char authcopy[256] = "";
+        char *username=NULL, *realm=NULL, *secret=NULL, *md5secret=NULL;
+       char *stringp;
+       struct sip_auth *auth;
+       struct sip_auth *b = NULL, *a = authlist;
+                 
+        if (!configuration || ast_strlen_zero(configuration))
+                return (authlist);
+
+       ast_log(LOG_DEBUG, "Auth config ::  %s\n", configuration);
+
+        strncpy(authcopy, configuration, sizeof(authcopy)-1);
+        stringp = authcopy;
+
+        username = stringp;
+        realm = strrchr(stringp, '@');
+        if (realm) {
+                *realm = '\0';
+                realm++;
+        }
+        if (!username || ast_strlen_zero(username) || !realm || ast_strlen_zero(realm)) {
+                ast_log(LOG_WARNING, "Format for authentication entry is user[:secret]@realm at line %d", lineno);
+                return (authlist);
+        }
+        stringp = username;
+        username = strsep(&stringp, ":");
+        if (username) {
+                secret = strsep(&stringp, ":");
+               if (!secret) {
+                       stringp = username;
+                       md5secret = strsep(&stringp,"#");
+               }
+        }
+       auth = malloc(sizeof(struct sip_auth));
+        if (auth) {
+                memset(auth, 0, sizeof(struct sip_auth));
+               strncpy(auth->realm, realm, sizeof(auth->realm)-1);
+               strncpy(auth->username, username, sizeof(auth->username)-1);
+               if (secret)
+                       strncpy(auth->secret, secret, sizeof(auth->secret)-1);
+               if (md5secret)
+                       strncpy(auth->md5secret, md5secret, sizeof(auth->md5secret)-1);
+        } else {
+                ast_log(LOG_ERROR, "Allocation of auth structure failed, Out of memory\n");
+                return (authlist);
+        }
+
+       /* Add authentication to authl */
+       if (!authlist) {        /* No existing list */
+               return(auth);
+       } else {
+               while(a) {
+                       b = a;
+                       a = a->next;
+               }
+               b->next = auth; /* Add structure add end of list */
+       }
+
+       if (option_verbose > 2)
+               ast_verbose("Added authentication for realm %s\n", realm);
+
+        return(authlist);
+
+}
+
+/*--- clear_realm_authentication: Clear realm authentication list (at reload) ---*/
+static int clear_realm_authentication(struct sip_auth *authlist)
+{
+       struct sip_auth *a = authlist;
+       struct sip_auth *b;
+
+        while(a) {
+                b = a;
+                a = a->next;
+                free(b);
+        }
+
+
+       return(1);
+}
+
+/*--- find_realm_authentication: Find authentication for a specific realm ---*/
+static struct sip_auth *find_realm_authentication(struct sip_auth *authlist, char *realm)
+{
+       struct sip_auth *a = authlist;  /* First entry in auth list */
+
+       while (a) {
+               if (!strcasecmp(a->realm, realm)){
+                       break;
+               }
+               a = a->next;
+       }
+       
+       return(a);
+}
+
 /*--- build_user: Initiate a SIP user structure from sip.conf ---*/
 static struct sip_user *build_user(const char *name, struct ast_variable *v, int realtime)
 {
@@ -9754,6 +9900,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, int
                                strncpy(peer->secret, v->value, sizeof(peer->secret)-1);
                        else if (!strcasecmp(v->name, "md5secret")) 
                                strncpy(peer->md5secret, v->value, sizeof(peer->md5secret)-1);
+                       else if (!strcasecmp(v->name, "auth"))
+                               peer->auth = add_realm_authentication(peer->auth, v->value, v->lineno);
                        else if (!strcasecmp(v->name, "callerid")) {
                                ast_callerid_split(v->value, peer->cid_name, sizeof(peer->cid_name), peer->cid_num, sizeof(peer->cid_num));
                        } else if (!strcasecmp(v->name, "context"))
@@ -10166,10 +10314,21 @@ static int reload_config(void)
                 v = v->next;
        }
        
+       /* Build list of authentication to various SIP realms, i.e. service providers */
+       v = ast_variable_browse(cfg, "authentication");
+       while(v) {
+               /* Format for authentication is auth = username:password@realm */
+               if (!strcasecmp(v->name, "auth")) {
+                       authl = add_realm_authentication(authl, v->value, v->lineno);
+               }
+               v = v->next;
+       }
+       
+       
        /* Load peers, users and friends */
        cat = ast_category_browse(cfg, NULL);
        while(cat) {
-               if (strcasecmp(cat, "general")) {
+               if (strcasecmp(cat, "general") && strcasecmp(cat, "authentication")) {
                        utype = ast_variable_retrieve(cfg, cat, "type");
                        if (utype) {
                                if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) {
@@ -10611,6 +10770,9 @@ static void sip_send_all_registers(void)
 /*--- sip_do_reload: Reload module */
 static int sip_do_reload(void)
 {
+       clear_realm_authentication(authl);
+       authl = (struct sip_auth *) NULL;
+
        delete_users();
        reload_config();
        prune_peers();
@@ -10735,6 +10897,8 @@ int unload_module()
        ast_manager_unregister("SIPpeers");
        ast_manager_unregister("SIPshowpeer");
        ast_channel_unregister(&sip_tech);
+
+
        if (!ast_mutex_lock(&iflock)) {
                /* Hangup all interfaces if they have an owner */
                p = iflist;
@@ -10787,6 +10951,8 @@ int unload_module()
        ASTOBJ_CONTAINER_DESTROY(&userl);
        ASTOBJ_CONTAINER_DESTROY(&peerl);
        ASTOBJ_CONTAINER_DESTROY(&regl);
+
+       clear_realm_authentication(authl);
                
        return 0;
 }