Back to index

citadel  8.12
serv_openid_rp.c
Go to the documentation of this file.
00001 /*
00002  * This is an implementation of OpenID 2.0 relying party support in stateless mode.
00003  *
00004  * Copyright (c) 2007-2012 by the citadel.org team
00005  *
00006  * This program is open source software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 3 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, see <http://www.gnu.org/licenses/>.
00018  */
00019 
00020 #include "sysdep.h"
00021 #include <stdlib.h>
00022 #include <unistd.h>
00023 #include <stdio.h>
00024 #include <fcntl.h>
00025 #include <signal.h>
00026 #include <pwd.h>
00027 #include <errno.h>
00028 #include <sys/types.h>
00029 
00030 #if TIME_WITH_SYS_TIME
00031 # include <sys/time.h>
00032 # include <time.h>
00033 #else
00034 # if HAVE_SYS_TIME_H
00035 #  include <sys/time.h>
00036 # else
00037 #  include <time.h>
00038 # endif
00039 #endif
00040 
00041 #include <sys/wait.h>
00042 #include <string.h>
00043 #include <limits.h>
00044 #include <curl/curl.h>
00045 #include <expat.h>
00046 #include "ctdl_module.h"
00047 #include "config.h"
00048 #include "citserver.h"
00049 #include "user_ops.h"
00050 
00051 typedef struct _ctdl_openid {
00052        StrBuf *op_url;                    /* OpenID Provider Endpoint URL */
00053        StrBuf *claimed_id;         /* Claimed Identifier */
00054        int verified;
00055        HashList *sreg_keys;
00056 } ctdl_openid;
00057 
00058 enum {
00059        openid_disco_none,
00060        openid_disco_xrds,
00061        openid_disco_html
00062 };
00063 
00064 
00065 void Free_ctdl_openid(ctdl_openid **FreeMe)
00066 {
00067        if (*FreeMe == NULL) {
00068               return;
00069        }
00070        FreeStrBuf(&(*FreeMe)->op_url);
00071        FreeStrBuf(&(*FreeMe)->claimed_id);
00072        DeleteHash(&(*FreeMe)->sreg_keys);
00073        free(*FreeMe);
00074        *FreeMe = NULL;
00075 }
00076 
00077 
00078 /*
00079  * This cleanup function blows away the temporary memory used by this module.
00080  */
00081 void openid_cleanup_function(void) {
00082        struct CitContext *CCC = CC;       /* CachedCitContext - performance boost */
00083 
00084        if (CCC->openid_data != NULL) {
00085               syslog(LOG_DEBUG, "Clearing OpenID session state");
00086               Free_ctdl_openid((ctdl_openid **) &CCC->openid_data);
00087        }
00088 }
00089 
00090 
00091 /**************************************************************************/
00092 /*                                                                        */
00093 /* Functions in this section handle Citadel internal OpenID mapping stuff */
00094 /*                                                                        */
00095 /**************************************************************************/
00096 
00097 
00098 /*
00099  * The structure of an openid record *key* is:
00100  *
00101  * |--------------claimed_id-------------|
00102  *     (actual length of claimed id)
00103  *
00104  *
00105  * The structure of an openid record *value* is:
00106  *
00107  * |-----user_number----|------------claimed_id---------------|
00108  *    (sizeof long)          (actual length of claimed id)
00109  *
00110  */
00111 
00112 
00113 
00114 /*
00115  * Attach an OpenID to a Citadel account
00116  */
00117 int attach_openid(struct ctdluser *who, StrBuf *claimed_id)
00118 {
00119        struct cdbdata *cdboi;
00120        long fetched_usernum;
00121        char *data;
00122        int data_len;
00123        char buf[2048];
00124 
00125        if (!who) return(1);
00126        if (StrLength(claimed_id)==0) return(1);
00127 
00128        /* Check to see if this OpenID is already in the database */
00129 
00130        cdboi = cdb_fetch(CDB_OPENID, ChrPtr(claimed_id), StrLength(claimed_id));
00131        if (cdboi != NULL) {
00132               memcpy(&fetched_usernum, cdboi->ptr, sizeof(long));
00133               cdb_free(cdboi);
00134 
00135               if (fetched_usernum == who->usernum) {
00136                      syslog(LOG_INFO, "%s already associated; no action is taken", ChrPtr(claimed_id));
00137                      return(0);
00138               }
00139               else {
00140                      syslog(LOG_INFO, "%s already belongs to another user", ChrPtr(claimed_id));
00141                      return(3);
00142               }
00143        }
00144 
00145        /* Not already in the database, so attach it now */
00146 
00147        data_len = sizeof(long) + StrLength(claimed_id) + 1;
00148        data = malloc(data_len);
00149 
00150        memcpy(data, &who->usernum, sizeof(long));
00151        memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1);
00152 
00153        cdb_store(CDB_OPENID, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len);
00154        free(data);
00155 
00156        snprintf(buf, sizeof buf, "User <%s> (#%ld) has claimed the OpenID URL %s\n",
00157                who->fullname, who->usernum, ChrPtr(claimed_id));
00158        CtdlAideMessage(buf, "OpenID claim");
00159        syslog(LOG_INFO, "%s", buf);
00160        return(0);
00161 }
00162 
00163 
00164 
00165 /*
00166  * When a user is being deleted, we have to delete any OpenID associations
00167  */
00168 void openid_purge(struct ctdluser *usbuf) {
00169        struct cdbdata *cdboi;
00170        HashList *keys = NULL;
00171        HashPos *HashPos;
00172        char *deleteme = NULL;
00173        long len;
00174        void *Value;
00175        const char *Key;
00176        long usernum = 0L;
00177 
00178        keys = NewHash(1, NULL);
00179        if (!keys) return;
00180 
00181        cdb_rewind(CDB_OPENID);
00182        while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
00183               if (cdboi->len > sizeof(long)) {
00184                      memcpy(&usernum, cdboi->ptr, sizeof(long));
00185                      if (usernum == usbuf->usernum) {
00186                             deleteme = strdup(cdboi->ptr + sizeof(long)),
00187                             Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
00188                      }
00189               }
00190               cdb_free(cdboi);
00191        }
00192 
00193        /* Go through the hash list, deleting keys we stored in it */
00194 
00195        HashPos = GetNewHashPos(keys, 0);
00196        while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
00197        {
00198               syslog(LOG_DEBUG, "Deleting associated OpenID <%s>", (char*)Value);
00199               cdb_delete(CDB_OPENID, Value, strlen(Value));
00200               /* note: don't free(Value) -- deleting the hash list will handle this for us */
00201        }
00202        DeleteHashPos(&HashPos);
00203        DeleteHash(&keys);
00204 }
00205 
00206 
00207 /*
00208  * List the OpenIDs associated with the currently logged in account
00209  */
00210 void cmd_oidl(char *argbuf) {
00211        struct cdbdata *cdboi;
00212        long usernum = 0L;
00213 
00214        if (CtdlAccessCheck(ac_logged_in)) return;
00215        cdb_rewind(CDB_OPENID);
00216        cprintf("%d Associated OpenIDs:\n", LISTING_FOLLOWS);
00217 
00218        while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
00219               if (cdboi->len > sizeof(long)) {
00220                      memcpy(&usernum, cdboi->ptr, sizeof(long));
00221                      if (usernum == CC->user.usernum) {
00222                             cprintf("%s\n", cdboi->ptr + sizeof(long));
00223                      }
00224               }
00225               cdb_free(cdboi);
00226        }
00227        cprintf("000\n");
00228 }
00229 
00230 
00231 /*
00232  * List ALL OpenIDs in the database
00233  */
00234 void cmd_oida(char *argbuf) {
00235        struct cdbdata *cdboi;
00236        long usernum;
00237        struct ctdluser usbuf;
00238 
00239        if (CtdlAccessCheck(ac_aide)) return;
00240        cdb_rewind(CDB_OPENID);
00241        cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS);
00242 
00243        while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
00244               if (cdboi->len > sizeof(long)) {
00245                      memcpy(&usernum, cdboi->ptr, sizeof(long));
00246                      if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
00247                             usbuf.fullname[0] = 0;
00248                      } 
00249                      cprintf("%s|%ld|%s\n",
00250                             cdboi->ptr + sizeof(long),
00251                             usernum,
00252                             usbuf.fullname
00253                      );
00254               }
00255               cdb_free(cdboi);
00256        }
00257        cprintf("000\n");
00258 }
00259 
00260 
00261 /*
00262  * Create a new user account, manually specifying the name, after successfully
00263  * verifying an OpenID (which will of course be attached to the account)
00264  */
00265 void cmd_oidc(char *argbuf) {
00266        ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
00267 
00268        if ( (!oiddata) || (!oiddata->verified) ) {
00269               cprintf("%d You have not verified an OpenID yet.\n", ERROR);
00270               return;
00271        }
00272 
00273        /* We can make the semantics of OIDC exactly the same as NEWU, simply
00274         * by _calling_ cmd_newu() and letting it run.  Very clever!
00275         */
00276        cmd_newu(argbuf);
00277 
00278        /* Now, if this logged us in, we have to attach the OpenID */
00279        if (CC->logged_in) {
00280               attach_openid(&CC->user, oiddata->claimed_id);
00281        }
00282 
00283 }
00284 
00285 
00286 /*
00287  * Detach an OpenID from the currently logged in account
00288  */
00289 void cmd_oidd(char *argbuf) {
00290        struct cdbdata *cdboi;
00291        char id_to_detach[1024];
00292        int this_is_mine = 0;
00293        long usernum = 0L;
00294 
00295        if (CtdlAccessCheck(ac_logged_in)) return;
00296        extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach);
00297        if (IsEmptyStr(id_to_detach)) {
00298               cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE);
00299        }
00300 
00301        cdb_rewind(CDB_OPENID);
00302        while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
00303               if (cdboi->len > sizeof(long)) {
00304                      memcpy(&usernum, cdboi->ptr, sizeof(long));
00305                      if (usernum == CC->user.usernum) {
00306                             this_is_mine = 1;
00307                      }
00308               }
00309               cdb_free(cdboi);
00310        }
00311 
00312        if (!this_is_mine) {
00313               cprintf("%d That OpenID was not found or not associated with your account.\n",
00314                      ERROR + ILLEGAL_VALUE);
00315               return;
00316        }
00317 
00318        cdb_delete(CDB_OPENID, id_to_detach, strlen(id_to_detach));
00319        cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach);
00320 }
00321 
00322 
00323 /*
00324  * Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange
00325  */
00326 int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys)
00327 {
00328        char *nickname = NULL;
00329        char *firstname = NULL;
00330        char *lastname = NULL;
00331        char new_password[32];
00332        long len;
00333        const char *Key;
00334        void *Value;
00335 
00336        if (config.c_auth_mode != AUTHMODE_NATIVE) return(1);
00337        if (config.c_disable_newu) return(2);
00338        if (CC->logged_in) return(3);
00339 
00340        HashPos *HashPos = GetNewHashPos(sreg_keys, 0);
00341        while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) {
00342               syslog(LOG_DEBUG, "%s = %s", Key, (char *)Value);
00343 
00344               if (cbmstrcasestr(Key, "value.nickname") != NULL) {
00345                      nickname = (char *)Value;
00346               }
00347               else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) {
00348                      nickname = (char *)Value;
00349               }
00350               else if (cbmstrcasestr(Key, "value.firstname") != NULL) {
00351                      firstname = (char *)Value;
00352               }
00353               else if (cbmstrcasestr(Key, "value.lastname") != NULL) {
00354                      lastname = (char *)Value;
00355               }
00356 
00357        }
00358        DeleteHashPos(&HashPos);
00359 
00360        if (nickname == NULL) {
00361               if ((firstname != NULL) || (lastname != NULL)) {
00362                      char fullname[1024] = "";
00363                      if (firstname) strcpy(fullname, firstname);
00364                      if (firstname && lastname) strcat(fullname, " ");
00365                      if (lastname) strcat(fullname, lastname);
00366                      nickname = fullname;
00367               }
00368        }
00369 
00370        if (nickname == NULL) {
00371               return(4);
00372        }
00373        syslog(LOG_DEBUG, "The desired account name is <%s>", nickname);
00374 
00375        len = cutuserkey(nickname);
00376        if (!CtdlGetUser(&CC->user, nickname)) {
00377               syslog(LOG_DEBUG, "<%s> is already taken by another user.", nickname);
00378               memset(&CC->user, 0, sizeof(struct ctdluser));
00379               return(5);
00380        }
00381 
00382        /* The desired account name is available.  Create the account and log it in! */
00383        if (create_user(nickname, len, 1)) return(6);
00384 
00385        /* Generate a random password.
00386         * The user doesn't care what the password is since he is using OpenID.
00387         */
00388        snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random());
00389        CtdlSetPassword(new_password);
00390 
00391        /* Now attach the verified OpenID to this account. */
00392        attach_openid(&CC->user, claimed_id);
00393 
00394        return(0);
00395 }
00396 
00397 
00398 /*
00399  * If a user account exists which is associated with the Claimed ID, log it in and return zero.
00400  * Otherwise it returns nonzero.
00401  */
00402 int login_via_openid(StrBuf *claimed_id)
00403 {
00404        struct cdbdata *cdboi;
00405        long usernum = 0;
00406 
00407        cdboi = cdb_fetch(CDB_OPENID, ChrPtr(claimed_id), StrLength(claimed_id));
00408        if (cdboi == NULL) {
00409               return(-1);
00410        }
00411 
00412        memcpy(&usernum, cdboi->ptr, sizeof(long));
00413        cdb_free(cdboi);
00414 
00415        if (!CtdlGetUserByNumber(&CC->user, usernum)) {
00416               /* Now become the user we just created */
00417               safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
00418               do_login();
00419               return(0);
00420        }
00421        else {
00422               memset(&CC->user, 0, sizeof(struct ctdluser));
00423               return(-1);
00424        }
00425 }
00426 
00427 
00428 
00429 
00430 /**************************************************************************/
00431 /*                                                                        */
00432 /* Functions in this section handle OpenID protocol                       */
00433 /*                                                                        */
00434 /**************************************************************************/
00435 
00436 
00437 /* 
00438  * Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
00439  */
00440 void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf)
00441 {
00442        int i;
00443        const char *ptr;
00444        const char *href_start = NULL;
00445        const char *href_end = NULL;
00446        const char *link_tag_start = NULL;
00447        const char *link_tag_end = NULL;
00448        const char *rel_start = NULL;
00449        const char *rel_end = NULL;
00450 
00451        if (!target_buf) return;
00452        if (!rel) return;
00453        if (!source_buf) return;
00454 
00455        ptr = ChrPtr(source_buf);
00456 
00457        FlushStrBuf(target_buf);
00458        while (ptr = cbmstrcasestr(ptr, "<link"), ptr != NULL) {
00459 
00460               link_tag_start = ptr;
00461               link_tag_end = strchr(ptr, '>');
00462               if (link_tag_end == NULL)
00463                      break;
00464               for (i=0; i < 1; i++ ){
00465                      rel_start = cbmstrcasestr(link_tag_start, "rel=");
00466                      if ((rel_start == NULL) ||
00467                          (rel_start > link_tag_end)) 
00468                             continue;
00469 
00470                      rel_start = strchr(rel_start, '\"');
00471                      if ((rel_start == NULL) ||
00472                          (rel_start > link_tag_end)) 
00473                             continue;
00474                      ++rel_start;
00475                      rel_end = strchr(rel_start, '\"');
00476                      if ((rel_end == NULL) ||
00477                          (rel_end == rel_start) ||
00478                          (rel_end >= link_tag_end) ) 
00479                             continue;
00480                      if (strncasecmp(rel, rel_start, repllen)!= 0)
00481                             continue; /* didn't match? never mind... */
00482                      
00483                      href_start = cbmstrcasestr(link_tag_start, "href=");
00484                      if ((href_start == NULL) || 
00485                          (href_start >= link_tag_end)) 
00486                             continue;
00487                      href_start = strchr(href_start, '\"');
00488                      if ((href_start == NULL) |
00489                          (href_start >= link_tag_end)) 
00490                             continue;
00491                      ++href_start;
00492                      href_end = strchr(href_start, '\"');
00493                      if ((href_end == NULL) || 
00494                          (href_end == href_start) ||
00495                          (href_start >= link_tag_end)) 
00496                             continue;
00497                      StrBufPlain(target_buf, href_start, href_end - href_start);
00498               }
00499               ptr = link_tag_end;  
00500        }
00501 }
00502 
00503 
00504 /*
00505  * Wrapper for curl_easy_init() that includes the options common to all calls
00506  * used in this module. 
00507  */
00508 CURL *ctdl_openid_curl_easy_init(char *errmsg) {
00509        CURL *curl;
00510 
00511        curl = curl_easy_init();
00512        if (!curl) {
00513               return(curl);
00514        }
00515 
00516        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
00517        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
00518 
00519        if (errmsg) {
00520               curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
00521        }
00522        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
00523 #ifdef CURLOPT_HTTP_CONTENT_DECODING
00524        curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
00525        curl_easy_setopt(curl, CURLOPT_ENCODING, "");
00526 #endif
00527        curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
00528        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);            /* die after 30 seconds */
00529 
00530        if (
00531               (!IsEmptyStr(config.c_ip_addr))
00532               && (strcmp(config.c_ip_addr, "*"))
00533               && (strcmp(config.c_ip_addr, "::"))
00534               && (strcmp(config.c_ip_addr, "0.0.0.0"))
00535        ) {
00536               curl_easy_setopt(curl, CURLOPT_INTERFACE, config.c_ip_addr);
00537        }
00538 
00539        return(curl);
00540 }
00541 
00542 
00543 struct xrds {
00544        StrBuf *CharData;
00545        int nesting_level;
00546        int in_xrd;
00547        int current_service_priority;
00548        int selected_service_priority;
00549        StrBuf *current_service_uri;
00550        StrBuf *selected_service_uri;
00551        int current_service_is_oid2auth;
00552 };
00553 
00554 
00555 void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
00556        struct xrds *xrds = (struct xrds *) data;
00557        int i;
00558 
00559        ++xrds->nesting_level;
00560 
00561        if (!strcasecmp(supplied_el, "XRD")) {
00562               ++xrds->in_xrd;
00563        }
00564 
00565        else if (!strcasecmp(supplied_el, "service")) {
00566               xrds->current_service_priority = 0;
00567               xrds->current_service_is_oid2auth = 0;
00568               for (i=0; attr[i] != NULL; i+=2) {
00569                      if (!strcasecmp(attr[i], "priority")) {
00570                             xrds->current_service_priority = atoi(attr[i+1]);
00571                      }
00572               }
00573        }
00574 
00575        FlushStrBuf(xrds->CharData);
00576 }
00577 
00578 
00579 void xrds_xml_end(void *data, const char *supplied_el) {
00580        struct xrds *xrds = (struct xrds *) data;
00581 
00582        --xrds->nesting_level;
00583 
00584        if (!strcasecmp(supplied_el, "XRD")) {
00585               --xrds->in_xrd;
00586        }
00587 
00588        else if (!strcasecmp(supplied_el, "type")) {
00589               if (   (xrds->in_xrd)
00590                      && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server"))
00591               ) {
00592                      xrds->current_service_is_oid2auth = 1;
00593               }
00594               if (   (xrds->in_xrd)
00595                      && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon"))
00596               ) {
00597                      xrds->current_service_is_oid2auth = 1;
00598                      /* FIXME in this case, the Claimed ID should be considered immutable */
00599               }
00600        }
00601 
00602        else if (!strcasecmp(supplied_el, "uri")) {
00603               if (xrds->in_xrd) {
00604                      FlushStrBuf(xrds->current_service_uri);
00605                      StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0);
00606               }
00607        }
00608 
00609        else if (!strcasecmp(supplied_el, "service")) {
00610               if (   (xrds->in_xrd)
00611                      && (xrds->current_service_priority < xrds->selected_service_priority)
00612                      && (xrds->current_service_is_oid2auth)
00613               ) {
00614                      xrds->selected_service_priority = xrds->current_service_priority;
00615                      FlushStrBuf(xrds->selected_service_uri);
00616                      StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0);
00617               }
00618 
00619        }
00620 
00621        FlushStrBuf(xrds->CharData);
00622 }
00623 
00624 
00625 void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
00626        struct xrds *xrds = (struct xrds *) data;
00627 
00628        StrBufAppendBufPlain (xrds->CharData, s, len, 0);
00629 }
00630 
00631 
00632 /*
00633  * Parse an XRDS document.
00634  * If an OpenID Provider URL is discovered, op_url to that value and return nonzero.
00635  * If nothing useful happened, return 0.
00636  */
00637 int parse_xrds_document(StrBuf *ReplyBuf) {
00638        ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
00639        struct xrds xrds;
00640        int return_value = 0;
00641 
00642        memset(&xrds, 0, sizeof (struct xrds));
00643        xrds.selected_service_priority = INT_MAX;
00644        xrds.CharData = NewStrBuf();
00645        xrds.current_service_uri = NewStrBuf();
00646        xrds.selected_service_uri = NewStrBuf();
00647        XML_Parser xp = XML_ParserCreate(NULL);
00648        if (xp) {
00649               XML_SetUserData(xp, &xrds);
00650               XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
00651               XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
00652               XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
00653               XML_Parse(xp, "", 0, 1);
00654               XML_ParserFree(xp);
00655        }
00656        else {
00657               syslog(LOG_ALERT, "Cannot create XML parser");
00658        }
00659 
00660        if (xrds.selected_service_priority < INT_MAX) {
00661               if (oiddata->op_url == NULL) {
00662                      oiddata->op_url = NewStrBuf();
00663               }
00664               FlushStrBuf(oiddata->op_url);
00665               StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0);
00666               return_value = openid_disco_xrds;
00667        }
00668 
00669        FreeStrBuf(&xrds.CharData);
00670        FreeStrBuf(&xrds.current_service_uri);
00671        FreeStrBuf(&xrds.selected_service_uri);
00672 
00673        return(return_value);
00674 }
00675 
00676 
00677 /*
00678  * Callback function for perform_openid2_discovery()
00679  * We're interested in the X-XRDS-Location: header.
00680  */
00681 size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) {
00682        char hdr[1024];
00683        StrBuf **x_xrds_location = (StrBuf **) userdata;
00684 
00685        memcpy(hdr, ptr, (size*nmemb));
00686        hdr[size*nmemb] = 0;
00687 
00688        if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) {
00689               *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16));
00690               StrBufTrim(*x_xrds_location);
00691        }
00692 
00693        return(size * nmemb);
00694 }
00695 
00696 
00697 /* Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5.
00698  * 
00699  * If Yadis fails, we then attempt HTML discovery using the same document.
00700  *
00701  * If successful, returns nonzero and calls parse_xrds_document() to act upon the received data.
00702  * If fails, returns 0 and does nothing else.
00703  */
00704 int perform_openid2_discovery(StrBuf *SuppliedURL) {
00705        ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
00706        int docbytes = (-1);
00707        StrBuf *ReplyBuf = NULL;
00708        int return_value = 0;
00709        CURL *curl;
00710        CURLcode result;
00711        char errmsg[1024] = "";
00712        struct curl_slist *my_headers = NULL;
00713        StrBuf *x_xrds_location = NULL;
00714 
00715        if (!SuppliedURL) return(0);
00716        syslog(LOG_DEBUG, "perform_openid2_discovery(%s)", ChrPtr(SuppliedURL));
00717        if (StrLength(SuppliedURL) == 0) return(0);
00718 
00719        ReplyBuf = NewStrBuf();
00720        if (!ReplyBuf) return(0);
00721 
00722        curl = ctdl_openid_curl_easy_init(errmsg);
00723        if (!curl) return(0);
00724 
00725        curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL));
00726        curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
00727        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
00728 
00729        my_headers = curl_slist_append(my_headers, "Accept:");  /* disable the default Accept: header */
00730        my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml");
00731        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers);
00732 
00733        curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location);
00734        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction);
00735 
00736        result = curl_easy_perform(curl);
00737        if (result) {
00738               syslog(LOG_DEBUG, "libcurl error %d: %s", result, errmsg);
00739        }
00740        curl_slist_free_all(my_headers);
00741        curl_easy_cleanup(curl);
00742        docbytes = StrLength(ReplyBuf);
00743 
00744        /*
00745         * The response from the server will be one of:
00746         * 
00747         * Option 1: An HTML document with a <head> element that includes a <meta> element with http-equiv
00748         * attribute, X-XRDS-Location,
00749         *
00750         * Does any provider actually do this?  If so then we will implement it in the future.
00751         */
00752 
00753        /*
00754         * Option 2: HTTP response-headers that include an X-XRDS-Location response-header,
00755         *           together with a document.
00756         * Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header,
00757         *           a contenttype response-header specifying MIME media type,
00758         *           application/xrds+xml, or both.
00759         *
00760         * If the X-XRDS-Location header was delivered, we know about it at this point...
00761         */
00762        if (   (x_xrds_location)
00763               && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL)))
00764        ) {
00765               syslog(LOG_DEBUG, "X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location));
00766               return_value = perform_openid2_discovery(x_xrds_location);
00767               FreeStrBuf(&x_xrds_location);
00768        }
00769 
00770        /*
00771         * Option 4: the returned web page may *be* an XRDS document.  Try to parse it.
00772         */
00773        if ( (return_value == 0) && (docbytes >= 0)) {
00774               return_value = parse_xrds_document(ReplyBuf);
00775        }
00776 
00777        /*
00778         * Option 5: if all else fails, attempt HTML based discovery.
00779         */
00780        if ( (return_value == 0) && (docbytes >= 0)) {
00781               if (oiddata->op_url == NULL) {
00782                      oiddata->op_url = NewStrBuf();
00783               }
00784               extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf);
00785               if (StrLength(oiddata->op_url) > 0) {
00786                      return_value = openid_disco_html;
00787               }
00788        }
00789 
00790        if (ReplyBuf != NULL) {
00791               FreeStrBuf(&ReplyBuf);
00792        }
00793        return(return_value);
00794 }
00795 
00796 
00797 /*
00798  * Setup an OpenID authentication
00799  */
00800 void cmd_oids(char *argbuf) {
00801        struct CitContext *CCC = CC;       /* CachedCitContext - performance boost */
00802        const char *Pos = NULL;
00803        StrBuf *ArgBuf = NULL;
00804        StrBuf *ReplyBuf = NULL;
00805        StrBuf *return_to = NULL;
00806        StrBuf *RedirectUrl = NULL;
00807        ctdl_openid *oiddata;
00808        int discovery_succeeded = 0;
00809 
00810        Free_ctdl_openid ((ctdl_openid**)&CCC->openid_data);
00811 
00812        CCC->openid_data = oiddata = malloc(sizeof(ctdl_openid));
00813        if (oiddata == NULL) {
00814               syslog(LOG_ALERT, "malloc() failed: %s", strerror(errno));
00815               cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
00816               return;
00817        }
00818        memset(oiddata, 0, sizeof(ctdl_openid));
00819 
00820        ArgBuf = NewStrBufPlain(argbuf, -1);
00821 
00822        oiddata->verified = 0;
00823        oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
00824        return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
00825 
00826        StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
00827        StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
00828 
00829        syslog(LOG_DEBUG, "User-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
00830 
00831        /********** OpenID 2.0 section 7.3 - Discovery **********/
00832 
00833        /* Section 7.3.1 says we have to attempt XRI based discovery.
00834         * No one is using this, no one is asking for it, no one wants it.
00835         * So we're not even going to bother attempting this mode.
00836         */
00837 
00838        /* Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery);
00839         */
00840        discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id);
00841 
00842        if (discovery_succeeded == 0) {
00843               cprintf("%d There is no OpenID identity provider at this location.\n", ERROR);
00844        }
00845 
00846        else {
00847               /*
00848                * If we get to this point we are in possession of a valid OpenID Provider URL.
00849                */
00850               syslog(LOG_DEBUG, "OP URI '%s' discovered using method %d",
00851                      ChrPtr(oiddata->op_url),
00852                      discovery_succeeded
00853               );
00854 
00855               /* We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf */
00856               if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) {
00857                      StrBuf *cid = oiddata->claimed_id;
00858                      oiddata->claimed_id = NewStrBufPlain(HKEY("http://"));
00859                      StrBufAppendBuf(oiddata->claimed_id, cid, 0);
00860                      FreeStrBuf(&cid);
00861               }
00862 
00863               /*
00864                * OpenID 2.0 section 9: request authentication
00865                * Assemble a URL to which the user-agent will be redirected.
00866                */
00867        
00868               RedirectUrl = NewStrBufDup(oiddata->op_url);
00869 
00870               StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0);
00871               StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0");
00872 
00873               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0);
00874 
00875               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0);
00876               StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
00877 
00878               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0);
00879               StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
00880 
00881               /* return_to tells the provider how to complete the round trip back to our site */
00882               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0);
00883               StrBufUrlescAppend(RedirectUrl, return_to, NULL);
00884 
00885               /* Attribute Exchange
00886                * See:
00887                *     http://openid.net/specs/openid-attribute-exchange-1_0.html
00888                *     http://code.google.com/apis/accounts/docs/OpenID.html#endpoint
00889                *     http://test-id.net/OP/AXFetch.aspx
00890                */
00891 
00892               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0);
00893               StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0");
00894 
00895               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0);
00896 
00897               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0);
00898 
00899               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0);
00900               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first");
00901 
00902               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0);
00903               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last");
00904 
00905               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0);
00906               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly");
00907 
00908               StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0);
00909               StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname");
00910 
00911               syslog(LOG_DEBUG, "OpenID: redirecting client to %s", ChrPtr(RedirectUrl));
00912               cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl));
00913        }
00914        
00915        FreeStrBuf(&ArgBuf);
00916        FreeStrBuf(&ReplyBuf);
00917        FreeStrBuf(&return_to);
00918        FreeStrBuf(&RedirectUrl);
00919 }
00920 
00921 
00922 /*
00923  * Finalize an OpenID authentication
00924  */
00925 void cmd_oidf(char *argbuf) {
00926        long len;
00927        char buf[2048];
00928        char thiskey[1024];
00929        char thisdata[1024];
00930        HashList *keys = NULL;
00931        const char *Key;
00932        void *Value;
00933        ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
00934 
00935        if (oiddata == NULL) {
00936               cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR);
00937               return;
00938        }
00939        if (StrLength(oiddata->op_url) == 0){
00940               cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE);
00941               return;
00942        }
00943        keys = NewHash(1, NULL);
00944        if (!keys) {
00945               cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
00946               return;
00947        }
00948        cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
00949 
00950        while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
00951               len = extract_token(thiskey, buf, 0, '|', sizeof thiskey);
00952               if (len < 0) {
00953                      len = sizeof(thiskey) - 1;
00954               }
00955               extract_token(thisdata, buf, 1, '|', sizeof thisdata);
00956               Put(keys, thiskey, len, strdup(thisdata), NULL);
00957        }
00958 
00959        /* Check to see if this is a correct response.
00960         * Start with verified=1 but then set it to 0 if anything looks wrong.
00961         */
00962        oiddata->verified = 1;
00963 
00964        char *openid_ns = NULL;
00965        if (   (!GetHash(keys, "ns", 2, (void *) &openid_ns))
00966               || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0"))
00967        ) {
00968               syslog(LOG_DEBUG, "This is not an an OpenID assertion");
00969               oiddata->verified = 0;
00970        }
00971 
00972        char *openid_mode = NULL;
00973        if (   (!GetHash(keys, "mode", 4, (void *) &openid_mode))
00974               || (strcasecmp(openid_mode, "id_res"))
00975        ) {
00976               oiddata->verified = 0;
00977        }
00978 
00979        char *openid_claimed_id = NULL;
00980        if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) {
00981               FreeStrBuf(&oiddata->claimed_id);
00982               oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1);
00983               syslog(LOG_DEBUG, "Provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id));
00984        }
00985 
00986        /* Validate the assertion against the server */
00987        syslog(LOG_DEBUG, "Validating...");
00988 
00989        CURL *curl;
00990        CURLcode res;
00991        struct curl_httppost *formpost = NULL;
00992        struct curl_httppost *lastptr = NULL;
00993        char errmsg[1024] = "";
00994        StrBuf *ReplyBuf = NewStrBuf();
00995 
00996        curl_formadd(&formpost, &lastptr,
00997               CURLFORM_COPYNAME,   "openid.mode",
00998               CURLFORM_COPYCONTENTS,      "check_authentication",
00999               CURLFORM_END
01000        );
01001 
01002        HashPos *HashPos = GetNewHashPos(keys, 0);
01003        while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) {
01004               if (strcasecmp(Key, "mode")) {
01005                      char k_o_keyname[1024];
01006                      snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key);
01007                      curl_formadd(&formpost, &lastptr,
01008                             CURLFORM_COPYNAME,   k_o_keyname,
01009                             CURLFORM_COPYCONTENTS,      (char *)Value,
01010                             CURLFORM_END
01011                      );
01012               }
01013        }
01014        DeleteHashPos(&HashPos);
01015 
01016        curl = ctdl_openid_curl_easy_init(errmsg);
01017        curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url));
01018        curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
01019        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
01020        curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
01021 
01022        res = curl_easy_perform(curl);
01023        if (res) {
01024               syslog(LOG_DEBUG, "cmd_oidf() libcurl error %d: %s", res, errmsg);
01025               oiddata->verified = 0;
01026        }
01027        curl_easy_cleanup(curl);
01028        curl_formfree(formpost);
01029 
01030        /* syslog(LOG_DEBUG, "Validation reply: \n%s", ChrPtr(ReplyBuf)); */
01031        if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) {
01032               oiddata->verified = 0;
01033        }
01034        FreeStrBuf(&ReplyBuf);
01035 
01036        syslog(LOG_DEBUG, "OpenID authentication %s", (oiddata->verified ? "succeeded" : "failed") );
01037 
01038        /* Respond to the client */
01039 
01040        if (oiddata->verified) {
01041 
01042               /* If we were already logged in, attach the OpenID to the user's account */
01043               if (CC->logged_in) {
01044                      if (attach_openid(&CC->user, oiddata->claimed_id) == 0) {
01045                             cprintf("attach\n");
01046                             syslog(LOG_DEBUG, "OpenID attach succeeded");
01047                      }
01048                      else {
01049                             cprintf("fail\n");
01050                             syslog(LOG_DEBUG, "OpenID attach failed");
01051                      }
01052               }
01053 
01054               /* Otherwise, a user is attempting to log in using the verified OpenID */    
01055               else {
01056                      /*
01057                       * Existing user who has claimed this OpenID?
01058                       *
01059                       * Note: if you think that sending the password back over the wire is insecure,
01060                       * check your assumptions.  If someone has successfully asserted an OpenID that
01061                       * is associated with the account, they already have password equivalency and can
01062                       * login, so they could just as easily change the password, etc.
01063                       */
01064                      if (login_via_openid(oiddata->claimed_id) == 0) {
01065                             cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
01066                             logged_in_response();
01067                             syslog(LOG_DEBUG, "Logged in using previously claimed OpenID");
01068                      }
01069 
01070                      /*
01071                       * If this system does not allow self-service new user registration, the
01072                       * remaining modes do not apply, so fail here and now.
01073                       */
01074                      else if (config.c_disable_newu) {
01075                             cprintf("fail\n");
01076                             syslog(LOG_DEBUG, "Creating user failed due to local policy");
01077                      }
01078 
01079                      /*
01080                       * New user whose OpenID is verified and Attribute Exchange gave us a name?
01081                       */
01082                      else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) {
01083                             cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
01084                             logged_in_response();
01085                             syslog(LOG_DEBUG, "Successfully auto-created new user");
01086                      }
01087 
01088                      /*
01089                       * OpenID is verified, but the desired username either was not specified or
01090                       * conflicts with an existing user.  Manual account creation is required.
01091                       */
01092                      else {
01093                             char *desired_name = NULL;
01094                             cprintf("verify_only\n");
01095                             cprintf("%s\n", ChrPtr(oiddata->claimed_id));
01096                             if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) {
01097                                    cprintf("%s\n", desired_name);
01098                             }
01099                             else {
01100                                    cprintf("\n");
01101                             }
01102                             syslog(LOG_DEBUG, "The desired display name is already taken.");
01103                      }
01104               }
01105        }
01106        else {
01107               cprintf("fail\n");
01108        }
01109        cprintf("000\n");
01110 
01111        if (oiddata->sreg_keys != NULL) {
01112               DeleteHash(&oiddata->sreg_keys);
01113               oiddata->sreg_keys = NULL;
01114        }
01115        oiddata->sreg_keys = keys;
01116 }
01117 
01118 
01119 
01120 /**************************************************************************/
01121 /*                                                                        */
01122 /* Functions in this section handle module initialization and shutdown    */
01123 /*                                                                        */
01124 /**************************************************************************/
01125 
01126 
01127 
01128 
01129 CTDL_MODULE_INIT(openid_rp)
01130 {
01131        if (!threading) {
01132 // evcurl call this for us. curl_global_init(CURL_GLOBAL_ALL);
01133 
01134               /* Only enable the OpenID command set when native mode authentication is in use. */
01135               if (config.c_auth_mode == AUTHMODE_NATIVE) {
01136                      CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
01137                      CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
01138                      CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
01139                      CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account");
01140                      CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID");
01141                      CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database");
01142               }
01143               CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10);
01144               CtdlRegisterUserHook(openid_purge, EVT_PURGEUSER);
01145               openid_level_supported = 1; /* This module supports OpenID 1.0 only */
01146        }
01147 
01148        /* return our module name for the log */
01149        return "openid_rp";
01150 }