fixup
[lcr.git] / sip.cpp
diff --git a/sip.cpp b/sip.cpp
index b5d9635..c3a4f00 100644 (file)
--- a/sip.cpp
+++ b/sip.cpp
@@ -16,6 +16,7 @@
 #include <sofia-sip/sip_header.h>
 #include <sofia-sip/stun.h>
 #include <sofia-sip/stun_tag.h>
+#include <sofia-sip/su_md5.h>
 
 #ifndef SOFIA_SIP_GCC_4_8_PATCH_APLLIED
 #warning ********************************************************
@@ -66,6 +67,8 @@ struct sip_inst {
        int                     options_interval;
        char                    auth_user[128];
        char                    auth_password[128];
+       char                    auth_realm[128];
+       char                    auth_nonce[128];
        su_root_t               *root;
        nua_t                   *nua;
 
@@ -880,6 +883,158 @@ unsigned int Psip::get_local_ip(unsigned int ip)
        return ip;
 }
 
+/* some simple nonce generator */
+static void generate_nonce(char *result)
+{
+       UPRINT(result, "%08x", (unsigned int)random());
+       result += 8;
+       UPRINT(result, "%08x", (unsigned int)random());
+       result += 8;
+       UPRINT(result, "%08x", (unsigned int)random());
+       result += 8;
+       UPRINT(result, "%08x", (unsigned int)random());
+}
+
+/* check authorization */
+static int check_authorization(sip_authorization_t const *authorization, const char *regstr, const char *check_user, const char *check_pass, const char *check_realm, const char *check_nonce, const char **auth_text)
+{
+       int ret = 500;
+       *auth_text = "Internal Server Error";
+
+       char *username = NULL;
+       char *realm = NULL;
+       char *nonce = NULL;
+       char *uri = NULL;
+       char *qop = NULL;
+       char *cnonce = NULL;
+       char *nc = NULL;
+       char *response = NULL;
+
+       int indexnum;
+        const char *cur;
+
+       char temp[256], first_digest[2 * SU_MD5_DIGEST_SIZE + 1], second_digest[2 * SU_MD5_DIGEST_SIZE + 1], third_digest[2 * SU_MD5_DIGEST_SIZE + 1];
+        su_md5_t md5_ctx;
+
+       if (!check_nonce || !check_nonce[0] || !authorization || !authorization->au_params) {
+               if (!strcmp(regstr, "REGISTER")) {
+                       *auth_text = "Unauthorized";
+                       ret = 401;
+               } else {
+                       *auth_text = "Proxy Authentication Required";
+                       ret = 407;
+               }
+               goto end;
+       }
+
+       /* parse header (stolen from freeswitch) */
+       for (indexnum = 0; (cur = authorization->au_params[indexnum]); indexnum++) {
+               char *var, *val, *p, *work;
+               var = val = work = NULL;
+               if ((work = strdup(cur))) {
+                       var = work;
+                       if ((val = strchr(var, '='))) {
+                               *val++ = '\0';
+                               while (*val == '"') {
+                                       *val++ = '\0';
+                               }
+                               if ((p = strchr(val, '"'))) {
+                                       *p = '\0';
+                               }
+               
+                               PDEBUG(DEBUG_SIP, "Found in Auth header: %s = %s\n", var, val);
+                               if (!strcasecmp(var, "username")) {
+                                       username = strdup(val);
+                               } else if (!strcasecmp(var, "realm")) {
+                                       realm = strdup(val);
+                               } else if (!strcasecmp(var, "nonce")) {
+                                       nonce = strdup(val);
+                               } else if (!strcasecmp(var, "uri")) {
+                                       uri = strdup(val);
+                               } else if (!strcasecmp(var, "qop")) {
+                                       qop = strdup(val);
+                               } else if (!strcasecmp(var, "cnonce")) {
+                                       cnonce = strdup(val);
+                               } else if (!strcasecmp(var, "response")) {
+                                       response = strdup(val);
+                               } else if (!strcasecmp(var, "nc")) {
+                                       nc = strdup(val);
+                               }
+                       }
+
+                       free(work);
+               }
+       }
+
+       if (!username || !realm || !nonce || ! uri || !response) {
+               *auth_text = "Authorization header incomplete";
+               ret = 400;
+               goto end;
+       }
+
+       if (!!strcmp(username, check_user)) {
+               *auth_text = "Authorization Username Missmatch";
+               ret = 403;
+               goto end;
+       }
+       if (!!strcmp(realm, check_realm)) {
+               *auth_text = "Authorization Realm Missmatch";
+               ret = 403;
+               goto end;
+       }
+       if (!!strcmp(nonce, check_nonce)) {
+               *auth_text = "Authorization Nonce Missmatch";
+               ret = 403;
+               goto end;
+       }
+
+       /* perform hash */
+       SPRINT(temp, "%s:%s:%s", check_user, realm, check_pass);
+       PDEBUG(DEBUG_SIP, "First hash: %s\n", temp);
+       su_md5_init(&md5_ctx);
+       su_md5_strupdate(&md5_ctx, temp);
+       su_md5_hexdigest(&md5_ctx, first_digest);
+       su_md5_deinit(&md5_ctx);
+
+       SPRINT(temp, "%s:%s", regstr, uri);
+       PDEBUG(DEBUG_SIP, "First hash: %s\n", temp);
+       su_md5_init(&md5_ctx);
+       su_md5_strupdate(&md5_ctx, temp);
+       su_md5_hexdigest(&md5_ctx, second_digest);
+       su_md5_deinit(&md5_ctx);
+
+       if (nc && cnonce && qop)
+               SPRINT(temp, "%s:%s:%s:%s:%s:%s", first_digest, nonce, nc, cnonce, qop, second_digest);
+       else
+               SPRINT(temp, "%s:%s:%s", first_digest, nonce, second_digest);
+       PDEBUG(DEBUG_SIP, "Third hash: %s\n", temp);
+       su_md5_init(&md5_ctx);
+       su_md5_strupdate(&md5_ctx, temp);
+       su_md5_hexdigest(&md5_ctx, third_digest);
+       su_md5_deinit(&md5_ctx);
+
+       if (!!strcmp(response, third_digest)) {
+               *auth_text = "Authorization Failed";
+               ret = 403;
+               goto end;
+       }
+
+       *auth_text = "Authorization Success";
+       ret = 200;
+
+end:
+       free(username);
+       free(realm);
+       free(nonce);
+       free(uri);
+       free(qop);
+       free(cnonce);
+       free(nc);
+       free(response);
+
+       return ret;
+}
+
 /*
  * endpoint sends messages to the SIP port
  */
