diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ce9e19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.libs +*.new +*.orig +my-pull.log +update-vh1.sh +*~ +*.lo +*.slo +*.la +t +tt +ttt +tttt diff --git a/Makefile b/Makefile index 38d913b..6ecaddf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,14 @@ CC=gcc + +# if user has not defined the apxs path, try to set +# it here ifeq ($(APXS_PATH),) -APXS_PATH=/usr/sbin/apxs + APXS_PATH := $(shell which apxs) +endif + +# check again, abort on error +ifeq ($(APXS_PATH),) + $(error Cannot find Apache utility program 'apxs') endif MY_LDFLAGS=-lcurl -lyajl @@ -8,9 +16,12 @@ MY_LDFLAGS=-lcurl -lyajl # Note that gcc flags are passed through apxs, so preface with -Wc MY_CFLAGS=-Wc,-I. -Wc,-Wall +# note apsx adds "_module" to the name +MODULE_NAME := auth_browserid + .SUFFIXES: .c .o .la .c.la: - $(APXS_PATH) $(MY_LDFLAGS) $(MY_CFLAGS) -c $< + $(APXS_PATH) $(MY_LDFLAGS) $(MY_CFLAGS) -c $< -n $(MODULE_NAME) .c.o: $(CC) -c $< @@ -18,7 +29,7 @@ all: mod_auth_browserid.la install: mod_auth_browserid.la @echo "-"$*"-" "-"$?"-" "-"$%"-" "-"$@"-" "-"$<"-" - $(MY_APXS) -i $? + $(APXS_PATH) -i -n $(MODULE_NAME) -a $? clean: -rm -f *.o *.lo *.la *.slo diff --git a/README.md b/README.md index 01f7d53..0919926 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -mod_browserid is a module for Apache 2.0 or later that implements Apache authentication for the BrowserID protocol. +mod_browserid is a module for Apache 2.0 or later that implements Apache authentication for the BrowserID protocol. Building and Installing ======================= @@ -57,7 +57,7 @@ Once authentication is set up, the "require" directive can be used with one of t * `require valid-user`: a valid BrowserID identity must have been presented * `require user `: a specific identity must be presented * `require userfile `: the BrowserID presented by the user must be the newline-separated list of identities found in this file - + NOT YET IMPLEMENTED ------------------- @@ -80,31 +80,35 @@ httpd.conf: LoadModule mod_auth_browserid_module modules/mod_auth_browserid.so - AuthBrowserIDCookieName myauthcookie - AuthBrowserIDSubmitPath "/id_login/submit" - AuthBrowserIDVerificationServerURL "https://browserid.org/verify" + AuthBrowserIDCookieName myauthcookie + AuthBrowserIDSubmitPath "/id_login/submit" + AuthBrowserIDVerificationServerURL "https://browserid.org/verify" - AuthType BrowserID - AuthBrowserIDAuthoritative on - AuthBrowserIDCookieName myauthcookie - AuthBrowserIDVerificationServerURL "https://browserid.org/verify" - - # must be set (apache mandatory) but not used by the module - AuthName "My Login" - - # to redirect unauthorized users to the login page - ErrorDocument 401 "/id_login/browserid_login.php" - - require userfile /usr/local/apache2/htdocs/id_demo_users + AuthType BrowserID + AuthBrowserIDAuthoritative on + AuthBrowserIDCookieName myauthcookie + AuthBrowserIDSecret aaz5R2w42^24A3uM&75Z822M79xQ82 + AuthBrowserIDVerificationServerURL "https://browserid.org/verify" + + # must be set (apache mandatory) but not used by the module + AuthName "My Login" + + # to redirect unauthorized users to the login page + ErrorDocument 401 "/id_login/browserid_login.php" + + # file with authorized user names (e-mail addresses) + Require userfile /usr/local/apache2/htdocs/id_demo_users + ``` /id_login/browserid_login.php: ``` - + Authentication @@ -126,10 +130,11 @@ httpd.conf: }); } + ?> ``` /usr/local/apache2/htdocs/id_demo_users: ``` user@site.com otheruser@site.com -``` \ No newline at end of file +``` diff --git a/TODO b/TODO new file mode 100644 index 0000000..1245edb --- /dev/null +++ b/TODO @@ -0,0 +1,81 @@ +query Mike and Dan about intent of "forwardedRequestHeader"--it appears not to be used + +query about Apache use of flags: is there a macro? + +E-mail to dev-identity list on 28 Mar +(https://groups.google.com/forum/?fromgroups#!topic/mozilla.dev.identity/g9yCsiIV_Hs) +===================================================================================== + +This morning I took a closer look at my running server using +mod_browserid, the configuration, and the module source code. +Following are some observations: + +Log errors: +======= + +[Wed Mar 28 01:04:56 2012] [error] [client 92.194.45.48] +Auth_browserID: ap_hook_check_user_id in - Auth_browserid_check_cookie +[Wed Mar 28 01:04:56 2012] [error] [client 92.194.45.48] PHP Notice: +Use of undefined constant php - assumed 'php' in +/home/tbrowde/public_html/mygnus.com/public/id_login/browserid_login.php +on line 1 +[Wed Mar 28 01:05:24 2012] [error] [client 92.194.45.48] +Auth_browserID: ap_hook_check_user_id in - +Auth_browserid_check_cookie, referer: https://mygnus.com/ + +Possible problems with my test setup: +=========================== + ++ directive "AuthBrowserIDAuthoritative" shows "on" in example, but +code says set "yes" or "no" (default) + ++ function "create_browserid_config" doesn't explicitly set all +variables to default values + +Some suggestions: +============= + +2. allow user to establish default max credential life for each sign in + +3. allow mod_browserid an option (directive) to adjust max credential +life for a sign in + +4. explain all directives in README (particularly "authBasicFix"); how +do they affect the example setup (two directories used two different +ways)? + +E-mail to dev-identity list of 29 Mar: +===================================== + ++ fix or document reasons for observed Apache log errors + ++ incorporate other observations in previous e-mail to dev-identity +list on 28 Mar [see above] + ++ add capability to use a dbm for authorized user e-mail addresses + ++ more examples + ++ add bits to docs (e.g., ensure all user options and inputs are documented) + +#========= COMPLETED ===================== + +1. module name for Apache2: [DONE] + +current: + +/* apache module name */ +module AP_MODULE_DECLARE_DATA mod_auth_browserid_module; + +suggested: +auth_browserid_module; + +Rationale: More like other module names in existing set shipping with Apache2. + +Example of a current one: +LoadModule auth_digest_module /usr/lib/apache2/modules/mod_auth_digest.so + ++ code cleanup [mostly DONE ongoing] + - minor typos + - rearrange some functions for easier maintenance (e.g., keep those + modifying struct BrowserIDConfigRec closer to it) diff --git a/TOMS-NOTES b/TOMS-NOTES new file mode 100644 index 0000000..884e1cb --- /dev/null +++ b/TOMS-NOTES @@ -0,0 +1,31 @@ +Base dbd use on standard module mod_authn_dbd. + +git hints for local repo clone: + +$ git remote -v +origin ssh://git@github.com/tbrowder/mod_browserid.git (fetch) +origin ssh://git@github.com/tbrowder/mod_browserid.git (push) +upstream git@github.com:mozilla/mod_browserid.git (fetch) +upstream git@github.com:mozilla/mod_browserid.git (push) + + +# update my repo on git hub from the local repo: +$ git push origin master # alias: gip + +# update my local repo from mozilla repo +$ git fetch upstream # alias giu or gif + +From Mozilla's Mike Hanson: +========================== + +If this is new territory for you, check out +https://help.github.com/articles/using-pull-requests - it's a good +explanation of how these things work. + +If you're working on an actual clone of mozilla/modbrowserid, you may +want to back up a step, fork to your own account, and commit your +changes on that repo. Then the github.com "pull request" button does +everything you want, including a nice diff view, comment system, etc. + +[cool--it works] + diff --git a/astyle.opt b/astyle.opt new file mode 100644 index 0000000..676587a --- /dev/null +++ b/astyle.opt @@ -0,0 +1,35 @@ +# Use k&r styling +style=k&r + +# set standard spacing +indent=spaces=4 + +# set pointer format to existing predominat style +align-pointer=name + +# Indent goto labels rather than making them flush on the left +indent-labels + +# no cuddled else's! +break-closing-brackets + +# use brackets for one-line conditionals +add-brackets + +# Indent switch cases +indent-switches + +# We don't want to add additional indentation to conditionals +min-conditional-indent=0 + +# Allow deeper indenting of statements +max-instatement-indent=79 + +# Pad operators +pad-oper + +# Tighten parenthesis (unless overridden by other options such as pad-header) +unpad-paren + +# Keep the space between if, while, etc. and the first paren +pad-header diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..bd1f520 --- /dev/null +++ b/format.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -z $1 ] ; then + echo "Usage: $0 " + exit +fi + +astyle --options=astyle.opt $1 diff --git a/mod_auth_browserid.c b/mod_auth_browserid.c index ae254d4..4176076 100644 --- a/mod_auth_browserid.c +++ b/mod_auth_browserid.c @@ -21,6 +21,10 @@ * * SHA-1 implementation by Steve Reid, steve@edmweb.com, in * public domain. + * + * Database usage is based on the Apache2 standard module + * "mod_authn_dbd" with directives renamed for this module. + * */ #include @@ -30,6 +34,7 @@ #include "apr_strings.h" #include "apr_uuid.h" #include "apr_tables.h" +#include "apr_dbd.h" /* for SQL DB use */ #include "httpd.h" #include "http_config.h" @@ -56,9 +61,9 @@ typedef struct { } SHA1_CTX; void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]); -void SHA1Init(SHA1_CTX* context); -void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len); -void SHA1Final(unsigned char digest[20], SHA1_CTX* context); +void SHA1Init(SHA1_CTX *context); +void SHA1Update(SHA1_CTX *context, const unsigned char *data, u_int32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX *context); /* ================ sha1.c ================ */ @@ -99,13 +104,13 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context); void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]) { -u_int32_t a, b, c, d, e; -typedef union { - unsigned char c[64]; - u_int32_t l[16]; -} CHAR64LONG16; + u_int32_t a, b, c, d, e; + typedef union { + unsigned char c[64]; + u_int32_t l[16]; + } CHAR64LONG16; #ifdef SHA1HANDSOFF -CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ memcpy(block, buffer, 64); #else /* The following had better never be used because it causes the @@ -113,7 +118,7 @@ CHAR64LONG16 block[1]; /* use array to appear as a pointer */ * And the result is written through. I threw a "const" in, hoping * this will cause a diagnostic. */ -CHAR64LONG16* block = (const CHAR64LONG16*)buffer; + CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer; #endif /* Copy context->state[] to working vars */ a = state[0]; @@ -122,26 +127,86 @@ CHAR64LONG16* block = (const CHAR64LONG16*)buffer; d = state[3]; e = state[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ - R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); - R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); - R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); - R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); - R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); - R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); - R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); - R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); - R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); - R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); - R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); - R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); - R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); - R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); - R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); - R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); - R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); - R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); - R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); - R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); /* Add the working vars back into context.state[] */ state[0] += a; state[1] += b; @@ -158,7 +223,7 @@ CHAR64LONG16* block = (const CHAR64LONG16*)buffer; /* SHA1Init - Initialize new context */ -void SHA1Init(SHA1_CTX* context) +void SHA1Init(SHA1_CTX *context) { /* SHA1 initialization constants */ context->state[0] = 0x67452301; @@ -172,36 +237,39 @@ void SHA1Init(SHA1_CTX* context) /* Run your data through this. */ -void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len) +void SHA1Update(SHA1_CTX *context, const unsigned char *data, u_int32_t len) { -u_int32_t i; -u_int32_t j; + u_int32_t i; + u_int32_t j; j = context->count[0]; - if ((context->count[0] += len << 3) < j) - context->count[1]++; - context->count[1] += (len>>29); + if ((context->count[0] += len << 3) < j) { + context->count[1]++; + } + context->count[1] += (len >> 29); j = (j >> 3) & 63; if ((j + len) > 63) { - memcpy(&context->buffer[j], data, (i = 64-j)); + memcpy(&context->buffer[j], data, (i = 64 - j)); SHA1Transform(context->state, context->buffer); - for ( ; i + 63 < len; i += 64) { + for (; i + 63 < len; i += 64) { SHA1Transform(context->state, &data[i]); } j = 0; } - else i = 0; + else { + i = 0; + } memcpy(&context->buffer[j], &data[i], len - i); } /* Add padding and return the message digest. */ -void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +void SHA1Final(unsigned char digest[20], SHA1_CTX *context) { -unsigned i; -unsigned char finalcount[8]; -unsigned char c; + unsigned i; + unsigned char finalcount[8]; + unsigned char c; #if 0 /* untested "improvement" by DHR */ /* Convert context->count to a sequence of bytes @@ -211,30 +279,30 @@ unsigned char c; */ unsigned char *fcp = &finalcount[8]; - for (i = 0; i < 2; i++) - { - u_int32_t t = context->count[i]; - int j; + for (i = 0; i < 2; i++) { + u_int32_t t = context->count[i]; + int j; - for (j = 0; j < 4; t >>= 8, j++) - *--fcp = (unsigned char) t - } + for (j = 0; j < 4; t >>= 8, j++) { + *--fcp = (unsigned char) t + } + } #else for (i = 0; i < 8; i++) { finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] - >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ } #endif - c = 0200; + c = 0200; SHA1Update(context, &c, 1); while ((context->count[0] & 504) != 448) { - c = 0000; + c = 0000; SHA1Update(context, &c, 1); } SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ for (i = 0; i < 20; i++) { digest[i] = (unsigned char) - ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + ((context->state[i>>2] >> ((3 - (i & 3)) * 8)) & 255); } /* Wipe variables */ memset(context, '\0', sizeof(*context)); @@ -245,59 +313,169 @@ unsigned char c; #define ERRTAG "Auth_browserID: " #define VERSION "1.0.0" -#define unless(c) if(!(c)) - -/* apache module name */ -module AP_MODULE_DECLARE_DATA mod_auth_browserid_module; +#define UNLESS(c) if(!(c)) /* config structure */ typedef struct { - char *cookieName; - int authoritative; - int authBasicFix; - char *forwardedRequestHeader; - char *submitPath; - char *logoutPath; - char *verificationServerURL; - int verifyLocally; - char *serverSecret; + int authBasicFix; + int authoritative; + char *cookieName; + char *forwardedRequestHeader; + char *logoutPath; + char *serverSecret; + char *submitPath; + char *verificationServerURL; + int verifyLocally; } BrowserIDConfigRec; +/* apache config function of the module */ +static const command_rec Auth_browserid_cmds[] = { + + /* from http_config.h: AP_INIT_FLAG This configuration directive + takes a flag (on/off) [the flag is an int, probably a macro] as a argument */ + AP_INIT_FLAG("AuthBrowserIDSimulateAuthBasic", ap_set_flag_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, authBasicFix), + OR_AUTHCFG, "Set to 'yes' to enable creation of a synthetic Basic Authorization header containing the username."), + + AP_INIT_FLAG("AuthBrowserIDAuthoritative", ap_set_flag_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, authoritative), + OR_AUTHCFG, "Set to 'yes' to allow access control to be passed along to lower modules; set to 'no' by default."), + + /* from http_config.h: AP_INIT_TAKE1 This configuration directive takes 1 argument */ + AP_INIT_TAKE1("AuthBrowserIDCookieName", ap_set_string_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, cookieName), + OR_AUTHCFG, "Name of cookie to set."), + + /* TB: doesn't appear to be used at the moment, looks like it could be a flag instead */ + AP_INIT_TAKE1("AuthBrowserIDSetHTTPHeader", ap_set_string_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, forwardedRequestHeader), + OR_AUTHCFG, "Set to 'yes' to forward a signed HTTP header containing the verified identity; set to 'no' by default."), + + AP_INIT_TAKE1("AuthBrowserIDLogoutPath", ap_set_string_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, logoutPath), + OR_AUTHCFG, "Path to which logout requests will be submitted. An optional 'returnto' parameter will be used for a redirection, if provided."), + + AP_INIT_TAKE1("AuthBrowserIDSecret", ap_set_string_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, serverSecret), + OR_AUTHCFG, "Server secret for authentication cookie."), + + AP_INIT_TAKE1("AuthBrowserIDSubmitPath", ap_set_string_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, submitPath), + OR_AUTHCFG, "Path to which login forms will be submitted. Form must contain a field named 'assertion'."), + + AP_INIT_TAKE1("AuthBrowserIDVerificationServerURL", ap_set_string_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, verificationServerURL), + OR_AUTHCFG, "URL of the BrowserID verification server."), + + AP_INIT_FLAG("AuthBrowserIDVerifyLocally", ap_set_flag_slot, + (void *)APR_OFFSETOF(BrowserIDConfigRec, verifyLocally), + OR_AUTHCFG, "Set to 'yes' to verify assertions locally; ignored if VerificationServerURL is set."), + + {NULL} +}; + +/* local function declarations */ +static int Auth_browserid_check_auth(request_rec *r); +static int Auth_browserid_check_cookie(request_rec *r); +static int Auth_browserid_fixups(request_rec *r); +static void *create_browserid_config(apr_pool_t *p, char *d); +static void createSessionCookie(request_rec *r, BrowserIDConfigRec *conf, char *identity); +static char *extract_cookie(request_rec *r, const char *szCookie_name); +static void fix_headers_in(request_rec *r, char *szPassword); +static char *generateSignature(request_rec *r, BrowserIDConfigRec *conf, char *userAddress); +apr_table_t *parseArgs(request_rec *r, char *argStr); +static int processAssertionFormSubmit(request_rec *r, BrowserIDConfigRec *conf); +static int processLogout(request_rec *r, BrowserIDConfigRec *conf); +static void register_hooks(apr_pool_t *p); +static int user_in_file(request_rec *r, char *username, char *filename); +static int validateCookie(request_rec *r, BrowserIDConfigRec *conf, char *szCookieValue); +static char *verifyAssertionRemote(request_rec *r, BrowserIDConfigRec *conf, char *assertionText); + +/* apache module name (used in LoadModule) */ +module AP_MODULE_DECLARE_DATA auth_browserid_module; + +/* apache module structure */ +module AP_MODULE_DECLARE_DATA auth_browserid_module = { + STANDARD20_MODULE_STUFF, + create_browserid_config, /* dir config creator */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + Auth_browserid_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; + + +/************************************************************************************ + * Apache CONFIG Phase: + ************************************************************************************/ +static void *create_browserid_config(apr_pool_t *p, char *d) +{ + BrowserIDConfigRec *conf = apr_palloc(p, sizeof(*conf)); + + conf->authBasicFix = 0; /* do not fix header for php auth by default */ + conf->authoritative = 0; /* not by default */ + conf->cookieName = apr_pstrdup(p, "BrowserID"); + conf->forwardedRequestHeader = NULL; /* pass the authenticated user, signed, as an HTTP header */ + conf->logoutPath = NULL; + conf->serverSecret = "BrowserIDSecret"; + conf->submitPath = "/mod_browserid_submit"; + conf->verificationServerURL = NULL; + conf->verifyLocally = 0; + return conf; +} + /* Look through the 'Cookie' headers for the indicated cookie; extract it * and URL-unescape it. Return the cookie on success, NULL on failure. */ -static char * extract_cookie(request_rec *r, const char *szCookie_name) +static char *extract_cookie(request_rec *r, const char *szCookie_name) { - char *szRaw_cookie_start=NULL, *szRaw_cookie_end; - char *szCookie; - /* get cookie string */ - char*szRaw_cookie = (char*)apr_table_get( r->headers_in, "Cookie"); - unless(szRaw_cookie) return 0; + char *szRaw_cookie_start = NULL, *szRaw_cookie_end; + char *szCookie; + /* get cookie string */ + char *szRaw_cookie = (char *)apr_table_get(r->headers_in, "Cookie"); + if (!szRaw_cookie) { + return 0; + } + + /* loop to search cookie name in cookie header */ + do { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Checking cookie %s, looking for %s", szRaw_cookie, szCookie_name); - /* loop to search cookie name in cookie header */ - do { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, ERRTAG "Checking cookie %s, looking for %s", szRaw_cookie, szCookie_name); + /* search cookie name in cookie string */ + if (!(szRaw_cookie = strstr(szRaw_cookie, szCookie_name))) { + return 0; + } + szRaw_cookie_start = szRaw_cookie; + /* search '=' */ + if (!(szRaw_cookie = strchr(szRaw_cookie, '='))) { + return 0; + } + } + while (strncmp(szCookie_name, szRaw_cookie_start, szRaw_cookie - szRaw_cookie_start) != 0); - /* search cookie name in cookie string */ - unless (szRaw_cookie =strstr(szRaw_cookie, szCookie_name)) return 0; - szRaw_cookie_start=szRaw_cookie; - /* search '=' */ - unless (szRaw_cookie = strchr(szRaw_cookie, '=')) return 0; - } while (strncmp(szCookie_name,szRaw_cookie_start,szRaw_cookie-szRaw_cookie_start)!=0); + /* skip '=' */ + szRaw_cookie++; - /* skip '=' */ - szRaw_cookie++; + /* search end of cookie name value: ';' or end of cookie strings */ + if (!((szRaw_cookie_end = strchr(szRaw_cookie, ';')) || (szRaw_cookie_end = strchr(szRaw_cookie, '\0')))) { + return 0; + } - /* search end of cookie name value: ';' or end of cookie strings */ - unless ((szRaw_cookie_end = strchr(szRaw_cookie, ';')) || (szRaw_cookie_end = strchr(szRaw_cookie, '\0'))) return 0; + /* dup the value string found in apache pool and set the result pool ptr to szCookie ptr */ + if (!(szCookie = apr_pstrndup(r->pool, szRaw_cookie, szRaw_cookie_end - szRaw_cookie))) { + return 0; + } - /* dup the value string found in apache pool and set the result pool ptr to szCookie ptr */ - unless (szCookie = apr_pstrndup(r->pool, szRaw_cookie, szRaw_cookie_end-szRaw_cookie)) return 0; - /* unescape the value string */ - unless (ap_unescape_url(szCookie) == 0) return 0; + /* unescape the value string */ + if (!(ap_unescape_url(szCookie) == 0)) { + return 0; + } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, ERRTAG "finished cookie scan, returning %s", szCookie); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, + ERRTAG "finished cookie scan, returning %s", szCookie); - return szCookie; + return szCookie; } /** Given a filename and username, open the file (using normal Apache @@ -305,72 +483,76 @@ static char * extract_cookie(request_rec *r, const char *szCookie_name) * in it (as a newline-seaparated list) */ static int user_in_file(request_rec *r, char *username, char *filename) { - apr_status_t status; - char l[MAX_STRING_LEN]; - ap_configfile_t *f; - status = ap_pcfg_openfile(&f, r->pool, filename); - if (status != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, - "Could not open user file: %s", filename); - return 0; - } - - int found = 0; - while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { - - /* Skip # or blank lines. */ - if ((l[0] == '#') || (!l[0])) { - continue; - } - - if (!strcmp(username, l)) { - found = 1; - break; - } - } - ap_cfg_closefile(f); - return found; -} + apr_status_t status; + char l[MAX_STRING_LEN]; + ap_configfile_t *f; + status = ap_pcfg_openfile(&f, r->pool, filename); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, + "Could not open user file: %s", filename); + return 0; + } + + int found = 0; + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + /* Skip # or blank lines. */ + if ((l[0] == '#') || (!l[0])) { + continue; + } + + if (!strcmp(username, l)) { + found = 1; + break; + } + } + ap_cfg_closefile(f); + return found; +} /* function to fix any headers in the input request that may be relied on by an application. e.g. php uses the Authorization header when logging the request in apache and not r->user (like it ought to). It is applied after the request has been authenticated. */ -static void fix_headers_in(request_rec *r,char*szPassword) +static void fix_headers_in(request_rec *r, char *szPassword) { - char *szUser=NULL; - /* Set an Authorization header in the input request table for php and - other applications that use it to obtain the username (mainly to fix - apache logging of php scripts). We only set this if there is no header - already present. */ - - if (apr_table_get(r->headers_in,"Authorization")==NULL) - { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, ERRTAG "fixing apache Authorization header for this request using user: %s",r->user); - - /* concat username and ':' */ - if (szPassword!=NULL) szUser=(char*)apr_pstrcat(r->pool,r->user,":",szPassword,NULL); - else szUser=(char*)apr_pstrcat(r->pool,r->user,":",NULL); + char *szUser = NULL; + /* Set an Authorization header in the input request table for php + and other applications that use it to obtain the username + (mainly to fix apache logging of php scripts). We only set this + if there is no header already present. */ + + if (apr_table_get(r->headers_in, "Authorization") == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "fixing apache Authorization header for this request using user: %s", r->user); + + /* concat username and ':' */ + if (szPassword != NULL) { + szUser = (char *)apr_pstrcat(r->pool, r->user, ":", szPassword, NULL); + } + else { + szUser = (char *)apr_pstrcat(r->pool, r->user, ":", NULL); + } - /* alloc memory for the estimated encode size of the username */ - char *szB64_enc_user=(char*)apr_palloc(r->pool,apr_base64_encode_len(strlen(szUser))+1); - unless (szB64_enc_user) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "memory alloc failed!"); - return; - } + /* alloc memory for the estimated encode size of the username */ + char *szB64_enc_user = (char *)apr_palloc(r->pool, apr_base64_encode_len(strlen(szUser)) + 1); + if (!szB64_enc_user) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "memory alloc failed!"); + return; + } - /* encode username in base64 format */ - apr_base64_encode(szB64_enc_user,szUser,strlen(szUser)); + /* encode username in base64 format */ + apr_base64_encode(szB64_enc_user, szUser, strlen(szUser)); - /* set authorization header */ - apr_table_set(r->headers_in,"Authorization", (char*)apr_pstrcat(r->pool,"Basic ",szB64_enc_user,NULL)); + /* set authorization header */ + apr_table_set(r->headers_in, "Authorization", (char *)apr_pstrcat(r->pool, "Basic ", szB64_enc_user, NULL)); - /* force auth type to basic */ - r->ap_auth_type=apr_pstrdup(r->pool,"Basic"); - } + /* force auth type to basic */ + r->ap_auth_type = apr_pstrdup(r->pool, "Basic"); + } - return; + return; } /** Generates a signature with the given inputs, returning a Base64-encoded @@ -379,13 +561,13 @@ static char *generateSignature(request_rec *r, BrowserIDConfigRec *conf, char *u { SHA1_CTX context; SHA1Init(&context); - SHA1Update(&context, (unsigned char*)userAddress, strlen(userAddress)); - SHA1Update(&context, (unsigned char*)conf->serverSecret, strlen(conf->serverSecret)); + SHA1Update(&context, (unsigned char *)userAddress, strlen(userAddress)); + SHA1Update(&context, (unsigned char *)conf->serverSecret, strlen(conf->serverSecret)); unsigned char digest[20]; SHA1Final(digest, &context); - + char *digest64 = apr_palloc(r->pool, apr_base64_encode_len(20)); - apr_base64_encode(digest64, (char*)digest, 20); + apr_base64_encode(digest64, (char *)digest, 20); return digest64; } @@ -396,23 +578,25 @@ static int validateCookie(request_rec *r, BrowserIDConfigRec *conf, char *szCook char *sig = NULL; char *addr = apr_strtok(szCookieValue, "|", &sig); if (!addr) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "malformed BrowserID cookie"); - return 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "malformed BrowserID cookie"); + return 1; } char *digest64 = generateSignature(r, conf, addr); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, ERRTAG "Got cookie: email is %s; expected digest is %s; got digest %s", - addr, digest64, sig); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Got cookie: email is %s; expected digest is %s; got digest %s", + addr, digest64, sig); /* paranoia indicates that we should use a time-invariant compare here */ if (strcmp(digest64, sig)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "invalid BrowserID cookie"); - free(digest64); - return 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG "invalid BrowserID cookie"); + free(digest64); + return 1; } - + /* Cookie is good: set r->user */ - r->user = (char*)addr; + r->user = (char *)addr; return 0; } @@ -423,51 +607,62 @@ static int validateCookie(request_rec *r, BrowserIDConfigRec *conf, char *szCook **************************************************/ static int Auth_browserid_check_cookie(request_rec *r) { - BrowserIDConfigRec *conf=NULL; - char *szCookieValue=NULL; - char *szRemoteIP=NULL; + BrowserIDConfigRec *conf = NULL; + char *szCookieValue = NULL; + char *szRemoteIP = NULL; - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG "ap_hook_check_user_id in - Auth_browserid_check_cookie"); + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "ap_hook_check_user_id in - Auth_browserid_check_cookie"); /* get apache config */ - conf = ap_get_module_config(r->per_dir_config, &mod_auth_browserid_module); + conf = ap_get_module_config(r->per_dir_config, &auth_browserid_module); - unless(conf->authoritative) - return DECLINED; + if (!conf->authoritative) { + return DECLINED; + } - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "AuthType are '%s'", ap_auth_type(r)); - unless(strncmp("BrowserID",ap_auth_type(r),9)==0) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "Auth type must be 'BrowserID'"); - return HTTP_UNAUTHORIZED; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "AuthType are '%s'", ap_auth_type(r)); + + if (!(strncmp("BrowserID", ap_auth_type(r), 9) == 0)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Auth type must be 'BrowserID'"); + return HTTP_UNAUTHORIZED; } - unless(conf->cookieName) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "No Auth_browserid_CookieName specified"); - return HTTP_UNAUTHORIZED; + if (!conf->cookieName) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "No Auth_browserid_CookieName specified"); + return HTTP_UNAUTHORIZED; } /* get cookie who are named cookieName */ - unless(szCookieValue = extract_cookie(r, conf->cookieName)) - { - ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, r, ERRTAG "BrowserID cookie not found; not authorized! RemoteIP:%s",szRemoteIP); - return HTTP_UNAUTHORIZED; + if (!(szCookieValue = extract_cookie(r, conf->cookieName))) { + ap_log_rerror(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, r, ERRTAG + "BrowserID cookie not found; not authorized! RemoteIP:%s", szRemoteIP); + return HTTP_UNAUTHORIZED; } - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "got cookie; value is %s", szCookieValue); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "got cookie; value is %s", szCookieValue); /* Check cookie validity */ if (validateCookie(r, conf, szCookieValue)) { - ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, ERRTAG "Invalid BrowserID cookie: %s", szCookieValue); + ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, ERRTAG + "Invalid BrowserID cookie: %s", szCookieValue); return HTTP_UNAUTHORIZED; } /* set REMOTE_USER var for scripts language */ - apr_table_setn(r->subprocess_env,"REMOTE_USER",r->user); + apr_table_setn(r->subprocess_env, "REMOTE_USER", r->user); - /* log authorisation ok */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, ERRTAG "BrowserID authentication ok"); + /* log authorization ok */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "BrowserID authentication ok"); /* fix http header for php */ - if (conf->authBasicFix) fix_headers_in(r,"browserid"); + if (conf->authBasicFix) { + fix_headers_in(r, "browserid"); + } /* if all is ok return auth ok */ return OK; @@ -483,188 +678,202 @@ static int Auth_browserid_check_cookie(request_rec *r) **************************************************/ static int Auth_browserid_check_auth(request_rec *r) { - BrowserIDConfigRec *conf=NULL; - char *szUser; - const apr_array_header_t *reqs_arr=NULL; - require_line *reqs=NULL; - register int x; - const char *szRequireLine; - char *szFileName; - char *szRequire_cmd; - - /* get apache config */ - conf = ap_get_module_config(r->per_dir_config, &mod_auth_browserid_module); - - /* check if this module is authoritative */ - unless(conf->authoritative) - return DECLINED; + BrowserIDConfigRec *conf = NULL; + char *szUser = NULL; + const apr_array_header_t *reqs_arr = NULL; + require_line *reqs = NULL; + register int x = 0; + const char *szRequireLine = NULL; + char *szFileName = NULL; + char *szRequire_cmd = NULL; - /* get require line */ - reqs_arr = ap_requires(r); - reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL; + /* get apache config */ + conf = ap_get_module_config(r->per_dir_config, &auth_browserid_module); - /* decline if no require line found */ - if (!reqs_arr) return DECLINED; + /* check if this module is authoritative */ + if (!conf->authoritative) { + return DECLINED; + } - /* walk through the array to check each require command */ - for (x = 0; x < reqs_arr->nelts; x++) { + /* get require line */ + reqs_arr = ap_requires(r); + reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL; - if (!(reqs[x].method_mask & (AP_METHOD_BIT << r->method_number))) - continue; + /* decline if no require line found */ + if (!reqs_arr) { + return DECLINED; + } - /* get require line */ - szRequireLine = reqs[x].requirement; - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "Require Line is '%s'", szRequireLine); - - /* get the first word in require line */ - szRequire_cmd = ap_getword_white(r->pool, &szRequireLine); - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "Require Cmd is '%s'", szRequire_cmd); - - /* if require cmd are valid-user, they are already authenticated than allow and return OK */ - if (!strcmp("valid-user",szRequire_cmd)) { - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "Require Cmd valid-user"); - return OK; - } - /* check the required user */ - else if (!strcmp("user",szRequire_cmd)) { - szUser = ap_getword_conf(r->pool, &szRequireLine); - if (strcmp(r->user, szUser)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r ,ERRTAG "user '%s' is not the required user '%s'",r->user, szUser); - return HTTP_FORBIDDEN; - } - ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, r ,ERRTAG "user '%s' is authorized",r->user); - return OK; - } - /* check for users in a file */ - else if (!strcmp("userfile",szRequire_cmd)) { - szFileName = ap_getword_conf(r->pool, &szRequireLine); - if (!user_in_file(r, r->user, szFileName)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r ,ERRTAG "user '%s' is not in username list at '%s'",r->user,szFileName); - return HTTP_FORBIDDEN; - } else { - return OK; - } - } - } - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r ,ERRTAG "user '%s' is not authorized",r->user); - /* forbid by default */ - return HTTP_FORBIDDEN; -} + /* walk through the array to check each require command */ + for (x = 0; x < reqs_arr->nelts; x++) { + + if (!(reqs[x].method_mask & (AP_METHOD_BIT << r->method_number))) { + continue; + } + /* get require line */ + szRequireLine = reqs[x].requirement; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Require Line is '%s'", szRequireLine); + + /* get the first word in require line */ + szRequire_cmd = ap_getword_white(r->pool, &szRequireLine); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Require Cmd is '%s'", szRequire_cmd); + + /* if require cmd are valid-user, they are already authenticated than allow and return OK */ + if (!strcmp("valid-user", szRequire_cmd)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Require Cmd valid-user"); + return OK; + } + /* check the required user */ + else if (!strcmp("user", szRequire_cmd)) { + szUser = ap_getword_conf(r->pool, &szRequireLine); + if (strcmp(r->user, szUser)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, ERRTAG + "user '%s' is not the required user '%s'", r->user, szUser); + return HTTP_FORBIDDEN; + } + ap_log_rerror(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, r, ERRTAG + "user '%s' is authorized", r->user); + return OK; + } + /* check for users in a file */ + else if (!strcmp("userfile", szRequire_cmd)) { + szFileName = ap_getword_conf(r->pool, &szRequireLine); + if (!user_in_file(r, r->user, szFileName)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, ERRTAG + "user '%s' is not in username list at '%s'", r->user, szFileName); + return HTTP_FORBIDDEN; + } + else { + return OK; + } + } + } + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "user '%s' is not authorized", r->user); + /* forbid by default */ + return HTTP_FORBIDDEN; +} /* Helper struct for CURL response */ struct MemoryStruct { - char *memory; - size_t size; - size_t realsize; - request_rec *r; + char *memory; + size_t size; + size_t realsize; + request_rec *r; }; - + /** Callback function for streaming CURL response */ static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { - size_t realsize = size * nmemb; - struct MemoryStruct *mem = (struct MemoryStruct *)userp; - - if (mem->size + realsize >= mem->realsize) { - mem->realsize = mem->size + realsize + 256; - void *tmp = apr_palloc(mem->r->pool, mem->size + realsize + 256); - memcpy(tmp, mem->memory, mem->size); - mem->memory = tmp; - } - - memcpy(&(mem->memory[mem->size]), contents, realsize); - mem->size += realsize; - mem->memory[mem->size] = 0; - return realsize; + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)userp; + + if (mem->size + realsize >= mem->realsize) { + mem->realsize = mem->size + realsize + 256; + void *tmp = apr_palloc(mem->r->pool, mem->size + realsize + 256); + memcpy(tmp, mem->memory, mem->size); + mem->memory = tmp; + } + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + return realsize; } /* Pass the assertion to the verification service defined in the config, * and return the result to the caller */ static char *verifyAssertionRemote(request_rec *r, BrowserIDConfigRec *conf, char *assertionText) { - CURL *curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, conf->verificationServerURL); - curl_easy_setopt(curl, CURLOPT_POST, 1); - - ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r , - ERRTAG "Requeting verification with audience %s", r->server->server_hostname); - - char *body = apr_psprintf(r->pool, "assertion=%s&audience=%s", - assertionText, r->server->server_hostname); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); - /** XXX set certificate for SSL negotiation */ - - struct MemoryStruct chunk; - chunk.memory = apr_pcalloc(r->pool, 1024); - chunk.size = 0; - chunk.realsize = 1024; - chunk.r = r; - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-mod_browserid-agent/1.0"); - - CURLcode result = curl_easy_perform(curl); - if (result != 0) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r , - ERRTAG "Error while communicating with BrowserID verification server: %s", - curl_easy_strerror(result)); - curl_easy_cleanup(curl); - return NULL; - } - long responseCode; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); - if (responseCode != 200) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r , - ERRTAG "Error while communicating with BrowserID verification server: result code %ld", responseCode); + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, conf->verificationServerURL); + curl_easy_setopt(curl, CURLOPT_POST, 1); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Requeting verification with audience %s", r->server->server_hostname); + + char *body = apr_psprintf(r->pool, "assertion=%s&audience=%s", + assertionText, r->server->server_hostname); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); + /** XXX set certificate for SSL negotiation */ + + struct MemoryStruct chunk; + chunk.memory = apr_pcalloc(r->pool, 1024); + chunk.size = 0; + chunk.realsize = 1024; + chunk.r = r; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-mod_browserid-agent/1.0"); + + CURLcode result = curl_easy_perform(curl); + if (result != 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Error while communicating with BrowserID verification server: %s", + curl_easy_strerror(result)); + curl_easy_cleanup(curl); + return NULL; + } + long responseCode; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); + if (responseCode != 200) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Error while communicating with BrowserID verification server: result code %ld", responseCode); + curl_easy_cleanup(curl); + return NULL; + } curl_easy_cleanup(curl); - return NULL; - } - curl_easy_cleanup(curl); - return chunk.memory; + return chunk.memory; } /* Parse x-www-url-formencoded args */ apr_table_t *parseArgs(request_rec *r, char *argStr) { - char* pair ; - char* last = NULL ; - char* eq ; - - apr_table_t *vars = apr_table_make(r->pool, 10) ; - char *delim = "&"; - - for ( pair = apr_strtok(r->args, delim, &last) ; - pair ; - pair = apr_strtok(NULL, delim, &last) ) - { - for (eq = pair ; *eq ; ++eq) - if ( *eq == '+' ) - *eq = ' ' ; - - ap_unescape_url(pair) ; - eq = strchr(pair, '=') ; - - if ( eq ) { - *eq++ = 0 ; - apr_table_merge(vars, pair, eq) ; - } else { - apr_table_merge(vars, pair, "") ; - } - } - return vars; + char *pair; + char *last = NULL; + char *eq; + + apr_table_t *vars = apr_table_make(r->pool, 10); + char *delim = "&"; + + for (pair = apr_strtok(r->args, delim, &last); + pair; + pair = apr_strtok(NULL, delim, &last)) { + + for (eq = pair; *eq; ++eq) { + if (*eq == '+') { + *eq = ' '; + } + } + + ap_unescape_url(pair); + eq = strchr(pair, '='); + + if (eq) { + *eq++ = 0; + apr_table_merge(vars, pair, eq); + } + else { + apr_table_merge(vars, pair, ""); + } + } + return vars; } /** Create a session cookie with a given identity */ static void createSessionCookie(request_rec *r, BrowserIDConfigRec *conf, char *identity) { - char *digest64 = generateSignature(r, conf, identity); - - /* syntax of cookie is identity|signature */ - apr_table_set(r->err_headers_out, "Set-Cookie", - apr_psprintf(r->pool, "%s=%s|%s; Path=/", - conf->cookieName, identity, digest64)); + char *digest64 = generateSignature(r, conf, identity); + + /* syntax of cookie is identity|signature */ + apr_table_set(r->err_headers_out, "Set-Cookie", + apr_psprintf(r->pool, "%s=%s|%s; Path=/", + conf->cookieName, identity, digest64)); } /* Called from the fixup_handler when we receive a form submission. @@ -674,114 +883,125 @@ static void createSessionCookie(request_rec *r, BrowserIDConfigRec *conf, char * */ static int processAssertionFormSubmit(request_rec *r, BrowserIDConfigRec *conf) { - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "Submission to BrowserID form handler"); - - /* parse the form and extract the assertion */ - if (r->method_number == M_GET) { - if ( r->args ) { - if ( strlen(r->args) > 16384 ) { - return HTTP_REQUEST_URI_TOO_LARGE ; - } - - apr_table_t *vars = parseArgs(r, r->args); - const char *assertionParsed = apr_table_get(vars, "assertion") ; - const char *returnto = apr_table_get(vars, "returnto") ; - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG - "In post_read_request; parsed assertion as %s", assertionParsed); - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG - "In post_read_request; parsed returnto as %s", returnto); - - /* verify the assertion... */ - yajl_val parsed_result = NULL; - if (conf->verificationServerURL) { - char *assertionResult = verifyAssertionRemote(r, conf, (char*)assertionParsed); - if (assertionResult) { - char errorBuffer[256]; - parsed_result = yajl_tree_parse(assertionResult, errorBuffer, 255); - if (!parsed_result) { - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG "Error parsing BrowserID verification response: malformed payload: %s", errorBuffer); - return DECLINED; - } - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG - "In post_read_request; parsed JSON from verification server: %s", assertionResult); - } else { - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG - "Unable to verify assertion; communication error with verification server"); - return DECLINED; - } - } else { - if (conf->verifyLocally) { - char *hdr=NULL, *payload=NULL, *sig=NULL; - char *assertion = apr_pstrdup(r->pool, assertionParsed); - hdr= apr_strtok(assertion, ".", &payload); - if (hdr) { - payload= apr_strtok(payload, ".", &sig); - if (sig) { - int len = apr_base64_decode_len(payload); - char *payloadDecode = apr_pcalloc(r->pool, len+1); - apr_base64_decode(payloadDecode, payload); - - char errorBuffer[256]; - parsed_result = yajl_tree_parse(payloadDecode, errorBuffer, 255); - if (!parsed_result) { - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG "Error parsing BrowserID login: malformed payload: %s", errorBuffer); - return DECLINED; - } - /** XXX more local validation required!!! Check timestamp, audience **/ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "Submission to BrowserID form handler"); + + /* parse the form and extract the assertion */ + if (r->method_number == M_GET) { + if (r->args) { + if (strlen(r->args) > 16384) { + return HTTP_REQUEST_URI_TOO_LARGE; + } + + apr_table_t *vars = parseArgs(r, r->args); + const char *assertionParsed = apr_table_get(vars, "assertion"); + const char *returnto = apr_table_get(vars, "returnto"); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "In post_read_request; parsed assertion as %s", assertionParsed); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "In post_read_request; parsed returnto as %s", returnto); + + /* verify the assertion... */ + yajl_val parsed_result = NULL; + if (conf->verificationServerURL) { + char *assertionResult = verifyAssertionRemote(r, conf, (char *)assertionParsed); + if (assertionResult) { + char errorBuffer[256]; + parsed_result = yajl_tree_parse(assertionResult, errorBuffer, 255); + if (!parsed_result) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Error parsing BrowserID verification response: malformed payload: %s", errorBuffer); + return DECLINED; + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "In post_read_request; parsed JSON from verification server: %s", assertionResult); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Unable to verify assertion; communication error with verification server"); + return DECLINED; + } + } + else { + if (conf->verifyLocally) { + char *hdr = NULL, *payload = NULL, *sig = NULL; + char *assertion = apr_pstrdup(r->pool, assertionParsed); + hdr = apr_strtok(assertion, ".", &payload); + if (hdr) { + payload = apr_strtok(payload, ".", &sig); + if (sig) { + int len = apr_base64_decode_len(payload); + char *payloadDecode = apr_pcalloc(r->pool, len + 1); + apr_base64_decode(payloadDecode, payload); + + char errorBuffer[256]; + parsed_result = yajl_tree_parse(payloadDecode, errorBuffer, 255); + if (!parsed_result) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Error parsing BrowserID login: malformed payload: %s", errorBuffer); + return DECLINED; + } + /** XXX more local validation required!!! Check timestamp, audience **/ + } + } + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Cannot verify BrowserID login: no verification server configured!"); + return DECLINED; + } + } + if (parsed_result) { + char *parsePath[2]; + parsePath[0] = "email"; + parsePath[1] = NULL; + yajl_val foundEmail = yajl_tree_get(parsed_result, (const char **)parsePath, yajl_t_any); + + /** XXX if we don't have an email, something went wrong. Should pull the error code properly! This will + * probably require refactoring this function since the local path is different. ***/ + if (!foundEmail || foundEmail->type != yajl_t_string) { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "Error parsing BrowserID login: no email in payload"); + return DECLINED; + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, ERRTAG + "In post_read_request; got email %s", foundEmail->u.string); + createSessionCookie(r, conf, foundEmail->u.string); + + /* redirect to the requested resource */ + apr_table_set(r->headers_out, "Location", returnto); + + return HTTP_TEMPORARY_REDIRECT; } - } - } else { - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG "Cannot verify BrowserID login: no verification server configured!"); - return DECLINED; - } - } - if (parsed_result) { - char *parsePath[2]; - parsePath[0] = "email"; - parsePath[1] = NULL; - yajl_val foundEmail = yajl_tree_get(parsed_result, (const char**)parsePath, yajl_t_any); - - /** XXX if we don't have an email, something went wrong. Should pull the error code properly! This will - * probably require refactoring this function since the local path is different. ***/ - if (!foundEmail || foundEmail->type != yajl_t_string) { - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG "Error parsing BrowserID login: no email in payload"); - return DECLINED; } - ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO, 0,r,ERRTAG "In post_read_request; got email %s", foundEmail->u.string); - createSessionCookie(r, conf, foundEmail->u.string); - - /* redirect to the requested resource */ - apr_table_set(r->headers_out,"Location", returnto); - - return HTTP_TEMPORARY_REDIRECT; - } - } - } else { - ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, 0,r,ERRTAG "In post_read_request; this is a POST - skipping it for now"); - } - return DECLINED; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, ERRTAG + "In post_read_request; this is a POST - skipping it for now"); + } + return DECLINED; } static int processLogout(request_rec *r, BrowserIDConfigRec *conf) { - apr_table_set(r->err_headers_out, "Set-Cookie", - apr_psprintf(r->pool, "%s=; Path=/; Expires=Thu, 01-Jan-1970 00:00:01 GMT", - conf->cookieName)); + apr_table_set(r->err_headers_out, "Set-Cookie", + apr_psprintf(r->pool, "%s=; Path=/; Expires=Thu, 01-Jan-1970 00:00:01 GMT", + conf->cookieName)); - if (r->args) { - if ( strlen(r->args) > 16384 ) { - return HTTP_REQUEST_URI_TOO_LARGE ; - } + if (r->args) { + if (strlen(r->args) > 16384) { + return HTTP_REQUEST_URI_TOO_LARGE; + } - apr_table_t *vars = parseArgs(r, r->args); - const char *returnto = apr_table_get(vars, "returnto") ; - if (returnto) { - apr_table_set(r->headers_out,"Location", returnto); - return HTTP_TEMPORARY_REDIRECT; + apr_table_t *vars = parseArgs(r, r->args); + const char *returnto = apr_table_get(vars, "returnto"); + if (returnto) { + apr_table_set(r->headers_out, "Location", returnto); + return HTTP_TEMPORARY_REDIRECT; + } } - } - apr_table_set(r->headers_out,"Location", "/"); - return HTTP_TEMPORARY_REDIRECT; + apr_table_set(r->headers_out, "Location", "/"); + return HTTP_TEMPORARY_REDIRECT; } /* @@ -794,18 +1014,18 @@ static int processLogout(request_rec *r, BrowserIDConfigRec *conf) */ static int Auth_browserid_fixups(request_rec *r) { - BrowserIDConfigRec *conf=NULL; + BrowserIDConfigRec *conf = NULL; /* get apache config */ - conf = ap_get_module_config(r->per_dir_config, &mod_auth_browserid_module); + conf = ap_get_module_config(r->per_dir_config, &auth_browserid_module); if (conf->submitPath && !strcmp(r->uri, conf->submitPath)) { - return processAssertionFormSubmit(r, conf); + return processAssertionFormSubmit(r, conf); } else if (conf->logoutPath && !strcmp(r->uri, conf->logoutPath)) { - return processLogout(r, conf); + return processLogout(r, conf); } - + /* otherwise we don't care */ return DECLINED; } @@ -820,74 +1040,3 @@ static void register_hooks(apr_pool_t *p) ap_hook_auth_checker(Auth_browserid_check_auth, NULL, NULL, APR_HOOK_FIRST); ap_hook_fixups(Auth_browserid_fixups, NULL, NULL, APR_HOOK_FIRST); } - -/************************************************************************************ - * Apache CONFIG Phase: - ************************************************************************************/ -static void *create_browserid_config(apr_pool_t *p, char *d) -{ - BrowserIDConfigRec *conf = apr_palloc(p, sizeof(*conf)); - - conf->cookieName = apr_pstrdup(p,"BrowserID"); - conf->submitPath = "/mod_browserid_submit"; - conf->serverSecret = "BrowserIDSecret"; - conf->logoutPath = NULL; - conf->authoritative = 0; /* not by default */ - conf->authBasicFix = 0; /* do not fix header for php auth by default */ - conf->forwardedRequestHeader = NULL; /* pass the authenticated user, signed, as an HTTP header */ - return conf; -} - -/* apache config fonction of the module */ -static const command_rec Auth_browserid_cmds[] = -{ - AP_INIT_TAKE1 ("AuthBrowserIDSetHTTPHeader", ap_set_string_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, forwardedRequestHeader), - OR_AUTHCFG, "Set to 'yes' to forward a signed HTTP header containing the verified identity; set to 'no' by default"), - - AP_INIT_TAKE1("AuthBrowserIDCookieName", ap_set_string_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, cookieName), - OR_AUTHCFG, "Name of cookie to set"), - - AP_INIT_FLAG ("AuthBrowserIDAuthoritative", ap_set_flag_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, authoritative), - OR_AUTHCFG, "Set to 'yes' to allow access control to be passed along to lower modules; set to 'no' by default"), - - AP_INIT_FLAG ("AuthBrowserIDSimulateAuthBasic", ap_set_flag_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, authBasicFix), - OR_AUTHCFG, "Set to 'yes' to enable creation of a synthetic Basic Authorization header containing the username"), - - AP_INIT_TAKE1 ("AuthBrowserIDSubmitPath", ap_set_string_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, submitPath), - OR_AUTHCFG, "Path to which login forms will be submitted. Form must contain a field named 'assertion'"), - - AP_INIT_TAKE1 ("AuthBrowserIDLogoutPath", ap_set_string_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, logoutPath), - OR_AUTHCFG, "Path to which logout requests will be submitted. An optional 'returnto' parameter will be used for a redirection, if provided."), - - AP_INIT_TAKE1 ("AuthBrowserIDVerificationServerURL", ap_set_string_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, verificationServerURL), - OR_AUTHCFG, "URL of the BrowserID verification server."), - - AP_INIT_FLAG ("AuthBrowserIDVerifyLocally", ap_set_flag_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, verifyLocally), - OR_AUTHCFG, "Set to 'yes' to verify assertions locally; ignored if VerificationServerURL is set"), - - AP_INIT_TAKE1 ("AuthBrowserIDSecret", ap_set_string_slot, - (void *)APR_OFFSETOF(BrowserIDConfigRec, serverSecret), - OR_AUTHCFG, "Server secret for authentication cookie."), - - {NULL} -}; - -/* apache module structure */ -module AP_MODULE_DECLARE_DATA mod_auth_browserid_module = -{ - STANDARD20_MODULE_STUFF, - create_browserid_config, /* dir config creator */ - NULL, /* dir merger --- default is to override */ - NULL, /* server config */ - NULL, /* merge server config */ - Auth_browserid_cmds, /* command apr_table_t */ - register_hooks /* register hooks */ -};