121
120
static const struct auth_class {
122
121
const char *id, *req_hdr, *resp_hdr, *resp_info_hdr;
123
int status_code, fail_code;
122
int status_code; /* Response status-code to trap. */
123
int fail_code; /* NE_* request to fail with. */
124
const char *error_noauth; /* Error message template use when
125
* giving up authentication attempts. */
124
126
} ah_server_class = {
126
128
"Authorization", "WWW-Authenticate", "Authentication-Info",
130
N_("Could not authenticate to server: %s")
128
131
}, ah_proxy_class = {
130
133
"Proxy-Authorization", "Proxy-Authenticate", "Proxy-Authentication-Info",
135
N_("Could not authenticate to proxy server: %s")
134
138
/* Authentication session state. */
221
222
/* Parse the authentication challenge; returns zero on success, or
222
223
* non-zero if this challenge be handled. 'attempt' is the number
223
* of times the request has been resent due to auth challenges. */
224
* of times the request has been resent due to auth challenges.
225
* On failure, challenge_error() should be used to append an error
226
* message to the error buffer 'errmsg'. */
224
227
int (*challenge)(auth_session *sess, int attempt,
225
struct auth_challenge *chall);
228
struct auth_challenge *chall, ne_buffer **errmsg);
227
230
/* Return the string to send in the -Authenticate request header:
228
231
* (ne_malloc-allocated, NUL-terminated string) */
229
232
char *(*response)(auth_session *sess, struct auth_request *req);
231
/* Parse a Authentication-Info response; returns NE_* error
234
/* Parse a Authentication-Info response; returns NE_* error code
235
* on failure; on failure, the session error string must be
233
237
int (*verify)(struct auth_request *req, auth_session *sess,
234
238
const char *value);
236
240
int flags; /* AUTH_FLAG_* flags */
239
/* Forward-declaration of protocols array. */
240
static const struct auth_protocol protocols[];
243
/* Helper function to append an error to the buffer during challenge
244
* handling. Pass printf-style string. *errmsg may be NULL and is
245
* allocated if necessary. errmsg must be non-NULL. */
246
static void challenge_error(ne_buffer **errmsg, const char *fmt, ...)
247
ne_attribute((format(printf, 2, 3)));
249
/* Free the domains array, precondition sess->ndomains > 0. */
250
static void free_domains(auth_session *sess)
253
ne_free(sess->domains[sess->ndomains - 1]);
254
} while (--sess->ndomains);
255
ne_free(sess->domains);
256
sess->domains = NULL;
242
259
static void clean_session(auth_session *sess)
281
299
hash = ne_md5_create_ctx();
284
if (RAND_status() == 1 && RAND_pseudo_bytes(data, sizeof data) >= 0)
303
gcry_create_nonce(data, sizeof data);
304
ne_md5_process_bytes(data, sizeof data, hash);
307
#elif defined(HAVE_OPENSSL)
308
if (RAND_status() == 1 && RAND_pseudo_bytes(data, sizeof data) >= 0) {
285
309
ne_md5_process_bytes(data, sizeof data, hash);
288
/* Fallback sources of random data: all bad, but no good sources
291
/* Uninitialized stack data; yes, happy valgrinders, this is
292
* supposed to be here. */
293
ne_md5_process_bytes(data, sizeof data, hash);
312
#endif /* HAVE_OPENSSL */
314
/* Fallback sources of random data: all bad, but no good sources
317
/* Uninitialized stack data; yes, happy valgrinders, this is
318
* supposed to be here. */
319
ne_md5_process_bytes(data, sizeof data, hash);
295
322
#ifdef HAVE_GETTIMEOFDAY
298
if (gettimeofday(&tv, NULL) == 0)
299
ne_md5_process_bytes(&tv, sizeof tv, hash);
324
if (gettimeofday(&tv, NULL) == 0)
325
ne_md5_process_bytes(&tv, sizeof tv, hash);
301
326
#else /* HAVE_GETTIMEOFDAY */
303
time_t t = time(NULL);
304
ne_md5_process_bytes(&t, sizeof t, hash);
327
time_t t = time(NULL);
328
ne_md5_process_bytes(&t, sizeof t, hash);
309
DWORD pid = GetCurrentThreadId();
333
DWORD pid = GetCurrentThreadId();
311
pid_t pid = getpid();
313
ne_md5_process_bytes(&pid, sizeof pid, hash);
335
pid_t pid = getpid();
337
ne_md5_process_bytes(&pid, sizeof pid, hash);
320
341
ne_md5_finish_ascii(hash, ret);
321
342
ne_md5_destroy_ctx(hash);
323
344
return ne_strdup(ret);
326
static int get_credentials(auth_session *sess, int attempt,
347
/* Callback to retrieve user credentials for given session on given
348
* attempt (pre request) for given challenge. Password is written to
349
* pwbuf (of size NE_ABUFSIZ. On error, challenge_error() is used
351
static int get_credentials(auth_session *sess, ne_buffer **errmsg, int attempt,
327
352
struct auth_challenge *chall, char *pwbuf)
329
return chall->handler->creds(chall->handler->userdata, sess->realm,
330
attempt, sess->username, pwbuf);
354
if (chall->handler->creds(chall->handler->userdata, sess->realm,
355
chall->handler->attempt++, sess->username, pwbuf) == 0) {
358
challenge_error(errmsg, _("rejected %s challenge"),
359
chall->protocol->name);
333
364
/* Examine a Basic auth challenge.
334
365
* Returns 0 if an valid challenge, else non-zero. */
335
366
static int basic_challenge(auth_session *sess, int attempt,
336
struct auth_challenge *parms)
367
struct auth_challenge *parms,
338
370
char *tmp, password[NE_ABUFSIZ];
340
372
/* Verify challenge... must have a realm */
341
373
if (parms->realm == NULL) {
374
challenge_error(errmsg, _("missing realm in Basic challenge"));
453
487
if (bintoken) ne_free(bintoken);
455
489
if (GSS_ERROR(major)) {
456
ne_buffer *err = ne_buffer_create();
459
make_gss_error(err, &flag, major, GSS_C_GSS_CODE);
460
make_gss_error(err, &flag, minor, GSS_C_MECH_CODE);
461
NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Error: %s\n", err->data);
462
ne_set_error(sess->sess, _("GSSAPI authentication error (%s)"),
464
ne_buffer_destroy(err);
492
challenge_error(errmsg, _("GSSAPI authentication error: "));
493
make_gss_error(*errmsg, &flag, major, GSS_C_GSS_CODE);
494
make_gss_error(*errmsg, &flag, minor, GSS_C_MECH_CODE);
495
526
/* Process a Negotiate challange CHALL in session SESS; returns zero
496
527
* if challenge is accepted. */
497
528
static int negotiate_challenge(auth_session *sess, int attempt,
498
struct auth_challenge *chall)
529
struct auth_challenge *chall,
500
532
const char *token = chall->opaque;
502
534
/* Respect an initial challenge - which must have no input token,
503
535
* or a continuation - which must have an input token. */
504
536
if (attempt == 0 || token) {
505
return continue_negotiate(sess, token);
537
return continue_negotiate(sess, token, errmsg);
508
NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Ignoring empty Negotiate "
509
"challenge (attempt=%d).\n", attempt);
540
challenge_error(errmsg, _("ignoring empty Negotiate continuation"));
541
574
NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Negotiate response token [%s]\n", ptr);
542
ret = continue_negotiate(sess, ptr);
575
ret = continue_negotiate(sess, ptr, &errmsg);
544
NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Mutual auth failed.\n");
577
ne_set_error(sess->sess, _("Negotiate response verification failure: %s"),
581
if (errmsg) ne_buffer_destroy(errmsg);
547
584
return ret ? NE_ERROR : NE_OK;
552
static char *request_sspi(auth_session *sess)
589
static char *request_sspi(auth_session *sess, struct auth_request *request)
554
591
return ne_concat(sess->protocol->name, " ", sess->sspi_token, "\r\n", NULL);
557
594
static int sspi_challenge(auth_session *sess, int attempt,
558
struct auth_challenge *parms)
595
struct auth_challenge *parms,
560
598
int ntlm = ne_strcasecmp(parms->protocol->name, "NTLM") == 0;
631
/* Parse the "domain" challenge parameter and set the domains array up
632
* in the session appropriately. */
633
static int parse_domain(auth_session *sess, const char *domain)
635
char *cp = ne_strdup(domain), *p = cp;
639
memset(&base, 0, sizeof base);
640
ne_fill_server_uri(sess->sess, &base);
643
char *token = ne_token(&p, ' ');
644
ne_uri rel, absolute;
646
if (ne_uri_parse(token, &rel) == 0) {
647
/* Resolve relative to the Request-URI. */
648
ne_uri_resolve(&base, &rel, &absolute);
650
base.path = absolute.path;
652
/* Ignore URIs not on this server. */
653
if (absolute.path && ne_uri_cmp(&absolute, &base) == 0) {
654
sess->domains = ne_realloc(sess->domains,
656
sizeof(*sess->domains));
657
sess->domains[sess->ndomains - 1] = absolute.path;
658
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Using domain %s from %s\n",
659
absolute.path, token);
660
absolute.path = NULL;
663
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignoring domain %s\n",
667
ne_uri_free(&absolute);
675
} while (p && !invalid);
677
if (invalid && sess->ndomains) {
587
688
/* Examine a digest challenge: return 0 if it is a valid Digest challenge,
588
689
* else non-zero. */
589
690
static int digest_challenge(auth_session *sess, int attempt,
590
struct auth_challenge *parms)
691
struct auth_challenge *parms,
592
694
char password[NE_ABUFSIZ];
594
696
if (parms->alg == auth_alg_unknown) {
595
ne_set_error(sess->sess, _("Unknown algorithm in Digest "
596
"authentication challenge"));
697
challenge_error(errmsg, _("unknown algorithm in Digest challenge"));
599
700
else if (parms->alg == auth_alg_md5_sess && !parms->qop_auth) {
600
ne_set_error(sess->sess, _("Incompatible algorithm in Digest "
601
"authentication challenge"));
701
challenge_error(errmsg, _("incompatible algorithm in Digest challenge"));
604
704
else if (parms->realm == NULL || parms->nonce == NULL) {
605
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Digest challenge missing parms.\n");
606
ne_set_error(sess->sess, _("Missing nonce or realm in Digest "
607
"authentication challenge"));
705
challenge_error(errmsg, _("missing parameter in Digest challenge"));
708
else if (parms->stale && sess->nonce_count == 0) {
709
challenge_error(errmsg, _("initial Digest challenge was stale"));
712
else if (parms->stale && (sess->alg != parms->alg
713
|| strcmp(sess->realm, parms->realm))) {
714
/* With stale=true the realm and algorithm cannot change since these
715
* require re-hashing H(A1) which defeats the point. */
716
challenge_error(errmsg, _("stale Digest challenge with new algorithm or realm"));
611
720
if (!parms->stale) {
612
/* Forget the old session details */
615
sess->realm = ne_strdup(parms->realm);
617
/* Not a stale response: really need user authentication */
618
if (get_credentials(sess, attempt, parms, password)) {
619
/* Failed to get credentials */
623
sess->alg = parms->alg;
721
/* Non-stale challenge: clear session and request credentials. */
724
/* The domain paramater must be parsed after the session is
725
* cleaned; ignore domain for proxy auth. */
726
if (parms->domain && sess->spec == &ah_server_class
727
&& parse_domain(sess, parms->domain)) {
728
challenge_error(errmsg, _("could not parse domain in Digest challenge"));
732
sess->realm = ne_strdup(parms->realm);
733
sess->alg = parms->alg;
734
sess->cnonce = get_cnonce();
736
if (get_credentials(sess, errmsg, attempt, parms, password)) {
737
/* Failed to get credentials */
742
/* Stale challenge: accept a new nonce or opaque. */
743
if (sess->nonce) ne_free(sess->nonce);
744
if (sess->opaque && parms->opaque) ne_free(sess->opaque);
624
747
sess->nonce = ne_strdup(parms->nonce);
625
sess->cnonce = get_cnonce();
626
/* TODO: add domain handling. */
627
if (parms->opaque != NULL) {
628
sess->opaque = ne_strdup(parms->opaque); /* don't strip the quotes */
749
sess->opaque = ne_strdup(parms->opaque);
631
752
if (parms->got_qop) {
805
/* Returns non-zero if given Request-URI is inside the authentication
806
* domain defined for the session. */
807
static int inside_domain(auth_session *sess, const char *req_uri)
813
/* Parse the Request-URI; it will be an absoluteURI if using a
814
* proxy, and possibly '*'. */
815
if (strcmp(req_uri, "*") == 0 || ne_uri_parse(req_uri, &uri) != 0) {
816
/* Presume outside the authentication domain. */
820
for (n = 0; n < sess->ndomains && !inside; n++) {
821
const char *d = sess->domains[n];
823
inside = strncmp(uri.path, d, strlen(d)) == 0;
826
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: '%s' is inside auth domain: %d.\n",
684
833
/* Return Digest authentication credentials header value for the given
686
835
static char *request_digest(auth_session *sess, struct auth_request *req)
891
1041
ne_set_error(sess->sess, _("Digest mutual authentication failure: "
892
1042
"client nonce mismatch"));
894
else if (nonce_count != sess->nonce_count) {
896
ne_set_error(sess->sess, _("Digest mutual authentication failure: "
897
"nonce count mismatch (%u not %u)"),
898
nonce_count, sess->nonce_count);
1048
nonce_count = strtoul(nc, &ptr, 16);
1049
if (*ptr != '\0' || errno) {
1051
ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1052
"could not parse nonce count"));
1054
else if (nonce_count != sess->nonce_count) {
1056
ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1057
"nonce count mismatch (%u not %u)"),
1058
nonce_count, sess->nonce_count);
901
/* Verify the response-digest field */
1062
/* Finally, for qop=auth cases, if everything else is OK, verify
1063
* the response-digest field. */
1064
if (qop == auth_qop_auth && ret == NE_OK) {
902
1065
struct ne_md5_ctx *a2;
903
1066
char a2_md5_ascii[33], rdig_md5_ascii[33];
1117
static const struct auth_protocol protocols[] = {
1118
{ NE_AUTH_BASIC, 10, "Basic",
1119
basic_challenge, request_basic, NULL,
1121
{ NE_AUTH_DIGEST, 20, "Digest",
1122
digest_challenge, request_digest, verify_digest_response,
1125
{ NE_AUTH_NEGOTIATE, 30, "Negotiate",
1126
negotiate_challenge, request_negotiate, verify_negotiate_response,
1127
AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1130
{ NE_AUTH_NEGOTIATE, 30, "NTLM",
1131
sspi_challenge, request_sspi, NULL,
1132
AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1133
{ NE_AUTH_NEGOTIATE, 30, "Negotiate",
1134
sspi_challenge, request_sspi, NULL,
1135
AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
954
1140
/* Insert a new auth challenge for protocol 'proto' in list of
955
1141
* challenges 'list'. The challenge list is kept in sorted order of
956
1142
* strength, with highest strength first. */
1286
1533
(*hdl)->creds = creds;
1287
1534
(*hdl)->userdata = userdata;
1288
1535
(*hdl)->next = NULL;
1291
static void auth_register_default(ne_session *sess, int isproxy,
1292
const struct auth_class *ahc, const char *id,
1293
ne_auth_creds creds, void *userdata)
1295
unsigned protomask = NE_AUTH_BASIC|NE_AUTH_DIGEST;
1297
if (strcmp(ne_get_scheme(sess), "https") == 0 || isproxy) {
1298
protomask |= NE_AUTH_NEGOTIATE;
1301
auth_register(sess, isproxy, protomask, ahc, id, creds, userdata);
1304
static const struct auth_protocol protocols[] = {
1305
{ NE_AUTH_BASIC, 10, "Basic",
1306
basic_challenge, request_basic, NULL,
1308
{ NE_AUTH_DIGEST, 20, "Digest",
1309
digest_challenge, request_digest, verify_digest_response,
1312
{ NE_AUTH_NEGOTIATE, 30, "Negotiate",
1313
negotiate_challenge, request_negotiate, verify_negotiate_response,
1314
AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x },
1317
{ NE_AUTH_NEGOTIATE, 30, "NTLM",
1318
sspi_challenge, request_sspi, NULL,
1319
AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x },
1320
{ NE_AUTH_NEGOTIATE, 30, "Negotiate",
1321
sspi_challenge, request_sspi, NULL,
1322
AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x },
1536
(*hdl)->attempt = 0;
1327
1539
void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1329
auth_register_default(sess, 0, &ah_server_class, HOOK_SERVER_ID,
1541
auth_register(sess, 0, NE_AUTH_DEFAULT, &ah_server_class, HOOK_SERVER_ID,
1333
1545
void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1335
auth_register_default(sess, 1, &ah_proxy_class, HOOK_PROXY_ID,
1547
auth_register(sess, 1, NE_AUTH_DEFAULT, &ah_proxy_class, HOOK_PROXY_ID,
1339
1551
void ne_add_server_auth(ne_session *sess, unsigned protocol,