17 buffer
*auth_backend_conf
;
20 const http_auth_backend_t
*auth_backend
;
26 plugin_config
**config_storage
;
31 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
);
32 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
);
33 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
);
35 INIT_FUNC(mod_auth_init
) {
36 static const http_auth_scheme_t http_auth_scheme_basic
= { "basic", mod_auth_check_basic
, NULL
};
37 static const http_auth_scheme_t http_auth_scheme_digest
= { "digest", mod_auth_check_digest
, NULL
};
38 static const http_auth_scheme_t http_auth_scheme_extern
= { "extern", mod_auth_check_extern
, NULL
};
41 /* register http_auth_scheme_* */
42 http_auth_scheme_set(&http_auth_scheme_basic
);
43 http_auth_scheme_set(&http_auth_scheme_digest
);
44 http_auth_scheme_set(&http_auth_scheme_extern
);
46 p
= calloc(1, sizeof(*p
));
51 FREE_FUNC(mod_auth_free
) {
56 if (!p
) return HANDLER_GO_ON
;
58 if (p
->config_storage
) {
60 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
61 plugin_config
*s
= p
->config_storage
[i
];
63 if (NULL
== s
) continue;
65 array_free(s
->auth_require
);
66 buffer_free(s
->auth_backend_conf
);
70 free(p
->config_storage
);
78 /* data type for mod_auth structured data
79 * (parsed from auth.require array of strings) */
82 http_auth_require_t
*require
;
85 static void data_auth_free(data_unset
*d
)
87 data_auth
* const dauth
= (data_auth
*)d
;
88 buffer_free(dauth
->key
);
89 http_auth_require_free(dauth
->require
);
93 static data_auth
*data_auth_init(void)
95 data_auth
* const dauth
= calloc(1, sizeof(*dauth
));
96 force_assert(NULL
!= dauth
);
97 dauth
->copy
= NULL
; /* must not be called on this data */
98 dauth
->free
= data_auth_free
;
99 dauth
->reset
= NULL
; /* must not be called on this data */
100 dauth
->insert_dup
= NULL
; /* must not be called on this data */
101 dauth
->print
= NULL
; /* must not be called on this data */
102 dauth
->type
= TYPE_OTHER
;
104 dauth
->key
= buffer_init();
105 dauth
->require
= http_auth_require_init();
110 static int mod_auth_require_parse (server
*srv
, http_auth_require_t
* const require
, const buffer
*b
)
112 /* user=name1|user=name2|group=name3|host=name4 */
114 const char *str
= b
->ptr
;
117 if (buffer_is_equal_string(b
, CONST_STR_LEN("valid-user"))) {
118 require
->valid_user
= 1;
119 return 1; /* success */
125 p
= strchr(str
, '|');
126 len
= NULL
!= p
? (size_t)(p
- str
) : strlen(str
);
127 eq
= memchr(str
, '=', len
);
129 log_error_write(srv
, __FILE__
, __LINE__
, "sssbss",
130 "error parsing auth.require 'require' field: missing '='",
131 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
132 "error value:", b
, "error near:", str
);
136 log_error_write(srv
, __FILE__
, __LINE__
, "sssbss",
137 "error parsing auth.require 'require' field: missing token after '='",
138 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
139 "error value:", b
, "error near:", str
);
143 switch ((int)(eq
- str
)) {
145 if (0 == memcmp(str
, CONST_STR_LEN("user"))) {
146 data_string
*ds
= data_string_init();
147 buffer_copy_string_len(ds
->key
,str
+5,len
-5); /*("user=" is 5)*/
148 array_insert_unique(require
->user
, (data_unset
*)ds
);
151 else if (0 == memcmp(str
, CONST_STR_LEN("host"))) {
152 data_string
*ds
= data_string_init();
153 buffer_copy_string_len(ds
->key
,str
+5,len
-5); /*("host=" is 5)*/
154 array_insert_unique(require
->host
, (data_unset
*)ds
);
155 log_error_write(srv
, __FILE__
, __LINE__
, "ssb",
156 "warning parsing auth.require 'require' field: 'host' not implemented;",
160 break; /* to error */
162 if (0 == memcmp(str
, CONST_STR_LEN("group"))) {
163 data_string
*ds
= data_string_init();
164 buffer_copy_string_len(ds
->key
,str
+6,len
-6); /*("group=" is 6)*/
165 array_insert_unique(require
->group
, (data_unset
*)ds
);
166 log_error_write(srv
, __FILE__
, __LINE__
, "ssb",
167 "warning parsing auth.require 'require' field: 'group' not implemented;",
171 break; /* to error */
173 if (0 == memcmp(str
, CONST_STR_LEN("valid-user"))) {
174 log_error_write(srv
, __FILE__
, __LINE__
, "sssb",
175 "error parsing auth.require 'require' field: valid user can not be combined with other require rules",
176 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
180 break; /* to error */
182 break; /* to error */
185 log_error_write(srv
, __FILE__
, __LINE__
, "sssbss",
186 "error parsing auth.require 'require' field: invalid/unsupported token",
187 "(expecting \"valid-user\" or \"user=a|user=b|group=g|host=h\").",
188 "error value:", b
, "error near:", str
);
191 } while (p
&& *((str
= p
+1)));
193 return 1; /* success */
196 SETDEFAULTS_FUNC(mod_auth_set_defaults
) {
197 plugin_data
*p
= p_d
;
200 config_values_t cv
[] = {
201 { "auth.backend", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
202 { "auth.require", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
203 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
206 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
208 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
209 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
214 s
= calloc(1, sizeof(plugin_config
));
215 s
->auth_backend_conf
= buffer_init();
217 s
->auth_require
= array_init();
219 cv
[0].destination
= s
->auth_backend_conf
;
220 cv
[1].destination
= s
->auth_require
; /* T_CONFIG_LOCAL; not modified by config_insert_values_global() */
222 p
->config_storage
[i
] = s
;
224 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
225 return HANDLER_ERROR
;
228 if (!buffer_string_is_empty(s
->auth_backend_conf
)) {
229 s
->auth_backend
= http_auth_backend_get(s
->auth_backend_conf
);
230 if (NULL
== s
->auth_backend
) {
231 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not supported:", s
->auth_backend_conf
);
233 return HANDLER_ERROR
;
237 /* no auth.require for this section */
238 if (NULL
== (da
= (data_array
*)array_get_element(config
->value
, "auth.require"))) continue;
240 if (da
->type
!= TYPE_ARRAY
) continue;
242 for (n
= 0; n
< da
->value
->used
; n
++) {
244 data_array
*da_file
= (data_array
*)da
->value
->data
[n
];
245 const buffer
*method
= NULL
, *realm
= NULL
, *require
= NULL
;
246 const http_auth_scheme_t
*auth_scheme
;
248 if (da
->value
->data
[n
]->type
!= TYPE_ARRAY
) {
249 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
250 "auth.require should contain an array as in:",
251 "auth.require = ( \"...\" => ( ..., ...) )");
253 return HANDLER_ERROR
;
256 for (m
= 0; m
< da_file
->value
->used
; m
++) {
257 if (da_file
->value
->data
[m
]->type
== TYPE_STRING
) {
258 data_string
*ds
= (data_string
*)da_file
->value
->data
[m
];
259 if (buffer_is_equal_string(ds
->key
, CONST_STR_LEN("method"))) {
261 } else if (buffer_is_equal_string(ds
->key
, CONST_STR_LEN("realm"))) {
263 } else if (buffer_is_equal_string(ds
->key
, CONST_STR_LEN("require"))) {
266 log_error_write(srv
, __FILE__
, __LINE__
, "ssbs",
267 "the field is unknown in:",
268 "auth.require = ( \"...\" => ( ..., -> \"",
269 da_file
->value
->data
[m
]->key
,
270 "\" <- => \"...\" ) )");
272 return HANDLER_ERROR
;
275 log_error_write(srv
, __FILE__
, __LINE__
, "ssbs",
276 "a string was expected for:",
277 "auth.require = ( \"...\" => ( ..., -> \"",
278 da_file
->value
->data
[m
]->key
,
279 "\" <- => \"...\" ) )");
281 return HANDLER_ERROR
;
285 if (buffer_string_is_empty(method
)) {
286 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
287 "the method field is missing or blank in:",
288 "auth.require = ( \"...\" => ( ..., \"method\" => \"...\" ) )");
289 return HANDLER_ERROR
;
291 auth_scheme
= http_auth_scheme_get(method
);
292 if (NULL
== auth_scheme
) {
293 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
294 "unknown method", method
, "(e.g. \"basic\", \"digest\" or \"extern\") in",
295 "auth.require = ( \"...\" => ( ..., \"method\" => \"...\") )");
296 return HANDLER_ERROR
;
300 if (buffer_is_empty(realm
)) {
301 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
302 "the realm field is missing in:",
303 "auth.require = ( \"...\" => ( ..., \"realm\" => \"...\" ) )");
304 return HANDLER_ERROR
;
307 if (buffer_string_is_empty(require
)) {
308 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
309 "the require field is missing or blank in:",
310 "auth.require = ( \"...\" => ( ..., \"require\" => \"...\" ) )");
311 return HANDLER_ERROR
;
314 if (require
) { /*(always true at this point)*/
315 data_auth
* const dauth
= data_auth_init();
316 buffer_copy_buffer(dauth
->key
, da_file
->key
);
317 dauth
->require
->scheme
= auth_scheme
;
318 buffer_copy_buffer(dauth
->require
->realm
, realm
);
319 if (!mod_auth_require_parse(srv
, dauth
->require
, require
)) {
320 dauth
->free((data_unset
*)dauth
);
321 return HANDLER_ERROR
;
323 array_insert_unique(s
->auth_require
, (data_unset
*)dauth
);
328 return HANDLER_GO_ON
;
333 static int mod_auth_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
335 plugin_config
*s
= p
->config_storage
[0];
340 /* skip the first, the global context */
341 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
342 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
343 s
= p
->config_storage
[i
];
345 /* condition didn't match */
346 if (!config_check_cond(srv
, con
, dc
)) continue;
349 for (j
= 0; j
< dc
->value
->used
; j
++) {
350 data_unset
*du
= dc
->value
->data
[j
];
352 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend"))) {
354 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.require"))) {
364 static handler_t
mod_auth_uri_handler(server
*srv
, connection
*con
, void *p_d
) {
366 plugin_data
*p
= p_d
;
368 mod_auth_patch_connection(srv
, con
, p
);
370 if (p
->conf
.auth_require
== NULL
) return HANDLER_GO_ON
;
372 /* search auth directives for first prefix match against URL path */
373 for (k
= 0; k
< p
->conf
.auth_require
->used
; k
++) {
374 const data_auth
* const dauth
= (data_auth
*)p
->conf
.auth_require
->data
[k
];
375 const buffer
*path
= dauth
->key
;
377 if (buffer_string_length(con
->uri
.path
) < buffer_string_length(path
)) continue;
379 /* if we have a case-insensitive FS we have to lower-case the URI here too */
381 if (!con
->conf
.force_lowercase_filenames
382 ? 0 == strncmp(con
->uri
.path
->ptr
, path
->ptr
, buffer_string_length(path
))
383 : 0 == strncasecmp(con
->uri
.path
->ptr
, path
->ptr
, buffer_string_length(path
))) {
384 const http_auth_scheme_t
* const scheme
= dauth
->require
->scheme
;
385 return scheme
->checkfn(srv
, con
, scheme
->p_d
, dauth
->require
, p
->conf
.auth_backend
);
389 /* nothing to do for us */
390 return HANDLER_GO_ON
;
393 int mod_auth_plugin_init(plugin
*p
);
394 int mod_auth_plugin_init(plugin
*p
) {
395 p
->version
= LIGHTTPD_VERSION_ID
;
396 p
->name
= buffer_init_string("auth");
397 p
->init
= mod_auth_init
;
398 p
->set_defaults
= mod_auth_set_defaults
;
399 p
->handle_uri_clean
= mod_auth_uri_handler
;
400 p
->cleanup
= mod_auth_free
;
411 * auth schemes (basic, digest, extern)
413 * (could be in separate file from mod_auth.c as long as registration occurs)
416 #include "response.h"
421 static handler_t
mod_auth_send_400_bad_request(server
*srv
, connection
*con
) {
424 /* a field was missing or invalid */
425 con
->http_status
= 400; /* Bad Request */
428 return HANDLER_FINISHED
;
431 static handler_t
mod_auth_send_401_unauthorized_basic(server
*srv
, connection
*con
, buffer
*realm
) {
432 con
->http_status
= 401;
435 buffer_copy_string_len(srv
->tmp_buf
, CONST_STR_LEN("Basic realm=\""));
436 buffer_append_string_buffer(srv
->tmp_buf
, realm
);
437 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", charset=\"UTF-8\""));
439 response_header_insert(srv
, con
, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv
->tmp_buf
));
441 return HANDLER_FINISHED
;
444 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
) {
445 data_string
*ds
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
449 handler_t rc
= HANDLER_UNSET
;
453 if (NULL
== backend
) {
454 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not configured for", con
->uri
.path
);
455 con
->http_status
= 500;
457 return HANDLER_FINISHED
;
460 if (NULL
== ds
|| buffer_is_empty(ds
->value
)) {
461 return mod_auth_send_401_unauthorized_basic(srv
, con
, require
->realm
);
464 if (0 != strncasecmp(ds
->value
->ptr
, "Basic ", sizeof("Basic ")-1)) {
465 return mod_auth_send_400_bad_request(srv
, con
);
468 username
= buffer_init();
471 /* coverity[overflow_sink : FALSE] */
472 if (!buffer_append_base64_decode(username
, b
->ptr
+sizeof("Basic ")-1, buffer_string_length(b
)-(sizeof("Basic ")-1), BASE64_STANDARD
)) {
473 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "decoding base64-string failed", username
);
475 buffer_free(username
);
476 return mod_auth_send_400_bad_request(srv
, con
);
479 /* r2 == user:password */
480 if (NULL
== (pw
= strchr(username
->ptr
, ':'))) {
481 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "missing ':' in", username
);
483 buffer_free(username
);
484 return mod_auth_send_400_bad_request(srv
, con
);
487 buffer_string_set_length(username
, pw
- username
->ptr
);
490 rc
= backend
->basic(srv
, con
, backend
->p_d
, require
, username
, pw
);
493 http_auth_setenv(con
->environment
, CONST_BUF_LEN(username
), CONST_STR_LEN("Basic"));
495 case HANDLER_WAIT_FOR_EVENT
:
496 case HANDLER_FINISHED
:
500 log_error_write(srv
, __FILE__
, __LINE__
, "sbsBsB", "password doesn't match for", con
->uri
.path
, "username:", username
, ", IP:", con
->dst_addr_buf
);
505 buffer_free(username
);
506 return (HANDLER_UNSET
!= rc
) ? rc
: mod_auth_send_401_unauthorized_basic(srv
, con
, require
->realm
);
510 #define HASHHEXLEN 32
511 typedef unsigned char HASH
[HASHLEN
];
512 typedef char HASHHEX
[HASHHEXLEN
+1];
514 static void CvtHex(const HASH Bin
, char (*Hex
)[33]) {
515 li_tohex(*Hex
, sizeof(*Hex
), (const char*) Bin
, 16);
524 static handler_t
mod_auth_send_401_unauthorized_digest(server
*srv
, connection
*con
, buffer
*realm
, int nonce_stale
);
526 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
) {
527 data_string
*ds
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
532 char *username
= NULL
;
536 char *algorithm
= NULL
;
540 char *respons
= NULL
;
543 const char *m
= NULL
;
557 digest_kv dkv
[10] = {
572 dkv
[0].ptr
= &username
;
576 dkv
[4].ptr
= &algorithm
;
578 dkv
[6].ptr
= &cnonce
;
580 dkv
[8].ptr
= &respons
;
584 if (NULL
== backend
) {
585 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not configured for", con
->uri
.path
);
586 con
->http_status
= 500;
588 return HANDLER_FINISHED
;
591 if (NULL
== ds
|| buffer_is_empty(ds
->value
)) {
592 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
595 if (0 != strncasecmp(ds
->value
->ptr
, "Digest ", sizeof("Digest ")-1)) {
596 return mod_auth_send_400_bad_request(srv
, con
);
600 /* coverity[overflow_sink : FALSE] */
601 buffer_copy_string_len(b
, ds
->value
->ptr
+sizeof("Digest ")-1, buffer_string_length(ds
->value
)-(sizeof("Digest ")-1));
603 /* parse credentials from client */
604 for (c
= b
->ptr
; *c
; c
++) {
605 /* skip whitespaces */
606 while (*c
== ' ' || *c
== '\t') c
++;
609 for (i
= 0; dkv
[i
].key
; i
++) {
610 if ((0 == strncmp(c
, dkv
[i
].key
, dkv
[i
].key_len
))) {
611 if ((c
[dkv
[i
].key_len
] == '"') &&
612 (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
+ 1, '"')))) {
613 /* value with "..." */
614 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
+ 1;
618 } else if (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
, ','))) {
619 /* value without "...", terminated by ',' */
620 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
625 /* value without "...", terminated by EOL */
626 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
634 /* check if everything is transmitted */
639 (qop
&& (!nc
|| !cnonce
)) ||
643 log_error_write(srv
, __FILE__
, __LINE__
, "s",
644 "digest: missing field");
647 return mod_auth_send_400_bad_request(srv
, con
);
651 * protect the md5-sess against missing cnonce and nonce
654 0 == strcasecmp(algorithm
, "md5-sess") &&
655 (!nonce
|| !cnonce
)) {
656 log_error_write(srv
, __FILE__
, __LINE__
, "s",
657 "digest: (md5-sess: missing field");
660 return mod_auth_send_400_bad_request(srv
, con
);
663 if (qop
&& strcasecmp(qop
, "auth-int") == 0) {
664 log_error_write(srv
, __FILE__
, __LINE__
, "s",
665 "digest: qop=auth-int not supported");
668 return mod_auth_send_400_bad_request(srv
, con
);
671 m
= get_http_method_name(con
->request
.http_method
);
674 /* detect if attacker is attempting to reuse valid digest for one uri
675 * on a different request uri. Might also happen if intermediate proxy
676 * altered client request line. (Altered request would not result in
677 * the same digest as that calculated by the client.)
678 * Internal redirects such as with mod_rewrite will modify request uri.
679 * Reauthentication is done to detect crossing auth realms, but this
680 * uri validation step is bypassed. con->request.orig_uri is original
681 * uri sent in client request. */
683 const size_t ulen
= strlen(uri
);
684 const size_t rlen
= buffer_string_length(con
->request
.orig_uri
);
685 if (!buffer_is_equal_string(con
->request
.orig_uri
, uri
, ulen
)
686 && !(rlen
< ulen
&& 0 == memcmp(con
->request
.orig_uri
->ptr
, uri
, rlen
) && uri
[rlen
] == '?')) {
687 log_error_write(srv
, __FILE__
, __LINE__
, "sbsssB",
688 "digest: auth failed: uri mismatch (", con
->request
.orig_uri
, "!=", uri
, "), IP:", con
->dst_addr_buf
);
690 return mod_auth_send_400_bad_request(srv
, con
);
694 /* password-string == HA1 */
695 switch (backend
->digest(srv
, con
, backend
->p_d
, username
, realm
, HA1
)) {
698 case HANDLER_WAIT_FOR_EVENT
:
700 return HANDLER_WAIT_FOR_EVENT
;
701 case HANDLER_FINISHED
:
703 return HANDLER_FINISHED
;
707 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
711 strcasecmp(algorithm
, "md5-sess") == 0) {
712 li_MD5_Init(&Md5Ctx
);
713 /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
715 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
716 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
717 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
718 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
719 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
720 li_MD5_Final(HA1
, &Md5Ctx
);
725 /* calculate H(A2) */
726 li_MD5_Init(&Md5Ctx
);
727 li_MD5_Update(&Md5Ctx
, (unsigned char *)m
, strlen(m
));
728 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
729 li_MD5_Update(&Md5Ctx
, (unsigned char *)uri
, strlen(uri
));
730 /* qop=auth-int not supported, already checked above */
732 if (qop && strcasecmp(qop, "auth-int") == 0) {
733 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
734 li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
737 li_MD5_Final(HA2
, &Md5Ctx
);
738 CvtHex(HA2
, &HA2Hex
);
740 /* calculate response */
741 li_MD5_Init(&Md5Ctx
);
742 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
743 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
744 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
745 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
747 li_MD5_Update(&Md5Ctx
, (unsigned char *)nc
, strlen(nc
));
748 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
749 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
750 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
751 li_MD5_Update(&Md5Ctx
, (unsigned char *)qop
, strlen(qop
));
752 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
754 li_MD5_Update(&Md5Ctx
, (unsigned char *)HA2Hex
, HASHHEXLEN
);
755 li_MD5_Final(RespHash
, &Md5Ctx
);
756 CvtHex(RespHash
, &a2
);
758 if (0 != strcmp(a2
, respons
)) {
760 log_error_write(srv
, __FILE__
, __LINE__
, "sssB",
761 "digest: auth failed for ", username
, ": wrong password, IP:", con
->dst_addr_buf
);
764 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
767 /* value is our allow-rules */
768 if (!http_auth_match_rules(require
, username
, NULL
, NULL
)) {
770 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
773 /* check age of nonce. Note, random data is used in nonce generation
774 * in mod_auth_send_401_unauthorized_digest(). If that were replaced
775 * with nanosecond time, then nonce secret would remain unique enough
776 * for the purposes of Digest auth, and would be reproducible (and
777 * verifiable) if nanoseconds were inclued with seconds as part of the
778 * nonce "timestamp:secret". Since that is not done, timestamp in
779 * nonce could theoretically be modified and still produce same md5sum,
780 * but that is highly unlikely within a 10 min (moving) window of valid
781 * time relative to current time (now) */
784 const unsigned char * const nonce_uns
= (unsigned char *)nonce
;
785 for (i
= 0; i
< 8 && light_isxdigit(nonce_uns
[i
]); ++i
) {
786 ts
= (ts
<< 4) + hex2int(nonce_uns
[i
]);
788 if (i
!= 8 || nonce
[8] != ':'
789 || ts
> srv
->cur_ts
|| srv
->cur_ts
- ts
> 600) { /*(10 mins)*/
790 /* nonce is stale; have client regenerate digest */
792 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 1);
793 } /*(future: might send nextnonce when expiration is imminent)*/
796 http_auth_setenv(con
->environment
, username
, strlen(username
), CONST_STR_LEN("Digest"));
800 return HANDLER_GO_ON
;
803 static handler_t
mod_auth_send_401_unauthorized_digest(server
*srv
, connection
*con
, buffer
*realm
, int nonce_stale
) {
808 force_assert(33 >= LI_ITOSTRING_LENGTH
); /*(buffer used for both li_itostrn() and CvtHex())*/
812 /* generate shared-secret */
813 li_MD5_Init(&Md5Ctx
);
815 li_itostrn(hh
, sizeof(hh
), srv
->cur_ts
);
816 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
817 li_itostrn(hh
, sizeof(hh
), li_rand_pseudo_bytes());
818 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
820 li_MD5_Final(h
, &Md5Ctx
);
824 /* generate WWW-Authenticate */
826 con
->http_status
= 401;
829 buffer_copy_string_len(srv
->tmp_buf
, CONST_STR_LEN("Digest realm=\""));
830 buffer_append_string_buffer(srv
->tmp_buf
, realm
);
831 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", charset=\"UTF-8\", nonce=\""));
832 buffer_append_uint_hex(srv
->tmp_buf
, (uintmax_t)srv
->cur_ts
);
833 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN(":"));
834 buffer_append_string(srv
->tmp_buf
, hh
);
835 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", qop=\"auth\""));
837 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN(", stale=true"));
840 response_header_insert(srv
, con
, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv
->tmp_buf
));
842 return HANDLER_FINISHED
;
845 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
) {
846 /* require REMOTE_USER already set */
847 data_string
*ds
= (data_string
*)array_get_element(con
->environment
, "REMOTE_USER");
851 if (NULL
!= ds
&& http_auth_match_rules(require
, ds
->value
->ptr
, NULL
, NULL
)) {
852 return HANDLER_GO_ON
;
854 con
->http_status
= 401;
856 return HANDLER_FINISHED
;