Begin separating YADIS handling from XRDS parsing
[citadel.git] / citadel / modules / openid / serv_openid_rp.c
index 69aa50b6e57e318ecb8b981fd06fbbca49f781f6..aa1ba633e41d19b3bb04bd1d1319e73b7277f8b6 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * This is an implementation of OpenID 1.1 Relying Party support, in stateless mode.
+ * This is an implementation of OpenID 2.0 RELYING PARTY SUPPORT CURRENTLY B0RKEN AND BEING DEVEL0PZ0RED
  *
- * Copyright (c) 2007-2010 by the citadel.org team
+ * Copyright (c) 2007-2011 by the citadel.org team
  *
- * This program is free software; you can redistribute it and/or modify
+ * This program is open source software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
@@ -14,8 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "sysdep.h"
@@ -43,6 +42,7 @@
 #include <string.h>
 #include <limits.h>
 #include <curl/curl.h>
+#include <expat.h>
 #include "ctdl_module.h"
 #include "config.h"
 #include "citserver.h"
@@ -74,7 +74,7 @@ void openid_cleanup_function(void) {
        struct CitContext *CCC = CC;    /* CachedCitContext - performance boost */
 
        if (CCC->openid_data != NULL) {
-               syslog(LOG_DEBUG, "Clearing OpenID session state\n");
+               syslog(LOG_DEBUG, "Clearing OpenID session state");
                Free_ctdl_openid((ctdl_openid **) &CCC->openid_data);
        }
 }
@@ -125,11 +125,11 @@ int attach_openid(struct ctdluser *who, StrBuf *claimed_id)
                cdb_free(cdboi);
 
                if (fetched_usernum == who->usernum) {
-                       syslog(LOG_INFO, "%s already associated; no action is taken\n", ChrPtr(claimed_id));
+                       syslog(LOG_INFO, "%s already associated; no action is taken", ChrPtr(claimed_id));
                        return(0);
                }
                else {
-                       syslog(LOG_INFO, "%s already belongs to another user\n", ChrPtr(claimed_id));
+                       syslog(LOG_INFO, "%s already belongs to another user", ChrPtr(claimed_id));
                        return(3);
                }
        }
