4 * This is an implementation of OpenID 1.1 Relying Party support, in stateless mode.
16 #include <sys/types.h>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
32 #include <curl/curl.h>
33 #include "ctdl_module.h"
35 #include "citserver.h"
38 char claimed_id[1024];
47 /**************************************************************************/
49 /* Functions in this section handle Citadel internal OpenID mapping stuff */
51 /**************************************************************************/
55 * Attach an OpenID to a Citadel account
57 int attach_openid(struct ctdluser *who, char *claimed_id)
59 struct cdbdata *cdboi;
62 if (!claimed_id) return(1);
63 if (IsEmptyStr(claimed_id)) return(1);
65 /* Check to see if this OpenID is already in the database */
67 cdboi = cdb_fetch(CDB_OPENID, claimed_id, strlen(claimed_id));
69 if ( (long)*cdboi->ptr == who->usernum ) {
71 CtdlLogPrintf(CTDL_INFO, "%s already associated; no action is taken\n", claimed_id);
76 CtdlLogPrintf(CTDL_INFO, "%s already belongs to another user\n", claimed_id);
81 /* Not already in the database, so attach it now */
83 cdb_store(CDB_OPENID, claimed_id, strlen(claimed_id), &who->usernum, sizeof(long));
84 CtdlLogPrintf(CTDL_INFO, "%s has been associated with %s (%ld)\n",
85 claimed_id, who->fullname, who->usernum);
91 * When a user is being deleted, we have to delete any OpenID associations
93 void openid_purge(struct ctdluser *usbuf) {
94 /* FIXME finish this */
100 * List the OpenIDs associated with the currently logged in account
102 void cmd_oidl(char *argbuf) {
103 struct cdbdata *cdboi;
105 if (CtdlAccessCheck(ac_logged_in)) return;
106 cdb_rewind(CDB_OPENID);
107 cprintf("%d Associated OpenIDs:\n", LISTING_FOLLOWS);
109 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
110 cprintf("FIXME %ld\n", (long)*cdboi->ptr );
118 /**************************************************************************/
120 /* Functions in this section handle OpenID protocol */
122 /**************************************************************************/
126 * Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
128 void extract_link(char *target_buf, int target_size, char *rel, char *source_buf)
130 char *ptr = source_buf;
132 if (!target_buf) return;
134 if (!source_buf) return;
138 while (ptr = bmstrcasestr(ptr, "<link"), ptr != NULL) {
140 char work_buffer[2048];
141 char *link_tag_start = NULL;
142 char *link_tag_end = NULL;
147 link_tag_start = ptr;
148 link_tag_end = strchr(ptr, '>');
152 if ((link_tag_end) && (link_tag_end > link_tag_start)) {
154 len = link_tag_end - link_tag_start;
155 if (len > sizeof work_buffer) len = sizeof work_buffer;
156 memcpy(work_buffer, link_tag_start, len);
158 char *rel_start = NULL;
159 char *rel_end = NULL;
160 rel_start = bmstrcasestr(work_buffer, "rel=");
162 rel_start = strchr(rel_start, '\"');
165 rel_end = strchr(rel_start, '\"');
166 if ((rel_end) && (rel_end > rel_start)) {
167 safestrncpy(rel_tag, rel_start, rel_end - rel_start + 1);
172 char *href_start = NULL;
173 char *href_end = NULL;
174 href_start = bmstrcasestr(work_buffer, "href=");
176 href_start = strchr(href_start, '\"');
179 href_end = strchr(href_start, '\"');
180 if ((href_end) && (href_end > href_start)) {
181 safestrncpy(href_tag, href_start, href_end - href_start + 1);
186 if (!strcasecmp(rel, rel_tag)) {
187 safestrncpy(target_buf, href_tag, target_size);
203 int total_bytes_received;
208 size_t fh_callback(void *ptr, size_t size, size_t nmemb, void *stream)
210 struct fh_data *fh = (struct fh_data *) stream;
211 int got_bytes = (size * nmemb);
213 if (fh->total_bytes_received + got_bytes > fh->maxbytes) {
214 got_bytes = fh->maxbytes - fh->total_bytes_received;
217 memcpy(&fh->buf[fh->total_bytes_received], ptr, got_bytes);
218 fh->total_bytes_received += got_bytes;
221 return (size * nmemb); /* always succeed; libcurl doesn't need to know if we truncated it */
227 * Begin an HTTP fetch (returns number of bytes actually fetched, or -1 for error) using libcurl.
229 * If 'normalize_len' is nonzero, the caller is specifying the buffer size of 'url', and is
230 * requesting that the effective (normalized) URL be copied back to it.
232 int fetch_http(char *url, char *target_buf, int maxbytes, int normalize_len)
236 char errmsg[1024] = "";
237 struct fh_data fh = {
242 char *effective_url = NULL;
244 if (!url) return(-1);
245 if (!target_buf) return(-1);
246 memset(target_buf, 0, maxbytes);
248 curl = curl_easy_init();
250 CtdlLogPrintf(CTDL_ALERT, "Unable to initialize libcurl.\n");
254 curl_easy_setopt(curl, CURLOPT_URL, url);
255 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
256 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
257 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fh);
258 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fh_callback);
259 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
260 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
261 curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
262 if (!IsEmptyStr(config.c_ip_addr)) {
263 curl_easy_setopt(curl, CURLOPT_INTERFACE, config.c_ip_addr);
265 res = curl_easy_perform(curl);
267 CtdlLogPrintf(CTDL_DEBUG, "fetch_http() libcurl error %d: %s\n", res, errmsg);
269 if (normalize_len > 0) {
270 curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
271 safestrncpy(url, effective_url, normalize_len);
273 curl_easy_cleanup(curl);
274 return fh.total_bytes_received;
279 * Setup an OpenID authentication
281 void cmd_oids(char *argbuf) {
282 char return_to[1024];
283 char trust_root[1024];
286 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
287 struct ctdl_openid *oiddata;
289 /* commented out because we may be attempting to attach an OpenID to
290 * an existing account that is logged in
292 if (CCC->logged_in) {
293 cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
298 if (CCC->openid_data != NULL) {
299 free(CCC->openid_data);
301 oiddata = malloc(sizeof(struct ctdl_openid));
302 if (oiddata == NULL) {
303 cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
306 memset(oiddata, 0, sizeof(struct ctdl_openid));
307 CCC->openid_data = (void *) oiddata;
309 extract_token(oiddata->claimed_id, argbuf, 0, '|', sizeof oiddata->claimed_id);
310 extract_token(return_to, argbuf, 1, '|', sizeof return_to);
311 extract_token(trust_root, argbuf, 2, '|', sizeof trust_root);
312 oiddata->validated = 0;
314 i = fetch_http(oiddata->claimed_id, buf, sizeof buf - 1, sizeof oiddata->claimed_id);
315 CtdlLogPrintf(CTDL_DEBUG, "Normalized URL and Claimed ID is: %s\n", oiddata->claimed_id);
316 buf[sizeof buf - 1] = 0;
318 char openid_delegate[1024];
320 extract_link(oiddata->server, sizeof oiddata->server, "openid.server", buf);
321 extract_link(openid_delegate, sizeof openid_delegate, "openid.delegate", buf);
323 if (IsEmptyStr(oiddata->server)) {
324 cprintf("%d There is no OpenID identity provider at this URL.\n", ERROR);
328 /* Empty delegate is legal; we just use the openid_url instead */
329 if (IsEmptyStr(openid_delegate)) {
330 safestrncpy(openid_delegate, oiddata->claimed_id, sizeof openid_delegate);
333 /* Assemble a URL to which the user-agent will be redirected. */
334 char redirect_string[4096];
335 char escaped_identity[512];
336 char escaped_return_to[2048];
337 char escaped_trust_root[1024];
338 char escaped_sreg_optional[256];
340 urlesc(escaped_identity, sizeof escaped_identity, openid_delegate);
341 urlesc(escaped_return_to, sizeof escaped_return_to, return_to);
342 urlesc(escaped_trust_root, sizeof escaped_trust_root, trust_root);
343 urlesc(escaped_sreg_optional, sizeof escaped_sreg_optional,
344 "nickname,email,fullname,postcode,country");
346 snprintf(redirect_string, sizeof redirect_string,
348 "?openid.mode=checkid_setup"
349 "&openid.identity=%s"
350 "&openid.return_to=%s"
351 "&openid.trust_root=%s"
352 "&openid.sreg.optional=%s"
358 escaped_sreg_optional
360 cprintf("%d %s\n", CIT_OK, redirect_string);
364 cprintf("%d Unable to fetch OpenID URL\n", ERROR);
370 * Callback function to free a pointer (used below in the hash list)
372 void free_oid_key(void *ptr) {
378 * Finalize an OpenID authentication
380 void cmd_oidf(char *argbuf) {
384 HashList *keys = NULL;
386 struct ctdl_openid *oiddata = (struct ctdl_openid *) CC->openid_data;
388 keys = NewHash(1, NULL);
390 cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
394 cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
396 while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
397 extract_token(thiskey, buf, 0, '|', sizeof thiskey);
398 extract_token(thisdata, buf, 1, '|', sizeof thisdata);
399 CtdlLogPrintf(CTDL_DEBUG, "%s: [%d] %s\n", thiskey, strlen(thisdata), thisdata);
400 Put(keys, thiskey, strlen(thiskey), strdup(thisdata), free_oid_key);
404 /* Now that we have all of the parameters, we have to validate the signature against the server */
405 CtdlLogPrintf(CTDL_DEBUG, "About to validate the signature...\n");
409 struct curl_httppost *formpost = NULL;
410 struct curl_httppost *lastptr = NULL;
411 char errmsg[1024] = "";
412 char *o_assoc_handle = NULL;
414 char *o_signed = NULL;
415 int num_signed_values;
418 char k_o_keyname[128];
419 char *k_value = NULL;
422 struct fh_data fh = {
428 curl_formadd(&formpost, &lastptr,
429 CURLFORM_COPYNAME, "openid.mode",
430 CURLFORM_COPYCONTENTS, "check_authentication",
432 CtdlLogPrintf(CTDL_DEBUG, "%25s : %s\n", "openid.mode", "check_authentication");
434 if (GetHash(keys, "assoc_handle", 12, (void *) &o_assoc_handle)) {
435 curl_formadd(&formpost, &lastptr,
436 CURLFORM_COPYNAME, "openid.assoc_handle",
437 CURLFORM_COPYCONTENTS, o_assoc_handle,
439 CtdlLogPrintf(CTDL_DEBUG, "%25s : %s\n", "openid.assoc_handle", o_assoc_handle);
442 if (GetHash(keys, "sig", 3, (void *) &o_sig)) {
443 curl_formadd(&formpost, &lastptr,
444 CURLFORM_COPYNAME, "openid.sig",
445 CURLFORM_COPYCONTENTS, o_sig,
447 CtdlLogPrintf(CTDL_DEBUG, "%25s : %s\n", "openid.sig", o_sig);
450 if (GetHash(keys, "signed", 6, (void *) &o_signed)) {
451 curl_formadd(&formpost, &lastptr,
452 CURLFORM_COPYNAME, "openid.signed",
453 CURLFORM_COPYCONTENTS, o_signed,
455 CtdlLogPrintf(CTDL_DEBUG, "%25s : %s\n", "openid.signed", o_signed);
457 num_signed_values = num_tokens(o_signed, ',');
458 for (i=0; i<num_signed_values; ++i) {
459 extract_token(k_keyname, o_signed, i, ',', sizeof k_keyname);
460 if (strcasecmp(k_keyname, "mode")) { // work around phpMyID bug
461 if (GetHash(keys, k_keyname, strlen(k_keyname), (void *) &k_value)) {
462 snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", k_keyname);
463 curl_formadd(&formpost, &lastptr,
464 CURLFORM_COPYNAME, k_o_keyname,
465 CURLFORM_COPYCONTENTS, k_value,
467 CtdlLogPrintf(CTDL_DEBUG, "%25s : %s\n", k_o_keyname, k_value);
470 CtdlLogPrintf(CTDL_INFO, "OpenID: signed field '%s' is missing\n",
477 curl = curl_easy_init();
478 curl_easy_setopt(curl, CURLOPT_URL, oiddata->server);
479 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
480 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
481 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fh);
482 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fh_callback);
483 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
484 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
485 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
486 curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
487 if (!IsEmptyStr(config.c_ip_addr)) {
488 curl_easy_setopt(curl, CURLOPT_INTERFACE, config.c_ip_addr);
491 res = curl_easy_perform(curl);
493 CtdlLogPrintf(CTDL_DEBUG, "cmd_oidf() libcurl error %d: %s\n", res, errmsg);
495 curl_easy_cleanup(curl);
496 curl_formfree(formpost);
498 valbuf[fh.total_bytes_received] = 0;
500 if (bmstrcasestr(valbuf, "is_valid:true")) {
501 oiddata->validated = 1;
504 CtdlLogPrintf(CTDL_DEBUG, "Authentication %s.\n", (oiddata->validated ? "succeeded" : "failed") );
506 /* Respond to the client */
508 if (oiddata->validated) {
510 /* If we were already logged in, attach the OpenID to the user's account */
512 if (attach_openid(&CC->user, oiddata->claimed_id) == 0) {
520 /* Otherwise, a user is attempting to log in using the validated OpenID */
522 cprintf("fail\n"); // FIXME do the login here!!
531 /* Free the hash list */
536 HashPos = GetNewHashPos();
537 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
541 DeleteHashPos(&HashPos);
546 // identity = [50] http://uncensored.citadel.org/~ajc/MyID.config.php
547 // assoc_handle = [26] 6ekac3ju181tgepk7v4h9r7ui7
548 // return_to = [42] http://jemcaterers.net/finish_openid_login
549 // sreg.nickname = [17] IGnatius T Foobar
550 // sreg.email = [26] ajc@uncensored.citadel.org
551 // sreg.fullname = [10] Art Cancro
552 // sreg.postcode = [5] 10549
553 // sreg.country = [2] US
554 // signed = [102] mode,identity,assoc_handle,return_to,sreg.nickname,sreg.email,sreg.fullname,sreg.postcode,sreg.country
555 // sig = [28] vixxxU4MAqWfxxxxCfrHv3TxxxhEw=
560 /**************************************************************************/
562 /* Functions in this section handle module initialization and shutdown */
564 /**************************************************************************/
568 * This cleanup function blows away the temporary memory used by this module.
570 void openid_cleanup_function(void) {
572 if (CC->openid_data != NULL) {
573 free(CC->openid_data);
578 CTDL_MODULE_INIT(openid_rp)
582 curl_global_init(CURL_GLOBAL_ALL);
583 CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
584 CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
585 CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
586 CtdlRegisterSessionHook(openid_cleanup_function, EVT_STOP);
587 CtdlRegisterUserHook(openid_purge, EVT_PURGEUSER);
590 /* return our Subversion id for the Log */
595 /* FIXME ... we have to add the new openid database to serv_vandelay.c */