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
;
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"
420 static handler_t
mod_auth_send_400_bad_request(server
*srv
, connection
*con
) {
423 /* a field was missing or invalid */
424 con
->http_status
= 400; /* Bad Request */
427 return HANDLER_FINISHED
;
430 static handler_t
mod_auth_send_401_unauthorized_basic(server
*srv
, connection
*con
, buffer
*realm
) {
431 con
->http_status
= 401;
434 buffer_copy_string_len(srv
->tmp_buf
, CONST_STR_LEN("Basic realm=\""));
435 buffer_append_string_buffer(srv
->tmp_buf
, realm
);
436 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", charset=\"UTF-8\""));
438 response_header_insert(srv
, con
, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv
->tmp_buf
));
440 return HANDLER_FINISHED
;
443 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
) {
444 data_string
*ds
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
448 handler_t rc
= HANDLER_UNSET
;
452 if (NULL
== backend
) {
453 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not configured for", con
->uri
.path
);
454 con
->http_status
= 500;
456 return HANDLER_FINISHED
;
459 if (NULL
== ds
|| buffer_is_empty(ds
->value
)) {
460 return mod_auth_send_401_unauthorized_basic(srv
, con
, require
->realm
);
463 if (0 != strncasecmp(ds
->value
->ptr
, "Basic ", sizeof("Basic ")-1)) {
464 return mod_auth_send_400_bad_request(srv
, con
);
467 username
= buffer_init();
470 /* coverity[overflow_sink : FALSE] */
471 if (!buffer_append_base64_decode(username
, b
->ptr
+sizeof("Basic ")-1, buffer_string_length(b
)-(sizeof("Basic ")-1), BASE64_STANDARD
)) {
472 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "decoding base64-string failed", username
);
474 buffer_free(username
);
475 return mod_auth_send_400_bad_request(srv
, con
);
478 /* r2 == user:password */
479 if (NULL
== (pw
= strchr(username
->ptr
, ':'))) {
480 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "missing ':' in", username
);
482 buffer_free(username
);
483 return mod_auth_send_400_bad_request(srv
, con
);
486 buffer_string_set_length(username
, pw
- username
->ptr
);
489 rc
= backend
->basic(srv
, con
, backend
->p_d
, require
, username
, pw
);
492 http_auth_setenv(con
->environment
, CONST_BUF_LEN(username
), CONST_STR_LEN("Basic"));
494 case HANDLER_WAIT_FOR_EVENT
:
495 case HANDLER_FINISHED
:
499 log_error_write(srv
, __FILE__
, __LINE__
, "sbsBsB", "password doesn't match for", con
->uri
.path
, "username:", username
, ", IP:", con
->dst_addr_buf
);
504 buffer_free(username
);
505 return (HANDLER_UNSET
!= rc
) ? rc
: mod_auth_send_401_unauthorized_basic(srv
, con
, require
->realm
);
509 #define HASHHEXLEN 32
510 typedef unsigned char HASH
[HASHLEN
];
511 typedef char HASHHEX
[HASHHEXLEN
+1];
513 static void CvtHex(const HASH Bin
, char (*Hex
)[33]) {
514 li_tohex(*Hex
, sizeof(*Hex
), (const char*) Bin
, 16);
523 static handler_t
mod_auth_send_401_unauthorized_digest(server
*srv
, connection
*con
, buffer
*realm
, int nonce_stale
);
525 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
) {
526 data_string
*ds
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
531 char *username
= NULL
;
535 char *algorithm
= NULL
;
539 char *respons
= NULL
;
542 const char *m
= NULL
;
556 digest_kv dkv
[10] = {
571 dkv
[0].ptr
= &username
;
575 dkv
[4].ptr
= &algorithm
;
577 dkv
[6].ptr
= &cnonce
;
579 dkv
[8].ptr
= &respons
;
583 if (NULL
== backend
) {
584 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "auth.backend not configured for", con
->uri
.path
);
585 con
->http_status
= 500;
587 return HANDLER_FINISHED
;
590 if (NULL
== ds
|| buffer_is_empty(ds
->value
)) {
591 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
594 if (0 != strncasecmp(ds
->value
->ptr
, "Digest ", sizeof("Digest ")-1)) {
595 return mod_auth_send_400_bad_request(srv
, con
);
599 /* coverity[overflow_sink : FALSE] */
600 buffer_copy_string_len(b
, ds
->value
->ptr
+sizeof("Digest ")-1, buffer_string_length(ds
->value
)-(sizeof("Digest ")-1));
602 /* parse credentials from client */
603 for (c
= b
->ptr
; *c
; c
++) {
604 /* skip whitespaces */
605 while (*c
== ' ' || *c
== '\t') c
++;
608 for (i
= 0; dkv
[i
].key
; i
++) {
609 if ((0 == strncmp(c
, dkv
[i
].key
, dkv
[i
].key_len
))) {
610 if ((c
[dkv
[i
].key_len
] == '"') &&
611 (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
+ 1, '"')))) {
612 /* value with "..." */
613 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
+ 1;
617 } else if (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
, ','))) {
618 /* value without "...", terminated by ',' */
619 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
624 /* value without "...", terminated by EOL */
625 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
633 /* check if everything is transmitted */
638 (qop
&& (!nc
|| !cnonce
)) ||
642 log_error_write(srv
, __FILE__
, __LINE__
, "s",
643 "digest: missing field");
646 return mod_auth_send_400_bad_request(srv
, con
);
650 * protect the md5-sess against missing cnonce and nonce
653 0 == strcasecmp(algorithm
, "md5-sess") &&
654 (!nonce
|| !cnonce
)) {
655 log_error_write(srv
, __FILE__
, __LINE__
, "s",
656 "digest: (md5-sess: missing field");
659 return mod_auth_send_400_bad_request(srv
, con
);
662 if (qop
&& strcasecmp(qop
, "auth-int") == 0) {
663 log_error_write(srv
, __FILE__
, __LINE__
, "s",
664 "digest: qop=auth-int not supported");
667 return mod_auth_send_400_bad_request(srv
, con
);
670 m
= get_http_method_name(con
->request
.http_method
);
673 /* detect if attacker is attempting to reuse valid digest for one uri
674 * on a different request uri. Might also happen if intermediate proxy
675 * altered client request line. (Altered request would not result in
676 * the same digest as that calculated by the client.)
677 * Internal redirects such as with mod_rewrite will modify request uri.
678 * Reauthentication is done to detect crossing auth realms, but this
679 * uri validation step is bypassed. con->request.orig_uri is original
680 * uri sent in client request. */
682 const size_t ulen
= strlen(uri
);
683 const size_t rlen
= buffer_string_length(con
->request
.orig_uri
);
684 if (!buffer_is_equal_string(con
->request
.orig_uri
, uri
, ulen
)
685 && !(rlen
< ulen
&& 0 == memcmp(con
->request
.orig_uri
->ptr
, uri
, rlen
) && uri
[rlen
] == '?')) {
686 log_error_write(srv
, __FILE__
, __LINE__
, "sbsssB",
687 "digest: auth failed: uri mismatch (", con
->request
.orig_uri
, "!=", uri
, "), IP:", con
->dst_addr_buf
);
689 return mod_auth_send_400_bad_request(srv
, con
);
693 /* password-string == HA1 */
694 switch (backend
->digest(srv
, con
, backend
->p_d
, username
, realm
, HA1
)) {
697 case HANDLER_WAIT_FOR_EVENT
:
699 return HANDLER_WAIT_FOR_EVENT
;
700 case HANDLER_FINISHED
:
702 return HANDLER_FINISHED
;
706 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
710 strcasecmp(algorithm
, "md5-sess") == 0) {
711 li_MD5_Init(&Md5Ctx
);
712 /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
714 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
715 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
716 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
717 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
718 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
719 li_MD5_Final(HA1
, &Md5Ctx
);
724 /* calculate H(A2) */
725 li_MD5_Init(&Md5Ctx
);
726 li_MD5_Update(&Md5Ctx
, (unsigned char *)m
, strlen(m
));
727 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
728 li_MD5_Update(&Md5Ctx
, (unsigned char *)uri
, strlen(uri
));
729 /* qop=auth-int not supported, already checked above */
731 if (qop && strcasecmp(qop, "auth-int") == 0) {
732 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
733 li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
736 li_MD5_Final(HA2
, &Md5Ctx
);
737 CvtHex(HA2
, &HA2Hex
);
739 /* calculate response */
740 li_MD5_Init(&Md5Ctx
);
741 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
742 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
743 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
744 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
746 li_MD5_Update(&Md5Ctx
, (unsigned char *)nc
, strlen(nc
));
747 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
748 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
749 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
750 li_MD5_Update(&Md5Ctx
, (unsigned char *)qop
, strlen(qop
));
751 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
753 li_MD5_Update(&Md5Ctx
, (unsigned char *)HA2Hex
, HASHHEXLEN
);
754 li_MD5_Final(RespHash
, &Md5Ctx
);
755 CvtHex(RespHash
, &a2
);
757 if (0 != strcmp(a2
, respons
)) {
759 log_error_write(srv
, __FILE__
, __LINE__
, "sssB",
760 "digest: auth failed for ", username
, ": wrong password, IP:", con
->dst_addr_buf
);
763 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
766 /* value is our allow-rules */
767 if (!http_auth_match_rules(require
, username
, NULL
, NULL
)) {
769 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 0);
772 /* check age of nonce. Note that rand() is used in nonce generation
773 * in mod_auth_send_401_unauthorized_digest(). If that were replaced
774 * with nanosecond time, then nonce secret would remain unique enough
775 * for the purposes of Digest auth, and would be reproducible (and
776 * verifiable) if nanoseconds were inclued with seconds as part of the
777 * nonce "timestamp:secret". Since that is not done, timestamp in
778 * nonce could theoretically be modified and still produce same md5sum,
779 * but that is highly unlikely within a 10 min (moving) window of valid
780 * time relative to current time (now) */
783 const unsigned char * const nonce_uns
= (unsigned char *)nonce
;
784 for (i
= 0; i
< 8 && light_isxdigit(nonce_uns
[i
]); ++i
) {
785 ts
= (ts
<< 4) + hex2int(nonce_uns
[i
]);
787 if (i
!= 8 || nonce
[8] != ':'
788 || ts
> srv
->cur_ts
|| srv
->cur_ts
- ts
> 600) { /*(10 mins)*/
789 /* nonce is stale; have client regenerate digest */
791 return mod_auth_send_401_unauthorized_digest(srv
, con
, require
->realm
, 1);
792 } /*(future: might send nextnonce when expiration is imminent)*/
795 http_auth_setenv(con
->environment
, username
, strlen(username
), CONST_STR_LEN("Digest"));
799 return HANDLER_GO_ON
;
802 static handler_t
mod_auth_send_401_unauthorized_digest(server
*srv
, connection
*con
, buffer
*realm
, int nonce_stale
) {
807 force_assert(33 >= LI_ITOSTRING_LENGTH
); /*(buffer used for both li_itostrn() and CvtHex())*/
811 /* using unknown contents of srv->tmp_buf (modified elsewhere)
812 * adds dubious amount of randomness. Remove use of srv->tmp_buf in nonce? */
814 /* generate shared-secret */
815 li_MD5_Init(&Md5Ctx
);
816 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(srv
->tmp_buf
)); /*(dubious randomness)*/
817 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN("+"));
819 /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
820 li_itostrn(hh
, sizeof(hh
), srv
->cur_ts
);
821 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
822 li_MD5_Update(&Md5Ctx
, (unsigned char *)srv
->entropy
, sizeof(srv
->entropy
));
823 li_itostrn(hh
, sizeof(hh
), rand());
824 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
826 li_MD5_Final(h
, &Md5Ctx
);
830 /* generate WWW-Authenticate */
832 con
->http_status
= 401;
835 buffer_copy_string_len(srv
->tmp_buf
, CONST_STR_LEN("Digest realm=\""));
836 buffer_append_string_buffer(srv
->tmp_buf
, realm
);
837 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", charset=\"UTF-8\", nonce=\""));
838 buffer_append_uint_hex(srv
->tmp_buf
, (uintmax_t)srv
->cur_ts
);
839 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN(":"));
840 buffer_append_string(srv
->tmp_buf
, hh
);
841 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN("\", qop=\"auth\""));
843 buffer_append_string_len(srv
->tmp_buf
, CONST_STR_LEN(", stale=true"));
846 response_header_insert(srv
, con
, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(srv
->tmp_buf
));
848 return HANDLER_FINISHED
;
851 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
) {
852 /* require REMOTE_USER already set */
853 data_string
*ds
= (data_string
*)array_get_element(con
->environment
, "REMOTE_USER");
857 if (NULL
!= ds
&& http_auth_match_rules(require
, ds
->value
->ptr
, NULL
, NULL
)) {
858 return HANDLER_GO_ON
;
860 con
->http_status
= 401;
862 return HANDLER_FINISHED
;