]> code.citadel.org Git - citadel.git/blob - citadel/server/modules/openid/serv_openid_rp.c
Revert "citserver: remove openid support"
[citadel.git] / citadel / server / modules / openid / serv_openid_rp.c
1 // This is an implementation of OpenID 2.0 relying party support in stateless mode.
2 //
3 // Copyright (c) 2007-2022 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7
8 #include "../../sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <pwd.h>
15 #include <errno.h>
16 #include <sys/types.h>
17 #include <time.h>
18 #include <sys/wait.h>
19 #include <string.h>
20 #include <limits.h>
21 #include <curl/curl.h>
22 #include <expat.h>
23 #include "../../ctdl_module.h"
24 #include "../../config.h"
25 #include "../../citserver.h"
26 #include "../../user_ops.h"
27
28 typedef struct _ctdl_openid {
29         StrBuf *op_url;                 // OpenID Provider Endpoint URL
30         StrBuf *claimed_id;             // Claimed Identifier
31         int verified;
32         HashList *sreg_keys;
33 } ctdl_openid;
34
35 enum {
36         openid_disco_none,
37         openid_disco_xrds,
38         openid_disco_html
39 };
40
41
42 void Free_ctdl_openid(ctdl_openid **FreeMe) {
43         if (*FreeMe == NULL) {
44                 return;
45         }
46         FreeStrBuf(&(*FreeMe)->op_url);
47         FreeStrBuf(&(*FreeMe)->claimed_id);
48         DeleteHash(&(*FreeMe)->sreg_keys);
49         free(*FreeMe);
50         *FreeMe = NULL;
51 }
52
53
54 // This cleanup function blows away the temporary memory used by this module.
55 void openid_cleanup_function(void) {
56
57         if (CC->openid_data != NULL) {
58                 syslog(LOG_DEBUG, "openid: Clearing OpenID session state");
59                 Free_ctdl_openid((ctdl_openid **) &CC->openid_data);
60         }
61 }
62
63
64 // Functions in this section handle Citadel internal OpenID mapping stuff
65
66
67 // The structure of an openid record *key* is:
68 //
69 // |--------------claimed_id-------------|
70 //     (actual length of claimed id)
71 //
72 //
73 // The structure of an openid record *value* is:
74 //
75 // |-----user_number----|------------claimed_id---------------|
76 //    (sizeof long)          (actual length of claimed id)
77
78
79
80 // Attach an external authenticator (such as an OpenID) to a Citadel account
81 int attach_extauth(struct ctdluser *who, StrBuf *claimed_id) {
82         struct cdbdata *cdboi;
83         long fetched_usernum;
84         char *data;
85         int data_len;
86         char buf[2048];
87
88         if (!who) return(1);
89         if (StrLength(claimed_id)==0) return(1);
90
91         // Check to see if this authenticator is already in the database
92
93         cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
94         if (cdboi != NULL) {
95                 memcpy(&fetched_usernum, cdboi->ptr, sizeof(long));
96                 cdb_free(cdboi);
97
98                 if (fetched_usernum == who->usernum) {
99                         syslog(LOG_INFO, "openid: %s already associated; no action is taken", ChrPtr(claimed_id));
100                         return(0);
101                 }
102                 else {
103                         syslog(LOG_INFO, "openid: %s already belongs to another user", ChrPtr(claimed_id));
104                         return(3);
105                 }
106         }
107
108         // Not already in the database, so attach it now
109
110         data_len = sizeof(long) + StrLength(claimed_id) + 1;
111         data = malloc(data_len);
112
113         memcpy(data, &who->usernum, sizeof(long));
114         memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1);
115
116         cdb_store(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len);
117         free(data);
118
119         snprintf(buf, sizeof buf, "User <%s> (#%ld) is now associated with %s\n", who->fullname, who->usernum, ChrPtr(claimed_id));
120         CtdlAideMessage(buf, "External authenticator claim");
121         syslog(LOG_INFO, "openid: %s", buf);
122         return(0);
123 }
124
125
126 // When a user is being deleted, we have to delete any OpenID associations
127 void extauth_purge(struct ctdluser *usbuf) {
128         struct cdbdata *cdboi;
129         HashList *keys = NULL;
130         HashPos *HashPos;
131         char *deleteme = NULL;
132         long len;
133         void *Value;
134         const char *Key;
135         long usernum = 0L;
136
137         keys = NewHash(1, NULL);
138         if (!keys) return;
139
140         cdb_rewind(CDB_EXTAUTH);
141         while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
142                 if (cdboi->len > sizeof(long)) {
143                         memcpy(&usernum, cdboi->ptr, sizeof(long));
144                         if (usernum == usbuf->usernum) {
145                                 deleteme = strdup(cdboi->ptr + sizeof(long)),
146                                 Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
147                         }
148                 }
149                 cdb_free(cdboi);
150         }
151
152         // Go through the hash list, deleting keys we stored in it
153
154         HashPos = GetNewHashPos(keys, 0);
155         while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
156         {
157                 syslog(LOG_DEBUG, "openid: deleting associated external authenticator <%s>", (char*)Value);
158                 cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
159                 // note: don't free(Value) -- deleting the hash list will handle this for us
160         }
161         DeleteHashPos(&HashPos);
162         DeleteHash(&keys);
163 }
164
165
166 // List the OpenIDs associated with the currently logged in account
167 void cmd_oidl(char *argbuf) {
168         struct cdbdata *cdboi;
169         long usernum = 0L;
170
171         if (CtdlGetConfigInt("c_disable_newu")) {
172                 cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED);
173                 return;
174         }
175         if (CtdlAccessCheck(ac_logged_in)) return;
176
177         cdb_rewind(CDB_EXTAUTH);
178         cprintf("%d Associated external authenticators:\n", LISTING_FOLLOWS);
179
180         while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
181                 if (cdboi->len > sizeof(long)) {
182                         memcpy(&usernum, cdboi->ptr, sizeof(long));
183                         if (usernum == CC->user.usernum) {
184                                 cprintf("%s\n", cdboi->ptr + sizeof(long));
185                         }
186                 }
187                 cdb_free(cdboi);
188         }
189         cprintf("000\n");
190 }
191
192
193 // List ALL OpenIDs in the database
194 void cmd_oida(char *argbuf) {
195         struct cdbdata *cdboi;
196         long usernum;
197         struct ctdluser usbuf;
198
199         if (CtdlGetConfigInt("c_disable_newu")) {
200                 cprintf("%d this system does not support openid.\n",
201                         ERROR + CMD_NOT_SUPPORTED);
202                 return;
203         }
204         if (CtdlAccessCheck(ac_aide)) return;
205         cdb_rewind(CDB_EXTAUTH);
206         cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS);
207
208         while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
209                 if (cdboi->len > sizeof(long)) {
210                         memcpy(&usernum, cdboi->ptr, sizeof(long));
211                         if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
212                                 usbuf.fullname[0] = 0;
213                         } 
214                         cprintf("%s|%ld|%s\n",
215                                 cdboi->ptr + sizeof(long),
216                                 usernum,
217                                 usbuf.fullname
218                         );
219                 }
220                 cdb_free(cdboi);
221         }
222         cprintf("000\n");
223 }
224
225
226 // Create a new user account, manually specifying the name, after successfully
227 // verifying an OpenID (which will of course be attached to the account)
228 void cmd_oidc(char *argbuf) {
229         ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
230
231         if (CtdlGetConfigInt("c_disable_newu")) {
232                 cprintf("%d this system does not support openid.\n",
233                         ERROR + CMD_NOT_SUPPORTED);
234                 return;
235         }
236         if ( (!oiddata) || (!oiddata->verified) ) {
237                 cprintf("%d You have not verified an OpenID yet.\n", ERROR);
238                 return;
239         }
240
241         // We can make the semantics of OIDC exactly the same as NEWU, simply
242         // by _calling_ cmd_newu() and letting it run.  Very clever!
243         cmd_newu(argbuf);
244
245         // Now, if this logged us in, we have to attach the OpenID
246         if (CC->logged_in) {
247                 attach_extauth(&CC->user, oiddata->claimed_id);
248         }
249
250 }
251
252
253 // Detach an OpenID from the currently logged in account
254 void cmd_oidd(char *argbuf) {
255         struct cdbdata *cdboi;
256         char id_to_detach[1024];
257         int this_is_mine = 0;
258         long usernum = 0L;
259
260         if (CtdlGetConfigInt("c_disable_newu")) {
261                 cprintf("%d this system does not support openid.\n",
262                         ERROR + CMD_NOT_SUPPORTED);
263                 return;
264         }
265         if (CtdlAccessCheck(ac_logged_in)) return;
266         extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach);
267         if (IsEmptyStr(id_to_detach)) {
268                 cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE);
269         }
270
271         cdb_rewind(CDB_EXTAUTH);
272         while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
273                 if (cdboi->len > sizeof(long)) {
274                         memcpy(&usernum, cdboi->ptr, sizeof(long));
275                         if (usernum == CC->user.usernum) {
276                                 this_is_mine = 1;
277                         }
278                 }
279                 cdb_free(cdboi);
280         }
281
282         if (!this_is_mine) {
283                 cprintf("%d That OpenID was not found or not associated with your account.\n",
284                         ERROR + ILLEGAL_VALUE);
285                 return;
286         }
287
288         cdb_delete(CDB_EXTAUTH, id_to_detach, strlen(id_to_detach));
289         cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach);
290 }
291
292
293 // Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange
294 int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys) {
295         char *nickname = NULL;
296         char *firstname = NULL;
297         char *lastname = NULL;
298         char new_password[32];
299         long len;
300         const char *Key;
301         void *Value;
302
303         if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) return(1);
304         if (CtdlGetConfigInt("c_disable_newu")) return(2);
305         if (CC->logged_in) return(3);
306
307         HashPos *HashPos = GetNewHashPos(sreg_keys, 0);
308         while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) {
309                 syslog(LOG_DEBUG, "openid: %s = %s", Key, (char *)Value);
310
311                 if (cbmstrcasestr(Key, "value.nickname") != NULL) {
312                         nickname = (char *)Value;
313                 }
314                 else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) {
315                         nickname = (char *)Value;
316                 }
317                 else if (cbmstrcasestr(Key, "value.firstname") != NULL) {
318                         firstname = (char *)Value;
319                 }
320                 else if (cbmstrcasestr(Key, "value.lastname") != NULL) {
321                         lastname = (char *)Value;
322                 }
323
324         }
325         DeleteHashPos(&HashPos);
326
327         if (nickname == NULL) {
328                 if ((firstname != NULL) || (lastname != NULL)) {
329                         char fullname[1024] = "";
330                         if (firstname) strcpy(fullname, firstname);
331                         if (firstname && lastname) strcat(fullname, " ");
332                         if (lastname) strcat(fullname, lastname);
333                         nickname = fullname;
334                 }
335         }
336
337         if (nickname == NULL) {
338                 return(4);
339         }
340         syslog(LOG_DEBUG, "openid: the desired account name is <%s>", nickname);
341
342         if (!CtdlGetUser(&CC->user, nickname)) {
343                 syslog(LOG_DEBUG, "openid: <%s> is already taken by another user.", nickname);
344                 memset(&CC->user, 0, sizeof(struct ctdluser));
345                 return(5);
346         }
347
348         // The desired account name is available.  Create the account and log it in!
349         if (create_user(nickname, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID)) return(6);
350
351         // Generate a random password.
352         // The user doesn't care what the password is since he is using OpenID.
353         snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random());
354         CtdlSetPassword(new_password);
355
356         // Now attach the verified OpenID to this account.
357         attach_extauth(&CC->user, claimed_id);
358
359         return(0);
360 }
361
362
363 // If a user account exists which is associated with the Claimed ID, log it in and return zero.
364 // Otherwise it returns nonzero.
365 int login_via_extauth(StrBuf *claimed_id) {
366         struct cdbdata *cdboi;
367         long usernum = 0;
368
369         cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
370         if (cdboi == NULL) {
371                 return(-1);
372         }
373
374         memcpy(&usernum, cdboi->ptr, sizeof(long));
375         cdb_free(cdboi);
376
377         if (!CtdlGetUserByNumber(&CC->user, usernum)) {
378                 // Now become the user we just created
379                 safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
380                 do_login();
381                 return(0);
382         }
383         else {
384                 memset(&CC->user, 0, sizeof(struct ctdluser));
385                 return(-1);
386         }
387 }
388
389
390 // Functions in this section handle OpenID protocol
391
392
393 // Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
394 void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf) {
395         int i;
396         const char *ptr;
397         const char *href_start = NULL;
398         const char *href_end = NULL;
399         const char *link_tag_start = NULL;
400         const char *link_tag_end = NULL;
401         const char *rel_start = NULL;
402         const char *rel_end = NULL;
403
404         if (!target_buf) return;
405         if (!rel) return;
406         if (!source_buf) return;
407
408         ptr = ChrPtr(source_buf);
409
410         FlushStrBuf(target_buf);
411         while (ptr = cbmstrcasestr(ptr, "<link"), ptr != NULL) {
412
413                 link_tag_start = ptr;
414                 link_tag_end = strchr(ptr, '>');
415                 if (link_tag_end == NULL)
416                         break;
417                 for (i=0; i < 1; i++ ){
418                         rel_start = cbmstrcasestr(link_tag_start, "rel=");
419                         if ((rel_start == NULL) ||
420                             (rel_start > link_tag_end)) 
421                                 continue;
422
423                         rel_start = strchr(rel_start, '\"');
424                         if ((rel_start == NULL) ||
425                             (rel_start > link_tag_end)) 
426                                 continue;
427                         ++rel_start;
428                         rel_end = strchr(rel_start, '\"');
429                         if ((rel_end == NULL) ||
430                             (rel_end == rel_start) ||
431                             (rel_end >= link_tag_end) ) 
432                                 continue;
433                         if (strncasecmp(rel, rel_start, repllen)!= 0)
434                                 continue; // didn't match? never mind...
435                         
436                         href_start = cbmstrcasestr(link_tag_start, "href=");
437                         if ((href_start == NULL) || 
438                             (href_start >= link_tag_end)) 
439                                 continue;
440                         href_start = strchr(href_start, '\"');
441                         if ((href_start == NULL) |
442                             (href_start >= link_tag_end)) 
443                                 continue;
444                         ++href_start;
445                         href_end = strchr(href_start, '\"');
446                         if ((href_end == NULL) || 
447                             (href_end == href_start) ||
448                             (href_start >= link_tag_end)) 
449                                 continue;
450                         StrBufPlain(target_buf, href_start, href_end - href_start);
451                 }
452                 ptr = link_tag_end;     
453         }
454 }
455
456
457 // Wrapper for curl_easy_init() that includes the options common to all calls used in this module. 
458 CURL *ctdl_openid_curl_easy_init(char *errmsg) {
459         CURL *curl;
460
461         curl = curl_easy_init();
462         if (!curl) {
463                 return(curl);
464         }
465
466         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
467         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
468
469         if (errmsg) {
470                 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
471         }
472         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
473 #ifdef CURLOPT_HTTP_CONTENT_DECODING
474         curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
475         curl_easy_setopt(curl, CURLOPT_ENCODING, "");
476 #endif
477         curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
478         curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);            // die after 30 seconds
479
480         if (
481                 (!IsEmptyStr(CtdlGetConfigStr("c_ip_addr")))
482                 && (strcmp(CtdlGetConfigStr("c_ip_addr"), "*"))
483                 && (strcmp(CtdlGetConfigStr("c_ip_addr"), "::"))
484                 && (strcmp(CtdlGetConfigStr("c_ip_addr"), "0.0.0.0"))
485         ) {
486                 curl_easy_setopt(curl, CURLOPT_INTERFACE, CtdlGetConfigStr("c_ip_addr"));
487         }
488
489         return(curl);
490 }
491
492
493 struct xrds {
494         StrBuf *CharData;
495         int nesting_level;
496         int in_xrd;
497         int current_service_priority;
498         int selected_service_priority;
499         StrBuf *current_service_uri;
500         StrBuf *selected_service_uri;
501         int current_service_is_oid2auth;
502 };
503
504
505 void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
506         struct xrds *xrds = (struct xrds *) data;
507         int i;
508
509         ++xrds->nesting_level;
510
511         if (!strcasecmp(supplied_el, "XRD")) {
512                 ++xrds->in_xrd;
513         }
514
515         else if (!strcasecmp(supplied_el, "service")) {
516                 xrds->current_service_priority = 0;
517                 xrds->current_service_is_oid2auth = 0;
518                 for (i=0; attr[i] != NULL; i+=2) {
519                         if (!strcasecmp(attr[i], "priority")) {
520                                 xrds->current_service_priority = atoi(attr[i+1]);
521                         }
522                 }
523         }
524
525         FlushStrBuf(xrds->CharData);
526 }
527
528
529 void xrds_xml_end(void *data, const char *supplied_el) {
530         struct xrds *xrds = (struct xrds *) data;
531
532         --xrds->nesting_level;
533
534         if (!strcasecmp(supplied_el, "XRD")) {
535                 --xrds->in_xrd;
536         }
537
538         else if (!strcasecmp(supplied_el, "type")) {
539                 if (    (xrds->in_xrd)
540                         && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server"))
541                 ) {
542                         xrds->current_service_is_oid2auth = 1;
543                 }
544                 if (    (xrds->in_xrd)
545                         && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon"))
546                 ) {
547                         xrds->current_service_is_oid2auth = 1;
548                         // FIXME in this case, the Claimed ID should be considered immutable
549                 }
550         }
551
552         else if (!strcasecmp(supplied_el, "uri")) {
553                 if (xrds->in_xrd) {
554                         FlushStrBuf(xrds->current_service_uri);
555                         StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0);
556                 }
557         }
558
559         else if (!strcasecmp(supplied_el, "service")) {
560                 if (    (xrds->in_xrd)
561                         && (xrds->current_service_priority < xrds->selected_service_priority)
562                         && (xrds->current_service_is_oid2auth)
563                 ) {
564                         xrds->selected_service_priority = xrds->current_service_priority;
565                         FlushStrBuf(xrds->selected_service_uri);
566                         StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0);
567                 }
568
569         }
570
571         FlushStrBuf(xrds->CharData);
572 }
573
574
575 void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
576         struct xrds *xrds = (struct xrds *) data;
577
578         StrBufAppendBufPlain (xrds->CharData, s, len, 0);
579 }
580
581
582 // Parse an XRDS document.
583 // If an OpenID Provider URL is discovered, op_url to that value and return nonzero.
584 // If nothing useful happened, return 0.
585 int parse_xrds_document(StrBuf *ReplyBuf) {
586         ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
587         struct xrds xrds;
588         int return_value = 0;
589
590         memset(&xrds, 0, sizeof (struct xrds));
591         xrds.selected_service_priority = INT_MAX;
592         xrds.CharData = NewStrBuf();
593         xrds.current_service_uri = NewStrBuf();
594         xrds.selected_service_uri = NewStrBuf();
595         XML_Parser xp = XML_ParserCreate(NULL);
596         if (xp) {
597                 XML_SetUserData(xp, &xrds);
598                 XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
599                 XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
600                 XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
601                 XML_Parse(xp, "", 0, 1);
602                 XML_ParserFree(xp);
603         }
604         else {
605                 syslog(LOG_ERR, "openid: cannot create XML parser");
606         }
607
608         if (xrds.selected_service_priority < INT_MAX) {
609                 if (oiddata->op_url == NULL) {
610                         oiddata->op_url = NewStrBuf();
611                 }
612                 FlushStrBuf(oiddata->op_url);
613                 StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0);
614                 return_value = openid_disco_xrds;
615         }
616
617         FreeStrBuf(&xrds.CharData);
618         FreeStrBuf(&xrds.current_service_uri);
619         FreeStrBuf(&xrds.selected_service_uri);
620
621         return(return_value);
622 }
623
624
625 // Callback function for perform_openid2_discovery()
626 // We're interested in the X-XRDS-Location: header.
627 size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) {
628         char hdr[1024];
629         StrBuf **x_xrds_location = (StrBuf **) userdata;
630
631         memcpy(hdr, ptr, (size*nmemb));
632         hdr[size*nmemb] = 0;
633
634         if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) {
635                 *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16));
636                 StrBufTrim(*x_xrds_location);
637         }
638
639         return(size * nmemb);
640 }
641
642
643 // Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5.
644 // 
645 // If Yadis fails, we then attempt HTML discovery using the same document.
646 //
647 // If successful, returns nonzero and calls parse_xrds_document() to act upon the received data.
648 // If fails, returns 0 and does nothing else.
649 int perform_openid2_discovery(StrBuf *SuppliedURL) {
650         ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
651         int docbytes = (-1);
652         StrBuf *ReplyBuf = NULL;
653         int return_value = 0;
654         CURL *curl;
655         CURLcode result;
656         char errmsg[1024] = "";
657         struct curl_slist *my_headers = NULL;
658         StrBuf *x_xrds_location = NULL;
659
660         if (!SuppliedURL) return(0);
661         syslog(LOG_DEBUG, "openid: perform_openid2_discovery(%s)", ChrPtr(SuppliedURL));
662         if (StrLength(SuppliedURL) == 0) return(0);
663
664         ReplyBuf = NewStrBuf();
665         if (!ReplyBuf) return(0);
666
667         curl = ctdl_openid_curl_easy_init(errmsg);
668         if (!curl) return(0);
669
670         curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL));
671         curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
672         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
673
674         my_headers = curl_slist_append(my_headers, "Accept:");  // disable the default Accept: header
675         my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml");
676         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers);
677
678         curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location);
679         curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction);
680
681         result = curl_easy_perform(curl);
682         if (result) {
683                 syslog(LOG_DEBUG, "openid: libcurl error %d: %s", result, errmsg);
684         }
685         curl_slist_free_all(my_headers);
686         curl_easy_cleanup(curl);
687         docbytes = StrLength(ReplyBuf);
688
689         // The response from the server will be one of:
690         // 
691         // Option 1: An HTML document with a <head> element that includes a <meta> element with http-equiv
692         // attribute, X-XRDS-Location,
693         //
694         // Does any provider actually do this?  If so then we will implement it in the future.
695         //
696         // Option 2: HTTP response-headers that include an X-XRDS-Location response-header,
697         //           together with a document.
698         // Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header,
699         //           a contenttype response-header specifying MIME media type,
700         //           application/xrds+xml, or both.
701         //
702         // If the X-XRDS-Location header was delivered, we know about it at this point...
703         if (    (x_xrds_location)
704                 && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL)))
705         ) {
706                 syslog(LOG_DEBUG, "openid: X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location));
707                 return_value = perform_openid2_discovery(x_xrds_location);
708                 FreeStrBuf(&x_xrds_location);
709         }
710
711         // Option 4: the returned web page may *be* an XRDS document.  Try to parse it.
712         if ( (return_value == 0) && (docbytes >= 0)) {
713                 return_value = parse_xrds_document(ReplyBuf);
714         }
715
716         // Option 5: if all else fails, attempt HTML based discovery.
717         if ( (return_value == 0) && (docbytes >= 0)) {
718                 if (oiddata->op_url == NULL) {
719                         oiddata->op_url = NewStrBuf();
720                 }
721                 extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf);
722                 if (StrLength(oiddata->op_url) > 0) {
723                         return_value = openid_disco_html;
724                 }
725         }
726
727         if (ReplyBuf != NULL) {
728                 FreeStrBuf(&ReplyBuf);
729         }
730         return(return_value);
731 }
732
733
734 // Setup an OpenID authentication
735 void cmd_oids(char *argbuf) {
736         const char *Pos = NULL;
737         StrBuf *ArgBuf = NULL;
738         StrBuf *ReplyBuf = NULL;
739         StrBuf *return_to = NULL;
740         StrBuf *RedirectUrl = NULL;
741         ctdl_openid *oiddata;
742         int discovery_succeeded = 0;
743
744         if (CtdlGetConfigInt("c_disable_newu")) {
745                 cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED);
746                 return;
747         }
748         Free_ctdl_openid ((ctdl_openid**)&CC->openid_data);
749
750         CC->openid_data = oiddata = malloc(sizeof(ctdl_openid));
751         if (oiddata == NULL) {
752                 syslog(LOG_ERR, "openid: malloc() failed: %m");
753                 cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
754                 return;
755         }
756         memset(oiddata, 0, sizeof(ctdl_openid));
757
758         ArgBuf = NewStrBufPlain(argbuf, -1);
759
760         oiddata->verified = 0;
761         oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
762         return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
763
764         StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
765         StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
766
767         syslog(LOG_DEBUG, "openid: user-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
768
769         // ********* OpenID 2.0 section 7.3 - Discovery *********
770
771         // Section 7.3.1 says we have to attempt XRI based discovery.
772         // No one is using this, no one is asking for it, no one wants it.
773         // So we're not even going to bother attempting this mode.
774
775         // Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery);
776         discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id);
777
778         if (discovery_succeeded == 0) {
779                 cprintf("%d There is no OpenID identity provider at this location.\n", ERROR);
780         }
781
782         else {
783                 // If we get to this point we are in possession of a valid OpenID Provider URL.
784                 syslog(LOG_DEBUG, "openid: OP URI '%s' discovered using method %d",
785                         ChrPtr(oiddata->op_url),
786                         discovery_succeeded
787                 );
788
789                 // We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf
790                 if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) {
791                         StrBuf *cid = oiddata->claimed_id;
792                         oiddata->claimed_id = NewStrBufPlain(HKEY("http://"));
793                         StrBufAppendBuf(oiddata->claimed_id, cid, 0);
794                         FreeStrBuf(&cid);
795                 }
796
797                 // OpenID 2.0 section 9: request authentication
798                 // Assemble a URL to which the user-agent will be redirected.
799         
800                 RedirectUrl = NewStrBufDup(oiddata->op_url);
801
802                 StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0);
803                 StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0");
804
805                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0);
806
807                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0);
808                 StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
809
810                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0);
811                 StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
812
813                 // return_to tells the provider how to complete the round trip back to our site
814                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0);
815                 StrBufUrlescAppend(RedirectUrl, return_to, NULL);
816
817                 // Attribute Exchange
818                 // See:
819                 //      http://openid.net/specs/openid-attribute-exchange-1_0.html
820                 //      http://code.google.com/apis/accounts/docs/OpenID.html#endpoint
821                 //      http://test-id.net/OP/AXFetch.aspx
822
823                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0);
824                 StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0");
825
826                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0);
827
828                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0);
829
830                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0);
831                 StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first");
832
833                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0);
834                 StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last");
835
836                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0);
837                 StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly");
838
839                 StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0);
840                 StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname");
841
842                 syslog(LOG_DEBUG, "openid: redirecting client to %s", ChrPtr(RedirectUrl));
843                 cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl));
844         }
845         
846         FreeStrBuf(&ArgBuf);
847         FreeStrBuf(&ReplyBuf);
848         FreeStrBuf(&return_to);
849         FreeStrBuf(&RedirectUrl);
850 }
851
852
853 // Finalize an OpenID authentication
854 void cmd_oidf(char *argbuf) {
855         long len;
856         char buf[2048];
857         char thiskey[1024];
858         char thisdata[1024];
859         HashList *keys = NULL;
860         const char *Key;
861         void *Value;
862         ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
863
864         if (CtdlGetConfigInt("c_disable_newu")) {
865                 cprintf("%d this system does not support openid.\n",
866                         ERROR + CMD_NOT_SUPPORTED);
867                 return;
868         }
869         if (oiddata == NULL) {
870                 cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR);
871                 return;
872         }
873         if (StrLength(oiddata->op_url) == 0){
874                 cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE);
875                 return;
876         }
877         keys = NewHash(1, NULL);
878         if (!keys) {
879                 cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
880                 return;
881         }
882         cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
883
884         while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
885                 len = extract_token(thiskey, buf, 0, '|', sizeof thiskey);
886                 if (len < 0) {
887                         len = sizeof(thiskey) - 1;
888                 }
889                 extract_token(thisdata, buf, 1, '|', sizeof thisdata);
890                 Put(keys, thiskey, len, strdup(thisdata), NULL);
891         }
892
893         // Check to see if this is a correct response.
894         // Start with verified=1 but then set it to 0 if anything looks wrong.
895         oiddata->verified = 1;
896
897         char *openid_ns = NULL;
898         if (    (!GetHash(keys, "ns", 2, (void *) &openid_ns))
899                 || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0"))
900         ) {
901                 syslog(LOG_DEBUG, "openid: this is not an an OpenID assertion");
902                 oiddata->verified = 0;
903         }
904
905         char *openid_mode = NULL;
906         if (    (!GetHash(keys, "mode", 4, (void *) &openid_mode))
907                 || (strcasecmp(openid_mode, "id_res"))
908         ) {
909                 oiddata->verified = 0;
910         }
911
912         char *openid_claimed_id = NULL;
913         if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) {
914                 FreeStrBuf(&oiddata->claimed_id);
915                 oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1);
916                 syslog(LOG_DEBUG, "openid: provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id));
917         }
918
919         // Validate the assertion against the server
920         syslog(LOG_DEBUG, "openid: validating...");
921
922         CURL *curl;
923         CURLcode res;
924         struct curl_httppost *formpost = NULL;
925         struct curl_httppost *lastptr = NULL;
926         char errmsg[1024] = "";
927         StrBuf *ReplyBuf = NewStrBuf();
928
929         curl_formadd(&formpost, &lastptr,
930                 CURLFORM_COPYNAME,      "openid.mode",
931                 CURLFORM_COPYCONTENTS,  "check_authentication",
932                 CURLFORM_END
933         );
934
935         HashPos *HashPos = GetNewHashPos(keys, 0);
936         while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) {
937                 if (strcasecmp(Key, "mode")) {
938                         char k_o_keyname[1024];
939                         snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key);
940                         curl_formadd(&formpost, &lastptr,
941                                 CURLFORM_COPYNAME,      k_o_keyname,
942                                 CURLFORM_COPYCONTENTS,  (char *)Value,
943                                 CURLFORM_END
944                         );
945                 }
946         }
947         DeleteHashPos(&HashPos);
948
949         curl = ctdl_openid_curl_easy_init(errmsg);
950         curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url));
951         curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
952         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
953         curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
954
955         res = curl_easy_perform(curl);
956         if (res) {
957                 syslog(LOG_DEBUG, "openid: cmd_oidf() libcurl error %d: %s", res, errmsg);
958                 oiddata->verified = 0;
959         }
960         curl_easy_cleanup(curl);
961         curl_formfree(formpost);
962
963         if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) {
964                 oiddata->verified = 0;
965         }
966         FreeStrBuf(&ReplyBuf);
967
968         syslog(LOG_DEBUG, "openid: authentication %s", (oiddata->verified ? "succeeded" : "failed") );
969
970         // Respond to the client
971
972         if (oiddata->verified) {
973
974                 // If we were already logged in, attach the OpenID to the user's account
975                 if (CC->logged_in) {
976                         if (attach_extauth(&CC->user, oiddata->claimed_id) == 0) {
977                                 cprintf("attach\n");
978                                 syslog(LOG_DEBUG, "openid: attach succeeded");
979                         }
980                         else {
981                                 cprintf("fail\n");
982                                 syslog(LOG_DEBUG, "openid: attach failed");
983                         }
984                 }
985
986                 // Otherwise, a user is attempting to log in using the verified OpenID
987                 else {
988                         // Existing user who has claimed this OpenID?
989                         //
990                         // Note: if you think that sending the password back over the wire is insecure,
991                         // check your assumptions.  If someone has successfully asserted an OpenID that
992                         // is associated with the account, they already have password equivalency and can
993                         // login, so they could just as easily change the password, etc.
994                         if (login_via_extauth(oiddata->claimed_id) == 0) {
995                                 cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
996                                 logged_in_response();
997                                 syslog(LOG_DEBUG, "openid: logged in using previously claimed OpenID");
998                         }
999
1000                         // If this system does not allow self-service new user registration, the
1001                         // remaining modes do not apply, so fail here and now.
1002                         else if (CtdlGetConfigInt("c_disable_newu")) {
1003                                 cprintf("fail\n");
1004                                 syslog(LOG_DEBUG, "openid: creating user failed due to local policy");
1005                         }
1006
1007                         // New user whose OpenID is verified and Attribute Exchange gave us a name?
1008                         else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) {
1009                                 cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
1010                                 logged_in_response();
1011                                 syslog(LOG_DEBUG, "openid: successfully auto-created new user");
1012                         }
1013
1014                         // OpenID is verified, but the desired username either was not specified or
1015                         // conflicts with an existing user.  Manual account creation is required.
1016                         else {
1017                                 char *desired_name = NULL;
1018                                 cprintf("verify_only\n");
1019                                 cprintf("%s\n", ChrPtr(oiddata->claimed_id));
1020                                 if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) {
1021                                         cprintf("%s\n", desired_name);
1022                                 }
1023                                 else {
1024                                         cprintf("\n");
1025                                 }
1026                                 syslog(LOG_DEBUG, "openid: the desired display name is already taken.");
1027                         }
1028                 }
1029         }
1030         else {
1031                 cprintf("fail\n");
1032         }
1033         cprintf("000\n");
1034
1035         if (oiddata->sreg_keys != NULL) {
1036                 DeleteHash(&oiddata->sreg_keys);
1037                 oiddata->sreg_keys = NULL;
1038         }
1039         oiddata->sreg_keys = keys;
1040 }
1041
1042
1043 // Initialization function, called from modules_init.c
1044 char *ctdl_module_init_openid_rp(void) {
1045         if (!threading) {
1046                 // Only enable the OpenID command set when native mode authentication is in use.
1047                 if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
1048                         CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
1049                         CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
1050                         CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
1051                         CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account");
1052                         CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID");
1053                         CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database");
1054                 }
1055                 CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10);
1056                 CtdlRegisterUserHook(extauth_purge, EVT_PURGEUSER);
1057                 openid_level_supported = 2;     // This module supports OpenID 2.0 only
1058         }
1059
1060         // return our module name for the log
1061         return "openid_rp";
1062 }