@@ -170,7 +170,6 @@ void openid_purge(struct ctdluser *usbuf) {
        keys = NewHash(1, NULL);
        if (!keys) return;
 
-
        cdb_rewind(CDB_OPENID);
        while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
                if (cdboi->len > sizeof(long)) {
@@ -188,7 +187,7 @@ void openid_purge(struct ctdluser *usbuf) {
        HashPos = GetNewHashPos(keys, 0);
        while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
        {
-               syslog(LOG_DEBUG, "Deleting associated OpenID <%s>\n", (char*)Value);
+               syslog(LOG_DEBUG, "Deleting associated OpenID <%s>", (char*)Value);
                cdb_delete(CDB_OPENID, Value, strlen(Value));
                /* note: don't free(Value) -- deleting the hash list will handle this for us */
        }
@@ -197,7 +196,6 @@ void openid_purge(struct ctdluser *usbuf) {
 }
 
 
-
 /*
  * List the OpenIDs associated with the currently logged in account
  */
@@ -367,8 +365,6 @@ void cmd_oidc(char *argbuf) {
 }
 
 
-
-
 /*
  * Detach an OpenID from the currently logged in account
  */
@@ -421,11 +417,11 @@ int openid_create_user_via_sreg(StrBuf *claimed_id, HashList *sreg_keys)
        if (CC->logged_in) return(3);
        if (!GetHash(sreg_keys, "sreg.nickname", 13, (void *) &desired_name)) return(4);
 
-       syslog(LOG_DEBUG, "The desired account name is <%s>\n", desired_name);
+       syslog(LOG_DEBUG, "The desired account name is <%s>", desired_name);
 
        len = cutuserkey(desired_name);
        if (!CtdlGetUser(&CC->user, desired_name)) {
-               syslog(LOG_DEBUG, "<%s> is already taken by another user.\n", desired_name);
+               syslog(LOG_DEBUG, "<%s> is already taken by another user.", desired_name);
                memset(&CC->user, 0, sizeof(struct ctdluser));
                return(5);
        }
@@ -485,7 +481,7 @@ int login_via_openid(StrBuf *claimed_id)
  */
 void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf)
 {
-       int len, i;
+       int i;
        const char *ptr;
        const char *href_start = NULL;
        const char *href_end = NULL;
@@ -508,8 +504,6 @@ void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *sou
                if (link_tag_end == NULL)
                        break;
                for (i=0; i < 1; i++ ){
-                       len = link_tag_end - link_tag_start;
-
                        rel_start = cbmstrcasestr(link_tag_start, "rel=");
                        if ((rel_start == NULL) ||
                            (rel_start > link_tag_end)) 
@@ -551,9 +545,6 @@ void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *sou
 
 /*
  * Begin an HTTP fetch (returns number of bytes actually fetched, or -1 for error) using libcurl.
- *
- * If 'normalize_len' is nonzero, the caller is specifying the buffer size of 'url', and is
- * requesting that the effective (normalized) URL be copied back to it.
  */
 int fetch_http(StrBuf *url, StrBuf **target_buf)
 {
@@ -569,7 +560,7 @@ int fetch_http(StrBuf *url, StrBuf **target_buf)
 
        curl = curl_easy_init();
        if (!curl) {
-               syslog(LOG_ALERT, "Unable to initialize libcurl.\n");
+               syslog(LOG_ALERT, "Unable to initialize libcurl.");
                return(-1);
        }
 
@@ -598,7 +589,7 @@ int fetch_http(StrBuf *url, StrBuf **target_buf)
        }
        res = curl_easy_perform(curl);
        if (res) {
-               syslog(LOG_DEBUG, "fetch_http() libcurl error %d: %s\n", res, errmsg);
+               syslog(LOG_DEBUG, "libcurl error %d: %s", res, errmsg);
        }
        curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
        StrBufPlain(url, effective_url, -1);
@@ -608,6 +599,97 @@ int fetch_http(StrBuf *url, StrBuf **target_buf)
 }
 
 
+
+struct xrds {
+       int nesting_level;
+       int in_xrd;
+};
+
+
+void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
+       struct xrds *xrds = (struct xrds *) data;
+
+       ++xrds->nesting_level;
+
+       if (!strcasecmp(supplied_el, "XRD")) {
+               ++xrds->in_xrd;
+               syslog(LOG_DEBUG, "*** XRD DOCUMENT BEGIN ***");
+       }
+}
+
+
+void xrds_xml_end(void *data, const char *supplied_el) {
+       struct xrds *xrds = (struct xrds *) data;
+
+       --xrds->nesting_level;
+
+       if (!strcasecmp(supplied_el, "XRD")) {
+               --xrds->in_xrd;
+               syslog(LOG_DEBUG, "*** XRD DOCUMENT END ***");
+       }
+}
+
+
+void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
+       struct xrds *xrds = (struct xrds *) data;
+       
+       syslog(LOG_DEBUG, "%2d xrds_xml_chardata()", xrds->nesting_level);
+       /* StrBufAppendBufPlain (xrds->CData, s, len, 0); */
+}
+
+
+/*
+ * Parse an XRDS document.
+ * If OpenID stuff is discovered, populate FIXME something and return nonzero
+ * If nothing useful happened, return 0.
+ */
+int parse_xrds_document(StrBuf *ReplyBuf) {
+       struct xrds xrds;
+
+       memset(&xrds, 0, sizeof (struct xrds));
+       XML_Parser xp = XML_ParserCreate(NULL);
+       if (xp) {
+               XML_SetUserData(xp, &xrds);
+               XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
+               XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
+               XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
+               XML_Parse(xp, "", 0, 1);
+               XML_ParserFree(xp);
+       }
+       else {
+               syslog(LOG_ALERT, "Cannot create XML parser");
+       }
+
+       return(0);
+}
+
+
+/*
+ * Attempt to perform YADIS discovery.
+ * If successful, returns nonzero and fills the session's claimed ID blah FIXME this comment
+ * If YADIS fails, returns 0 and does nothing else.
+ */
+int perform_yadis_discovery(StrBuf *YadisURL) {
+       int docbytes = (-1);
+       StrBuf *ReplyBuf = NULL;
+       int r;
+
+
+       docbytes = fetch_http(YadisURL, &ReplyBuf);
+       if (docbytes < 0) {
+               return(0);
+       }
+       if (docbytes == 0) {
+               FreeStrBuf(&ReplyBuf);
+               return(0);
+       }
+
+       r = parse_xrds_document(ReplyBuf);
+       FreeStrBuf(&ReplyBuf);
+       return(r);
+}
+
+
 /*
  * Setup an OpenID authentication
  */
@@ -619,7 +701,6 @@ void cmd_oids(char *argbuf) {
        StrBuf *trust_root = NULL;
        StrBuf *openid_delegate = NULL;
        StrBuf *RedirectUrl = NULL;
-       int i;
        struct CitContext *CCC = CC;    /* CachedCitContext - performance boost */
        ctdl_openid *oiddata;
 
@@ -635,6 +716,7 @@ void cmd_oids(char *argbuf) {
 
        ArgBuf = NewStrBufPlain(argbuf, -1);
 
+       oiddata->verified = 0;
        oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
        trust_root = NewStrBufPlain(NULL, StrLength(ArgBuf));
        return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
@@ -642,14 +724,27 @@ void cmd_oids(char *argbuf) {
        StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
        StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
        StrBufExtract_NextToken(trust_root, ArgBuf, &Pos, '|');
-       
-       oiddata->verified = 0;
 
-       i = fetch_http(oiddata->claimed_id, &ReplyBuf);
-       syslog(LOG_DEBUG, "Normalized URL and Claimed ID is: %s\n", 
-                     ChrPtr(oiddata->claimed_id));
-       if ((StrLength(ReplyBuf) > 0) && (i > 0)) {
+       syslog(LOG_DEBUG, "User-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
+
 
+       /********** OpenID 2.0 section 7.3 - Discovery **********/
+
+       /* First we're supposed to attempt XRI based resolution.
+        * No one is using this, no one is asking for it, no one wants it.
+        * So we're not even going to bother attempting this mode.
+        */
+
+       /* Second we attempt YADIS.
+        * Google uses this so we'd better do our best to implement it.
+        */
+       int yadis_succeeded = perform_yadis_discovery(oiddata->claimed_id);
+
+       /* Third we attempt HTML-based discovery.  Here we go! */
+       if (    (yadis_succeeded == 0)
+               && (fetch_http(oiddata->claimed_id, &ReplyBuf) > 0)
+               && (StrLength(ReplyBuf) > 0)
+       ) {
                openid_delegate = NewStrBuf();
                oiddata->server = NewStrBuf();
                extract_link(oiddata->server, HKEY("openid.server"), ReplyBuf);
@@ -699,6 +794,7 @@ void cmd_oids(char *argbuf) {
 
                return;
        }
+
        FreeStrBuf(&ArgBuf);
        FreeStrBuf(&ReplyBuf);
        FreeStrBuf(&return_to);
@@ -744,13 +840,13 @@ void cmd_oidf(char *argbuf) {
                if (len < 0)
                        len = sizeof(thiskey) - 1;
                extract_token(thisdata, buf, 1, '|', sizeof thisdata);
-               syslog(LOG_DEBUG, "%s: [%d] %s\n", thiskey, strlen(thisdata), thisdata);
+               syslog(LOG_DEBUG, "%s: ["SIZE_T_FMT"] %s", thiskey, strlen(thisdata), thisdata);
                Put(keys, thiskey, len, strdup(thisdata), NULL);
        }
 
 
        /* Now that we have all of the parameters, we have to validate the signature against the server */
-       syslog(LOG_DEBUG, "About to validate the signature...\n");
+       syslog(LOG_DEBUG, "About to validate the signature...");
 
        CURL *curl;
        CURLcode res;
@@ -771,14 +867,14 @@ void cmd_oidf(char *argbuf) {
                CURLFORM_COPYNAME,      "openid.mode",
                CURLFORM_COPYCONTENTS,  "check_authentication",
                CURLFORM_END);
-       syslog(LOG_DEBUG, "%25s : %s\n", "openid.mode", "check_authentication");
+       syslog(LOG_DEBUG, "%25s : %s", "openid.mode", "check_authentication");
 
        if (GetHash(keys, "assoc_handle", 12, (void *) &o_assoc_handle)) {
                curl_formadd(&formpost, &lastptr,
                        CURLFORM_COPYNAME,      "openid.assoc_handle",
                        CURLFORM_COPYCONTENTS,  o_assoc_handle,
                        CURLFORM_END);
-               syslog(LOG_DEBUG, "%25s : %s\n", "openid.assoc_handle", o_assoc_handle);
+               syslog(LOG_DEBUG, "%25s : %s", "openid.assoc_handle", o_assoc_handle);
        }
 
        if (GetHash(keys, "sig", 3, (void *) &o_sig)) {
@@ -786,7 +882,7 @@ void cmd_oidf(char *argbuf) {
                        CURLFORM_COPYNAME,      "openid.sig",
                        CURLFORM_COPYCONTENTS,  o_sig,
                        CURLFORM_END);
-                       syslog(LOG_DEBUG, "%25s : %s\n", "openid.sig", o_sig);
+                       syslog(LOG_DEBUG, "%25s : %s", "openid.sig", o_sig);
        }
 
        if (GetHash(keys, "signed", 6, (void *) &o_signed)) {
@@ -794,7 +890,7 @@ void cmd_oidf(char *argbuf) {
                        CURLFORM_COPYNAME,      "openid.signed",
                        CURLFORM_COPYCONTENTS,  o_signed,
                        CURLFORM_END);
-               syslog(LOG_DEBUG, "%25s : %s\n", "openid.signed", o_signed);
+               syslog(LOG_DEBUG, "%25s : %s", "openid.signed", o_signed);
 
                num_signed_values = num_tokens(o_signed, ',');
                for (i=0; i<num_signed_values; ++i) {
@@ -806,10 +902,10 @@ void cmd_oidf(char *argbuf) {
                                                CURLFORM_COPYNAME,      k_o_keyname,
                                                CURLFORM_COPYCONTENTS,  k_value,
                                                CURLFORM_END);
-                                       syslog(LOG_DEBUG, "%25s : %s\n", k_o_keyname, k_value);
+                                       syslog(LOG_DEBUG, "%25s : %s", k_o_keyname, k_value);
                                }
                                else {
-                                       syslog(LOG_INFO, "OpenID: signed field '%s' is missing\n",
+                                       syslog(LOG_INFO, "OpenID: signed field '%s' is missing",
                                                k_keyname);
                                }
                        }
@@ -845,7 +941,7 @@ void cmd_oidf(char *argbuf) {
 
        res = curl_easy_perform(curl);
        if (res) {
-               syslog(LOG_DEBUG, "cmd_oidf() libcurl error %d: %s\n", res, errmsg);
+               syslog(LOG_DEBUG, "cmd_oidf() libcurl error %d: %s", res, errmsg);
        }
        curl_easy_cleanup(curl);
        curl_formfree(formpost);
@@ -855,7 +951,7 @@ void cmd_oidf(char *argbuf) {
        }
        FreeStrBuf(&ReplyBuf);
 
-       syslog(LOG_DEBUG, "Authentication %s.\n", (oiddata->verified ? "succeeded" : "failed") );
+       syslog(LOG_DEBUG, "Authentication %s.", (oiddata->verified ? "succeeded" : "failed") );
 
        /* Respond to the client */
 
@@ -865,11 +961,11 @@ void cmd_oidf(char *argbuf) {
                if (CC->logged_in) {
                        if (attach_openid(&CC->user, oiddata->claimed_id) == 0) {
                                cprintf("attach\n");
-                               syslog(LOG_DEBUG, "OpenID attach succeeded\n");
+                               syslog(LOG_DEBUG, "OpenID attach succeeded");
                        }
                        else {
                                cprintf("fail\n");
-                               syslog(LOG_DEBUG, "OpenID attach failed\n");
+                               syslog(LOG_DEBUG, "OpenID attach failed");
                        }
                }
 
@@ -886,7 +982,7 @@ void cmd_oidf(char *argbuf) {
                        if (login_via_openid(oiddata->claimed_id) == 0) {
                                cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
                                logged_in_response();
-                               syslog(LOG_DEBUG, "Logged in using previously claimed OpenID\n");
+                               syslog(LOG_DEBUG, "Logged in using previously claimed OpenID");
                        }
 
                        /*
@@ -895,7 +991,7 @@ void cmd_oidf(char *argbuf) {
                         */
                        else if (config.c_disable_newu) {
                                cprintf("fail\n");
-                               syslog(LOG_DEBUG, "Creating user failed due to local policy\n");
+                               syslog(LOG_DEBUG, "Creating user failed due to local policy");
                        }
 
                        /*
@@ -904,7 +1000,7 @@ void cmd_oidf(char *argbuf) {
                        else if (openid_create_user_via_sreg(oiddata->claimed_id, keys) == 0) {
                                cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
                                logged_in_response();
-                               syslog(LOG_DEBUG, "Successfully auto-created new user\n");
+                               syslog(LOG_DEBUG, "Successfully auto-created new user");
                        }
 
                        /*
@@ -921,7 +1017,7 @@ void cmd_oidf(char *argbuf) {
                                else {
                                        cprintf("\n");
                                }
-                               syslog(LOG_DEBUG, "The desired Simple Registration name is already taken.\n");
+                               syslog(LOG_DEBUG, "The desired Simple Registration name is already taken.");
                        }
                }
        }
@@ -967,6 +1063,6 @@ CTDL_MODULE_INIT(openid_rp)
                openid_level_supported = 1;     /* This module supports OpenID 1.0 only */
        }
 
-       /* return our Subversion id for the Log */
+       /* return our module name for the log */
        return "openid_rp";
 }