17 buffer
*auth_backend_conf
;
18 unsigned short auth_extern_authn
;
21 const http_auth_backend_t
*auth_backend
;
27 plugin_config
**config_storage
;
32 static handler_t
mod_auth_check_basic(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
);
33 static handler_t
mod_auth_check_digest(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
);
34 static handler_t
mod_auth_check_extern(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
);
36 INIT_FUNC(mod_auth_init
) {
37 static const http_auth_scheme_t http_auth_scheme_basic
= { "basic", mod_auth_check_basic
, NULL
};
38 static const http_auth_scheme_t http_auth_scheme_digest
= { "digest", mod_auth_check_digest
, NULL
};
39 static const http_auth_scheme_t http_auth_scheme_extern
= { "extern", mod_auth_check_extern
, NULL
};
42 /* register http_auth_scheme_* */
43 http_auth_scheme_set(&http_auth_scheme_basic
);
44 http_auth_scheme_set(&http_auth_scheme_digest
);
45 http_auth_scheme_set(&http_auth_scheme_extern
);
47 p
= calloc(1, sizeof(*p
));
52 FREE_FUNC(mod_auth_free
) {
57 if (!p
) return HANDLER_GO_ON
;
59 if (p
->config_storage
) {
61 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
62 plugin_config
*s
= p
->config_storage
[i
];
64 if (NULL
== s
) continue;
66 array_free(s
->auth_require
);
67 buffer_free(s
->auth_backend_conf
);
71 free(p
->config_storage
);
79 /* data type for mod_auth structured data
80 * (parsed from auth.require array of strings) */
83 http_auth_require_t
*require
;
86 static void data_auth_free(data_unset
*d
)
88 data_auth
* const dauth
= (data_auth
*)d
;
89 buffer_free(dauth
->key
);
90 http_auth_require_free(dauth
->require
);
94 static data_auth
*data_auth_init(void)
96 data_auth
* const dauth
= calloc(1, sizeof(*dauth
));
97 force_assert(NULL
!= dauth
);
98 dauth
->copy
= NULL
; /* must not be called on this data */
99 dauth
->free
= data_auth_free
;
100 dauth
->reset
= NULL
; /* must not be called on this data */
101 dauth
->insert_dup
= NULL
; /* must not be called on this data */
102 dauth
->print
= NULL
; /* must not be called on this data */
103 dauth
->type
= TYPE_OTHER
;
105 dauth
->key
= buffer_init();
106 dauth
->require
= http_auth_require_init();
111 static int mod_auth_require_parse (server
*srv
, http_auth_require_t
* const require
, const buffer
*b
)
113 /* user=name1|user=name2|group=name3|host=name4 */
115 const char *str
= b
->ptr
;
118 if (buffer_is_equal_string(b
, CONST_STR_LEN("valid-user"))) {
119 require
->valid_user
= 1;
120 return 1; /* success */
126 p
= strchr(str
, '|');
127 len
= NULL
!= p
? (size_t)(p
- str
) : strlen(str
);
128 eq
= memchr(str
, '=', len
);
130 log_error_write(srv
, __FILE__
, __LINE__
, "sssbss",
131 "error parsing auth.require 'require' field: missing '='",
132 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
133 "error value:", b
, "error near:", str
);
137 log_error_write(srv
, __FILE__
, __LINE__
, "sssbss",
138 "error parsing auth.require 'require' field: missing token after '='",
139 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
140 "error value:", b
, "error near:", str
);
144 switch ((int)(eq
- str
)) {
146 if (0 == memcmp(str
, CONST_STR_LEN("user"))) {
147 data_string
*ds
= data_string_init();
148 buffer_copy_string_len(ds
->key
,str
+5,len
-5); /*("user=" is 5)*/
149 array_insert_unique(require
->user
, (data_unset
*)ds
);
152 else if (0 == memcmp(str
, CONST_STR_LEN("host"))) {
153 data_string
*ds
= data_string_init();
154 buffer_copy_string_len(ds
->key
,str
+5,len
-5); /*("host=" is 5)*/
155 array_insert_unique(require
->host
, (data_unset
*)ds
);
156 log_error_write(srv
, __FILE__
, __LINE__
, "ssb",
157 "warning parsing auth.require 'require' field: 'host' not implemented;",
161 break; /* to error */
163 if (0 == memcmp(str
, CONST_STR_LEN("group"))) {
164 data_string
*ds
= data_string_init();
165 buffer_copy_string_len(ds
->key
,str
+6,len
-6); /*("group=" is 6)*/
166 array_insert_unique(require
->group
, (data_unset
*)ds
);
167 #if 0/*(supported by mod_authn_ldap, but not all other backends)*/
168 log_error_write(srv
, __FILE__
, __LINE__
, "ssb",
169 "warning parsing auth.require 'require' field: 'group' not implemented;",
174 break; /* to error */
176 if (0 == memcmp(str
, CONST_STR_LEN("valid-user"))) {
177 log_error_write(srv
, __FILE__
, __LINE__
, "sssb",
178 "error parsing auth.require 'require' field: valid user can not be combined with other require rules",
179 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
183 break; /* to error */
185 break; /* to error */
188 log_error_write(srv
, __FILE__
, __LINE__
, "sssbss",
189 "error parsing auth.require 'require' field: invalid/unsupported token",
190 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
191 "error value:", b
, "error near:", str
);
194 } while (p
&& *((str
= p
+1)));
196 return 1; /* success */
199 SETDEFAULTS_FUNC(mod_auth_set_defaults
) {
200 plugin_data
*p
= p_d
;
203 config_values_t cv
[] = {
204 { "auth.backend", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
205 { "auth.require", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
206 { "auth.extern-authn", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
},/* 2 */
207 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
210 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
212 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
213 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
218 s
= calloc(1, sizeof(plugin_config
));
219 s
->auth_backend_conf
= buffer_init();
221 s
->auth_require
= array_init();
223 cv
[0].destination
= s
->auth_backend_conf
;
224 cv
[1].destination
= s
->auth_require
; /* T_CONFIG_LOCAL; not modified by config_insert_values_global() */
225 cv
[2].destination
= &s
->auth_extern_authn
;
227 p
->config_storage
[i
] = s
;
229 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
230 return HANDLER_ERROR
;
233 if (!buffer_string_is_empty(s
->auth_backend_conf
)) {
234 s
->auth_backend
= http_auth_backend_get(s
->auth_backend_conf
);
235 if (NULL
== s
->auth_backend
) {
236 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not supported:", s
->auth_backend_conf
);
238 return HANDLER_ERROR
;
242 /* no auth.require for this section */
243 if (NULL
== (da
= (data_array
*)array_get_element(config
->value
, "auth.require"))) continue;
245 if (da
->type
!= TYPE_ARRAY
|| !array_is_kvarray(da
->value
)) {
246 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
247 "unexpected value for auth.require; expected ",
248 "auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )");
249 return HANDLER_ERROR
;
253 for (n
= 0; n
< da
->value
->used
; n
++) {
255 data_array
*da_file
= (data_array
*)da
->value
->data
[n
];
256 const buffer
*method
= NULL
, *realm
= NULL
, *require
= NULL
;
257 const http_auth_scheme_t
*auth_scheme
;
259 if (!array_is_kvstring(da_file
->value
)) {
260 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
261 "unexpected value for auth.require; expected ",
262 "auth.require = ( \"urlpath\" => ( \"option\" => \"value\" ) )");
264 return HANDLER_ERROR
;
267 for (m
= 0; m
< da_file
->value
->used
; m
++) {
268 if (da_file
->value
->data
[m
]->type
== TYPE_STRING
) {
269 data_string
*ds
= (data_string
*)da_file
->value
->data
[m
];
270 if (buffer_is_equal_string(ds
->key
, CONST_STR_LEN("method"))) {
272 } else if (buffer_is_equal_string(ds
->key
, CONST_STR_LEN("realm"))) {
274 } else if (buffer_is_equal_string(ds
->key
, CONST_STR_LEN("require"))) {
277 log_error_write(srv
, __FILE__
, __LINE__
, "ssbs",
278 "the field is unknown in:",
279 "auth.require = ( \"...\" => ( ..., -> \"",
280 da_file
->value
->data
[m
]->key
,
281 "\" <- => \"...\" ) )");
283 return HANDLER_ERROR
;
286 log_error_write(srv
, __FILE__
, __LINE__
, "ssbs",
287 "a string was expected for:",
288 "auth.require = ( \"...\" => ( ..., -> \"",
289 da_file
->value
->data
[m
]->key
,
290 "\" <- => \"...\" ) )");
292 return HANDLER_ERROR
;
296 if (buffer_string_is_empty(method
)) {
297 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
298 "the method field is missing or blank in:",
299 "auth.require = ( \"...\" => ( ..., \"method\" => \"...\" ) )");
300 return HANDLER_ERROR
;
302 auth_scheme
= http_auth_scheme_get(method
);
303 if (NULL
== auth_scheme
) {
304 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
305 "unknown method", method
, "(e.g. \"basic\", \"digest\" or \"extern\") in",
306 "auth.require = ( \"...\" => ( ..., \"method\" => \"...\") )");
307 return HANDLER_ERROR
;
311 if (buffer_is_empty(realm
)) {
312 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
313 "the realm field is missing in:",
314 "auth.require = ( \"...\" => ( ..., \"realm\" => \"...\" ) )");
315 return HANDLER_ERROR
;
318 if (buffer_string_is_empty(require
)) {
319 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
320 "the require field is missing or blank in:",
321 "auth.require = ( \"...\" => ( ..., \"require\" => \"...\" ) )");
322 return HANDLER_ERROR
;
325 if (require
) { /*(always true at this point)*/
326 data_auth
* const dauth
= data_auth_init();
327 buffer_copy_buffer(dauth
->key
, da_file
->key
);
328 dauth
->require
->scheme
= auth_scheme
;
329 buffer_copy_buffer(dauth
->require
->realm
, realm
);
330 if (!mod_auth_require_parse(srv
, dauth
->require
, require
)) {
331 dauth
->free((data_unset
*)dauth
);
332 return HANDLER_ERROR
;
334 array_insert_unique(s
->auth_require
, (data_unset
*)dauth
);
339 return HANDLER_GO_ON
;
344 static int mod_auth_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
346 plugin_config
*s
= p
->config_storage
[0];
350 PATCH(auth_extern_authn
);
352 /* skip the first, the global context */
353 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
354 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
355 s
= p
->config_storage
[i
];
357 /* condition didn't match */
358 if (!config_check_cond(srv
, con
, dc
)) continue;
361 for (j
= 0; j
< dc
->value
->used
; j
++) {
362 data_unset
*du
= dc
->value
->data
[j
];
364 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend"))) {
366 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.require"))) {
368 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.extern-authn"))) {
369 PATCH(auth_extern_authn
);
378 static handler_t
mod_auth_uri_handler(server
*srv
, connection
*con
, void *p_d
) {
380 plugin_data
*p
= p_d
;
382 mod_auth_patch_connection(srv
, con
, p
);
384 if (p
->conf
.auth_require
== NULL
) return HANDLER_GO_ON
;
386 /* search auth directives for first prefix match against URL path */
387 for (k
= 0; k
< p
->conf
.auth_require
->used
; k
++) {
388 const data_auth
* const dauth
= (data_auth
*)p
->conf
.auth_require
->data
[k
];
389 const buffer
*path
= dauth
->key
;
391 if (buffer_string_length(con
->uri
.path
) < buffer_string_length(path
)) continue;
393 /* if we have a case-insensitive FS we have to lower-case the URI here too */
395 if (!con
->conf
.force_lowercase_filenames
396 ? 0 == strncmp(con
->uri
.path
->ptr
, path
->ptr
, buffer_string_length(path
))
397 : 0 == strncasecmp(con
->uri
.path
->ptr
, path
->ptr
, buffer_string_length(path
))) {
398 const http_auth_scheme_t
* const scheme
= dauth
->require
->scheme
;
399 if (p
->conf
.auth_extern_authn
) {
400 data_string
*ds
= (data_string
*)array_get_element(con
->environment
, "REMOTE_USER");
401 if (NULL
!= ds
&& http_auth_match_rules(dauth
->require
, ds
->value
->ptr
, NULL
, NULL
)) {
402 return HANDLER_GO_ON
;
405 return scheme
->checkfn(srv
, con
, scheme
->p_d
, dauth
->require
, p
->conf
.auth_backend
);
409 /* nothing to do for us */
410 return HANDLER_GO_ON
;
413 int mod_auth_plugin_init(plugin
*p
);
414 int mod_auth_plugin_init(plugin
*p
) {
415 p
->version
= LIGHTTPD_VERSION_ID
;
416 p
->name
= buffer_init_string("auth");
417 p
->init
= mod_auth_init
;
418 p
->set_defaults
= mod_auth_set_defaults
;
419 p
->handle_uri_clean
= mod_auth_uri_handler
;
420 p
->cleanup
= mod_auth_free
;
431 * auth schemes (basic, digest, extern)
433 * (could be in separate file from mod_auth.c as long as registration occurs)
436 #include "response.h"
441 static handler_t
mod_auth_send_400_bad_request(server
*srv
, connection
*con
) {
444 /* a field was missing or invalid */
445 con
->http_status
= 400; /* Bad Request */
448 return HANDLER_FINISHED
;
451 static handler_t
mod_auth_send_401_unauthorized_basic(server
*srv
, connection
*con
, buffer
*realm
) {
452 con
->http_status
= 401;
455 buffer_copy_string_len(srv
->tmp_buf
, CONST_STR_LEN("Basic realm=\""));
456 buffer_append_string_buffer(srv
->tmp_buf
, realm
);
457 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", charset=\"UTF-8\""));
459 response_header_insert(srv
, con
, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv
->tmp_buf
));
461 return HANDLER_FINISHED
;
464 static handler_t
mod_auth_check_basic(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
) {
465 data_string
*ds
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
469 handler_t rc
= HANDLER_UNSET
;
473 if (NULL
== backend
) {
474 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not configured for", con
->uri
.path
);
475 con
->http_status
= 500;
477 return HANDLER_FINISHED
;
480 if (NULL
== ds
|| buffer_is_empty(ds
->value
)) {
481 return mod_auth_send_401_unauthorized_basic(srv
, con
, require
->realm
);
484 if (0 != strncasecmp(ds
->value
->ptr
, "Basic ", sizeof("Basic ")-1)) {
485 return mod_auth_send_400_bad_request(srv
, con
);
488 if (buffer_string_length(ds
->value
) < sizeof("Basic ")-1) {
489 return mod_auth_send_400_bad_request(srv
, con
);
493 username
= buffer_init();
496 /* coverity[overflow_sink : FALSE] */
497 if (!buffer_append_base64_decode(username
, b
->ptr
+sizeof("Basic ")-1, buffer_string_length(b
)-(sizeof("Basic ")-1), BASE64_STANDARD
)) {
498 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "decoding base64-string failed", username
);
500 buffer_free(username
);
501 return mod_auth_send_400_bad_request(srv
, con
);
504 /* r2 == user:password */
505 if (NULL
== (pw
= strchr(username
->ptr
, ':'))) {
506 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "missing ':' in", username
);
508 buffer_free(username
);
509 return mod_auth_send_400_bad_request(srv
, con
);
512 buffer_string_set_length(username
, pw
- username
->ptr
);
515 rc
= backend
->basic(srv
, con
, backend
->p_d
, require
, username
, pw
);
518 http_auth_setenv(con
->environment
, CONST_BUF_LEN(username
), CONST_STR_LEN("Basic"));
520 case HANDLER_WAIT_FOR_EVENT
:
521 case HANDLER_FINISHED
:
525 log_error_write(srv
, __FILE__
, __LINE__
, "sbsBsB", "password doesn't match for", con
->uri
.path
, "username:", username
, ", IP:", con
->dst_addr_buf
);
530 buffer_free(username
);
531 return (HANDLER_UNSET
!= rc
) ? rc
: mod_auth_send_401_unauthorized_basic(srv
, con
, require
->realm
);
535 #define HASHHEXLEN 32
536 typedef unsigned char HASH
[HASHLEN
];
537 typedef char HASHHEX
[HASHHEXLEN
+1];
539 static void CvtHex(const HASH Bin
, char (*Hex
)[33]) {
540 li_tohex(*Hex
, sizeof(*Hex
), (const char*) Bin
, 16);
549 static handler_t
mod_auth_send_401_unauthorized_digest(server
*srv
, connection
*con
, buffer
*realm
, int nonce_stale
);
551 static handler_t
mod_auth_check_digest(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
) {
552 data_string
*ds
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
557 char *username
= NULL
;
561 char *algorithm
= NULL
;
565 char *respons
= NULL
;
568 const char *m
= NULL
;
582 digest_kv dkv
[10] = {
597 dkv
[0].ptr
= &username
;
601 dkv
[4].ptr
= &algorithm
;
603 dkv
[6].ptr
= &cnonce
;
605 dkv
[8].ptr
= &respons
;
609 if (NULL
== backend
) {
610 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not configured for", con
->uri
.path
);
611 con
->http_status
= 500;
613 return HANDLER_FINISHED
;
616 if (NULL
== ds
|| buffer_is_empty(ds
->value
)) {
617 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
620 if (0 != strncasecmp(ds
->value
->ptr
, "Digest ", sizeof("Digest ")-1)) {
621 return mod_auth_send_400_bad_request(srv
, con
);
623 size_t n
= buffer_string_length(ds
->value
);
625 if (n
< sizeof("Digest ")-1) {
626 return mod_auth_send_400_bad_request(srv
, con
);
629 n
-= (sizeof("Digest ")-1);
631 buffer_copy_string_len(b
,ds
->value
->ptr
+sizeof("Digest ")-1,n
);
634 /* parse credentials from client */
635 for (c
= b
->ptr
; *c
; c
++) {
636 /* skip whitespaces */
637 while (*c
== ' ' || *c
== '\t') c
++;
640 for (i
= 0; dkv
[i
].key
; i
++) {
641 if ((0 == strncmp(c
, dkv
[i
].key
, dkv
[i
].key_len
))) {
642 if ((c
[dkv
[i
].key_len
] == '"') &&
643 (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
+ 1, '"')))) {
644 /* value with "..." */
645 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
+ 1;
649 } else if (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
, ','))) {
650 /* value without "...", terminated by ',' */
651 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
656 /* value without "...", terminated by EOL */
657 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
665 /* check if everything is transmitted */
670 (qop
&& (!nc
|| !cnonce
)) ||
674 log_error_write(srv
, __FILE__
, __LINE__
, "s",
675 "digest: missing field");
678 return mod_auth_send_400_bad_request(srv
, con
);
682 * protect the md5-sess against missing cnonce and nonce
685 0 == strcasecmp(algorithm
, "md5-sess") &&
686 (!nonce
|| !cnonce
)) {
687 log_error_write(srv
, __FILE__
, __LINE__
, "s",
688 "digest: (md5-sess: missing field");
691 return mod_auth_send_400_bad_request(srv
, con
);
694 if (qop
&& strcasecmp(qop
, "auth-int") == 0) {
695 log_error_write(srv
, __FILE__
, __LINE__
, "s",
696 "digest: qop=auth-int not supported");
699 return mod_auth_send_400_bad_request(srv
, con
);
702 m
= get_http_method_name(con
->request
.http_method
);
705 /* detect if attacker is attempting to reuse valid digest for one uri
706 * on a different request uri. Might also happen if intermediate proxy
707 * altered client request line. (Altered request would not result in
708 * the same digest as that calculated by the client.)
709 * Internal redirects such as with mod_rewrite will modify request uri.
710 * Reauthentication is done to detect crossing auth realms, but this
711 * uri validation step is bypassed. con->request.orig_uri is original
712 * uri sent in client request. */
714 const size_t ulen
= strlen(uri
);
715 const size_t rlen
= buffer_string_length(con
->request
.orig_uri
);
716 if (!buffer_is_equal_string(con
->request
.orig_uri
, uri
, ulen
)
717 && !(rlen
< ulen
&& 0 == memcmp(con
->request
.orig_uri
->ptr
, uri
, rlen
) && uri
[rlen
] == '?')) {
718 log_error_write(srv
, __FILE__
, __LINE__
, "sbsssB",
719 "digest: auth failed: uri mismatch (", con
->request
.orig_uri
, "!=", uri
, "), IP:", con
->dst_addr_buf
);
721 return mod_auth_send_400_bad_request(srv
, con
);
725 /* password-string == HA1 */
726 switch (backend
->digest(srv
, con
, backend
->p_d
, username
, realm
, HA1
)) {
729 case HANDLER_WAIT_FOR_EVENT
:
731 return HANDLER_WAIT_FOR_EVENT
;
732 case HANDLER_FINISHED
:
734 return HANDLER_FINISHED
;
738 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
742 strcasecmp(algorithm
, "md5-sess") == 0) {
743 li_MD5_Init(&Md5Ctx
);
744 /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
746 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
747 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
748 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
749 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
750 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
751 li_MD5_Final(HA1
, &Md5Ctx
);
756 /* calculate H(A2) */
757 li_MD5_Init(&Md5Ctx
);
758 li_MD5_Update(&Md5Ctx
, (unsigned char *)m
, strlen(m
));
759 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
760 li_MD5_Update(&Md5Ctx
, (unsigned char *)uri
, strlen(uri
));
761 /* qop=auth-int not supported, already checked above */
763 if (qop && strcasecmp(qop, "auth-int") == 0) {
764 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
765 li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
768 li_MD5_Final(HA2
, &Md5Ctx
);
769 CvtHex(HA2
, &HA2Hex
);
771 /* calculate response */
772 li_MD5_Init(&Md5Ctx
);
773 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
774 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
775 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
776 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
778 li_MD5_Update(&Md5Ctx
, (unsigned char *)nc
, strlen(nc
));
779 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
780 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
781 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
782 li_MD5_Update(&Md5Ctx
, (unsigned char *)qop
, strlen(qop
));
783 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
785 li_MD5_Update(&Md5Ctx
, (unsigned char *)HA2Hex
, HASHHEXLEN
);
786 li_MD5_Final(RespHash
, &Md5Ctx
);
787 CvtHex(RespHash
, &a2
);
789 if (0 != strcmp(a2
, respons
)) {
791 log_error_write(srv
, __FILE__
, __LINE__
, "sssB",
792 "digest: auth failed for ", username
, ": wrong password, IP:", con
->dst_addr_buf
);
795 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
798 /* value is our allow-rules */
799 if (!http_auth_match_rules(require
, username
, NULL
, NULL
)) {
801 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
804 /* check age of nonce. Note, random data is used in nonce generation
805 * in mod_auth_send_401_unauthorized_digest(). If that were replaced
806 * with nanosecond time, then nonce secret would remain unique enough
807 * for the purposes of Digest auth, and would be reproducible (and
808 * verifiable) if nanoseconds were inclued with seconds as part of the
809 * nonce "timestamp:secret". Since that is not done, timestamp in
810 * nonce could theoretically be modified and still produce same md5sum,
811 * but that is highly unlikely within a 10 min (moving) window of valid
812 * time relative to current time (now) */
815 const unsigned char * const nonce_uns
= (unsigned char *)nonce
;
816 for (i
= 0; i
< 8 && light_isxdigit(nonce_uns
[i
]); ++i
) {
817 ts
= (ts
<< 4) + hex2int(nonce_uns
[i
]);
820 || ts
> srv
->cur_ts
|| srv
->cur_ts
- ts
> 600) { /*(10 mins)*/
821 /* nonce is stale; have client regenerate digest */
823 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 1);
824 } /*(future: might send nextnonce when expiration is imminent)*/
827 http_auth_setenv(con
->environment
, username
, strlen(username
), CONST_STR_LEN("Digest"));
831 return HANDLER_GO_ON
;
834 static handler_t
mod_auth_send_401_unauthorized_digest(server
*srv
, connection
*con
, buffer
*realm
, int nonce_stale
) {
839 force_assert(33 >= LI_ITOSTRING_LENGTH
); /*(buffer used for both li_itostrn() and CvtHex())*/
843 /* generate shared-secret */
844 li_MD5_Init(&Md5Ctx
);
846 li_itostrn(hh
, sizeof(hh
), srv
->cur_ts
);
847 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
848 li_itostrn(hh
, sizeof(hh
), li_rand_pseudo());
849 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
851 li_MD5_Final(h
, &Md5Ctx
);
855 /* generate WWW-Authenticate */
857 con
->http_status
= 401;
860 buffer_copy_string_len(srv
->tmp_buf
, CONST_STR_LEN("Digest realm=\""));
861 buffer_append_string_buffer(srv
->tmp_buf
, realm
);
862 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", charset=\"UTF-8\", nonce=\""));
863 buffer_append_uint_hex(srv
->tmp_buf
, (uintmax_t)srv
->cur_ts
);
864 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN(":"));
865 buffer_append_string(srv
->tmp_buf
, hh
);
866 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", qop=\"auth\""));
868 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN(", stale=true"));
871 response_header_insert(srv
, con
, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv
->tmp_buf
));
873 return HANDLER_FINISHED
;
876 static handler_t
mod_auth_check_extern(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
) {
877 /* require REMOTE_USER already set */
878 data_string
*ds
= (data_string
*)array_get_element(con
->environment
, "REMOTE_USER");
882 if (NULL
!= ds
&& http_auth_match_rules(require
, ds
->value
->ptr
, NULL
, NULL
)) {
883 return HANDLER_GO_ON
;
885 con
->http_status
= 401;
887 return HANDLER_FINISHED
;