@@ -1549,6 +1704,17 @@ static void i_register(struct sip_inst *inst, int status, char const *phrase, nu
        contact = sip->sip_contact;
        nua_save_event(nua, saved);
        nua_event_data_t const *data = nua_event_data(saved);
+       sip_authorization_t const *authorization;
+       char uri[256] = "";
+       const char *auth_text = NULL;
+       char auth_str[256] = "";
+
+       if (contact->m_url->url_host)
+               SCPY(uri, contact->m_url->url_host);
+       if (contact->m_url->url_port && contact->m_url->url_port[0]) {
+               SCAT(uri, ":");
+               SCAT(uri, contact->m_url->url_port);
+       }
 
        if (!inst->allow_register) {
                sip_trace_header(NULL, inst->interface_name, "REGISTER", DIRECTION_IN);
@@ -1560,23 +1726,36 @@ static void i_register(struct sip_inst *inst, int status, char const *phrase, nu
                return;
        }
 
-       if (contact->m_url->url_host)
-               SCPY(inst->remote_peer, contact->m_url->url_host);
-       if (contact->m_url->url_port && contact->m_url->url_port[0]) {
-               SCAT(inst->remote_peer, ":");
-               SCAT(inst->remote_peer, contact->m_url->url_port);
-       }
-
        sip_trace_header(NULL, inst->interface_name, "REGISTER", DIRECTION_IN);
-       add_trace("contact", "uri", "%s", inst->remote_peer);
+       add_trace("contact", "uri", "%s", uri);
        end_trace();
 
+       sip_trace_header(NULL, inst->interface_name, "Authorization", DIRECTION_IN);
+       if (inst->auth_realm[0]) {
+               authorization = sip->sip_authorization;
+               status = check_authorization(authorization, "REGISTER", inst->auth_user, inst->auth_password, inst->auth_realm, inst->auth_nonce, &auth_text);
+               if (status == 401) {
+                       if (!inst->auth_nonce[0])
+                               generate_nonce(inst->auth_nonce);
+                       SPRINT(auth_str, "Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\"", inst->auth_realm, inst->auth_nonce);
+               }
+       } else {
+               status = 200;
+               auth_text = "Authentication not required";
+       }
+       add_trace("result", NULL, "%s", auth_text);
+       end_trace();
+
+       if (status == 200) {
+               SCPY(inst->remote_peer, uri);
+       }
+
        sip_trace_header(NULL, inst->interface_name, "RESPOND", DIRECTION_OUT);
-       add_trace("respond", "value", "200 OK");
-       add_trace("reason", NULL, "peer registered");
+       add_trace("respond", "value", "%d", status);
+       add_trace("reason", NULL, "peer registers");
        end_trace();
 
-       nua_respond(nh, SIP_200_OK, SIPTAG_CONTACT(sip->sip_contact), NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END());
+       nua_respond(nh, status, auth_text, SIPTAG_CONTACT(sip->sip_contact), NUTAG_WITH_THIS_MSG(data->e_msg), TAG_IF(auth_str[0], SIPTAG_WWW_AUTHENTICATE_STR(auth_str)), TAG_END());
        nua_handle_destroy(nh);
        inst->register_handle = NULL;
 }
@@ -1643,6 +1822,9 @@ void Psip::i_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag
        int payloads = 0;
        unsigned char payload_type;
        int media_type;
+       sip_authorization_t const *authorization;
+       const char *auth_text = NULL;
+       char auth_str[256] = "";
 
        interface = getinterfacebyname(inst->interface_name);
        if (!interface) {
@@ -1668,6 +1850,39 @@ void Psip::i_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag
        }
        PDEBUG(DEBUG_SIP, "invite received (%s->%s)\n", from, to);
 
+       sip_trace_header(this, inst->interface_name, "Authorization", DIRECTION_IN);
+       if (inst->auth_realm[0] || p_state != PORT_STATE_IDLE) {
+               /* only authenticate remote, if we have a realm set and we don't have re-invite */
+               authorization = sip->sip_proxy_authorization;
+               status = check_authorization(authorization, "INVITE", inst->auth_user, inst->auth_password, inst->auth_realm, inst->auth_nonce, &auth_text);
+               if (status == 407) {
+                       if (!inst->auth_nonce[0])
+                               generate_nonce(inst->auth_nonce);
+                       SPRINT(auth_str, "Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\"", inst->auth_realm, inst->auth_nonce);
+               }
+       } else {
+               status = 200;
+               auth_text = "Authentication not required";
+       }
+       add_trace("result", NULL, "%s", auth_text);
+       end_trace();
+
+       if (status == 200) {
+       } else {
+               sip_trace_header(this, inst->interface_name, "INVITE", DIRECTION_IN);
+               end_trace();
+
+               sip_trace_header(this, inst->interface_name, "RESPOND", DIRECTION_OUT);
+               add_trace("respond", "value", "%d", status);
+               add_trace("reason", NULL, "peer invited");
+               end_trace();
+
+               nua_respond(nh, status, auth_text, SIPTAG_CONTACT(sip->sip_contact), TAG_IF(auth_str[0], SIPTAG_PROXY_AUTHENTICATE_STR(auth_str)), TAG_END());
+               new_state(PORT_STATE_RELEASE);
+               trigger_work(&p_s_delete);
+               return;
+       }
+
        sip_trace_header(this, inst->interface_name, "Payload received", DIRECTION_NONE);
        ret = parse_sdp(sip, &p_s_rtp_ip_remote, &p_s_rtp_port_remote, payload_types, media_types, &payloads, sizeof(payload_types));
        if (!ret) {
@@ -2015,6 +2230,12 @@ void Psip::r_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag
        add_trace("respond", "value", "%d", status);
        end_trace();
 
+       if (status == 401 || status == 407) {
+               PDEBUG(DEBUG_SIP, "Invite challenge received\n");
+               challenge(inst, this, status, phrase, nua, magic, nh, hmagic, sip, tags);
+               return;
+       }
+
        /* connect audio */
        if (status == 183 || (status >= 200 && status <= 299)) {
                int ret;
@@ -2189,7 +2410,6 @@ static void sip_callback(nua_event_t event, int status, char const *phrase, nua_
        struct sip_inst *inst = (struct sip_inst *) magic;
        class Port *port;
        class Psip *psip = NULL;
-       int rc;
 
        PDEBUG(DEBUG_SIP, "Event %d from SIP stack received (handle=%p)\n", event, nh);
        if (!nh)
@@ -2278,14 +2498,6 @@ static void sip_callback(nua_event_t event, int status, char const *phrase, nua_
                return;
        }
 
-       switch (status) {
-       case 401:
-       case 407:
-               rc = challenge(inst, psip, status, phrase, nua, magic, nh, hmagic, sip, tags);
-               if (rc >= 0)
-                       return;
-       }
-
        switch (event) {
        case nua_r_set_params:
                PDEBUG(DEBUG_SIP, "setparam response\n");
@@ -2469,6 +2681,7 @@ int sip_init_inst(struct interface *interface)
        }
        SCPY(inst->auth_user, interface->sip_auth_user);
        SCPY(inst->auth_password, interface->sip_auth_password);
+       SCPY(inst->auth_realm, interface->sip_auth_realm);
        inst->register_interval = interface->sip_register_interval;
        inst->options_interval = interface->sip_options_interval;