1 /*
2 * This file is part of the Sofia-SIP package
3 *
4 * Copyright (C) 2005 Nokia Corporation.
5 *
6 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; either version 2.1 of
11 * the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25 /**@CFILE auth_module.c
26 * @brief Authentication verification module
27 *
28 * The authentication module provides server or proxy-side authentication
29 * verification for network elements like registrars, presence servers, and
30 * proxies.
31 *
32 * @author Pekka Pessi <Pekka.Pessi@nokia.com>.
33 *
34 * @date Created: Wed Apr 11 15:14:03 2001 ppessi
35 */
36
37 #include "config.h"
38
39 #include <stddef.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <stdio.h>
43 #include <assert.h>
44
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <unistd.h>
48
49 #include <sofia-sip/auth_digest.h>
50
51 #include "iptsec_debug.h"
52
53 #if HAVE_FUNC
54 #elif HAVE_FUNCTION
55 #define __func__ __FUNCTION__
56 #else
57 static char const __func__[] = "auth_mod";
58 #endif
59
60 #include <sofia-sip/su_debug.h>
61
62 #include <sofia-sip/su_wait.h>
63 #include <sofia-sip/su_alloc.h>
64 #include <sofia-sip/su_string.h>
65 #include <sofia-sip/su_tagarg.h>
66
67 #include <sofia-sip/base64.h>
68 #include <sofia-sip/su_md5.h>
69
70 #include <sofia-sip/msg_parser.h>
71 #include <sofia-sip/msg_date.h>
72
73 #include "sofia-sip/auth_module.h"
74 #include "sofia-sip/auth_plugin.h"
75
76 #define APW_HASH(apw) ((apw)->apw_index)
77
78 char const auth_internal_server_error[] = "Internal server error";
79
80 static void auth_call_scheme_destructor(void *);
81 static void auth_md5_hmac_key(auth_mod_t *am);
82
83 #ifdef __clang__
84 #pragma clang diagnostic push
85 #pragma clang diagnostic ignored "-Wunused-function"
86 #endif
87
88 HTABLE_PROTOS_WITH(auth_htable, aht, auth_passwd_t, usize_t, unsigned);
89 HTABLE_BODIES_WITH(auth_htable, aht, auth_passwd_t, APW_HASH,
90 usize_t, unsigned);
91
92 #ifdef __clang__
93 #pragma clang diagnostic pop
94 #endif
95
96 /**Allocate an authentication module instance.
97 *
98 * The function auth_mod_alloc() allocates an authentication module object.
99 *
100 */
auth_mod_alloc(auth_scheme_t * scheme,tag_type_t tag,tag_value_t value,...)101 auth_mod_t *auth_mod_alloc(auth_scheme_t *scheme,
102 tag_type_t tag, tag_value_t value, ...)
103 {
104 auth_mod_t *am = NULL;
105
106 if ((am = su_home_new(scheme->asch_size))) {
107 am->am_scheme = scheme;
108 su_home_destructor(am->am_home, auth_call_scheme_destructor);
109 }
110
111 return am;
112 }
113
114 /**Initialize an authentication module instance.
115 *
116 * The function auth_mod_init_default() initializes an authentication module
117 * object used to authenticate the requests.
118 *
119 * @param am
120 * @param base
121 * @param root
122 * @param tag,value,... tagged argument list
123 *
124 * @TAGS
125 * AUTHTAG_REALM(), AUTHTAG_OPAQUE(), AUTHTAG_DB(), AUTHTAG_QOP(),
126 * AUTHTAG_ALGORITHM(), AUTHTAG_EXPIRES(), AUTHTAG_NEXT_EXPIRES(),
127 * AUTHTAG_BLACKLIST(), AUTHTAG_FORBIDDEN(), AUTHTAG_ANONYMOUS(),
128 * AUTHTAG_FAKE(), AUTHTAG_ALLOW(), AUTHTAG_REMOTE(), and
129 * AUTHTAG_MASTER_KEY().
130 *
131 * @return 0 if successful
132 * @return -1 upon an error
133 */
auth_init_default(auth_mod_t * am,auth_scheme_t * base,su_root_t * root,tag_type_t tag,tag_value_t value,...)134 int auth_init_default(auth_mod_t *am,
135 auth_scheme_t *base,
136 su_root_t *root,
137 tag_type_t tag, tag_value_t value, ...)
138 {
139 int retval = 0;
140
141 ta_list ta;
142
143 char const *realm = NULL, *opaque = NULL, *db = NULL, *allows = NULL;
144 char const *qop = NULL, *algorithm = NULL;
145 unsigned expires = 60 * 60, next_expires = 5 * 60;
146 unsigned max_ncount = 0;
147 unsigned blacklist = 5;
148 int forbidden = 0;
149 int anonymous = 0;
150 int fake = 0;
151 url_string_t const *remote = NULL;
152 char const *master_key = "fish";
153 char *s;
154
155 ta_start(ta, tag, value);
156
157 /* Authentication stuff */
158 tl_gets(ta_args(ta),
159 AUTHTAG_REALM_REF(realm),
160 AUTHTAG_OPAQUE_REF(opaque),
161 AUTHTAG_DB_REF(db),
162 AUTHTAG_QOP_REF(qop),
163 AUTHTAG_ALGORITHM_REF(algorithm),
164 AUTHTAG_EXPIRES_REF(expires),
165 AUTHTAG_NEXT_EXPIRES_REF(next_expires),
166 AUTHTAG_MAX_NCOUNT_REF(max_ncount),
167 AUTHTAG_BLACKLIST_REF(blacklist),
168 AUTHTAG_FORBIDDEN_REF(forbidden),
169 AUTHTAG_ANONYMOUS_REF(anonymous),
170 AUTHTAG_FAKE_REF(fake),
171 AUTHTAG_ALLOW_REF(allows),
172 AUTHTAG_REMOTE_REF(remote),
173 AUTHTAG_MASTER_KEY_REF(master_key),
174 TAG_NULL());
175
176 if (!realm) realm = "*";
177 if (!allows) allows = "ACK, BYE, CANCEL";
178
179 am->am_realm = su_strdup(am->am_home, realm);
180 am->am_opaque = su_strdup(am->am_home, opaque);
181 am->am_db = su_strdup(am->am_home, db);
182 s = su_strdup(am->am_home, allows);
183 if (s)
184 msg_commalist_d(am->am_home, &s, &am->am_allow, NULL);
185 am->am_expires = expires;
186 am->am_next_exp = next_expires;
187 am->am_max_ncount = max_ncount;
188 am->am_blacklist = blacklist;
189 am->am_forbidden = forbidden;
190 am->am_anonymous = anonymous;
191 am->am_fake = fake;
192 am->am_remote = url_hdup(am->am_home, (url_t *)remote);
193 am->am_algorithm = algorithm ? su_strdup(am->am_home, algorithm) : "MD5";
194 am->am_nextnonce = !algorithm || su_casematch(algorithm, "MD5");
195 if (next_expires == 0)
196 am->am_nextnonce = 0;
197 am->am_qop = su_strdup(am->am_home, qop);
198
199 if (master_key) {
200 su_md5_t md5[1];
201
202 su_md5_init(md5);
203 su_md5_strupdate(md5, master_key);
204 su_md5_strupdate(md5, "70P 53KR37");
205 su_md5_digest(md5, am->am_master_key);
206 }
207
208 auth_md5_hmac_key(am);
209
210 /* Make sure that we have something
211 that can be used to identify credentials */
212 if (am->am_opaque && strcmp(am->am_opaque, "*") == 0) {
213 #ifndef HOST_NAME_MAX
214 #define HOST_NAME_MAX 255
215 #endif
216 char hostname[HOST_NAME_MAX + 1];
217 su_md5_t md5[1];
218 uint8_t hmac[6];
219
220 gethostname(hostname, sizeof hostname);
221
222 auth_md5_hmac_init(am, md5);
223
224 su_md5_strupdate(md5, hostname);
225 su_md5_update(md5, ":", 1);
226 if (am->am_remote)
227 url_update(md5, am->am_remote);
228
229 auth_md5_hmac_digest(am, md5, hmac, sizeof hmac);
230
231 base64_e(hostname, sizeof hostname, hmac, sizeof hmac);
232
233 am->am_opaque = su_strdup(am->am_home, hostname);
234
235 if (!am->am_opaque) {
236 retval = -1;
237 SU_DEBUG_1(("%s: cannot create unique identifier\n", __func__));
238 }
239 }
240
241 if (retval < 0)
242 ;
243 else if (db) {
244 retval = auth_readdb(am);
245 if (retval == -1) {
246 int err = errno;
247 SU_DEBUG_1(("auth-module: %s: %s\n", am->am_db, strerror(err)));
248 errno = err;
249 }
250 }
251 else {
252 retval = auth_htable_resize(am->am_home, am->am_users, 0);
253 }
254
255 ta_end(ta);
256
257 return retval;
258 }
259
260 /** Destroy (a reference to) an authentication module. */
auth_mod_destroy(auth_mod_t * am)261 void auth_mod_destroy(auth_mod_t *am)
262 {
263 su_home_unref(am->am_home);
264 }
265
266 /** Call scheme-specific destructor function. */
auth_call_scheme_destructor(void * arg)267 static void auth_call_scheme_destructor(void *arg)
268 {
269 auth_mod_t *am = arg;
270 am->am_scheme->asch_destroy(am);
271 }
272
273 /** Do-nothing destroy function.
274 *
275 * The auth_destroy_default() is the default member function called by
276 * auth_mod_destroy().
277 */
auth_destroy_default(auth_mod_t * am)278 void auth_destroy_default(auth_mod_t *am)
279 {
280 }
281
282 /** Create a new reference to authentication module. */
auth_mod_ref(auth_mod_t * am)283 auth_mod_t *auth_mod_ref(auth_mod_t *am)
284 {
285 return (auth_mod_t *)su_home_ref(am->am_home);
286 }
287
288 /** Destroy a reference to an authentication module. */
auth_mod_unref(auth_mod_t * am)289 void auth_mod_unref(auth_mod_t *am)
290 {
291 su_home_unref(am->am_home);
292 }
293
294 /** Get authenticatin module name. @NEW_1_12_4. */
auth_mod_name(auth_mod_t * am)295 char const *auth_mod_name(auth_mod_t *am)
296 {
297 return am ? am->am_scheme->asch_method : "<nil>";
298 }
299
300 /** Initialize a auth_status_t stucture.
301 *
302 * @retval NULL upon an error
303 * @relates auth_status_t
304 */
auth_status_init(void * p,isize_t size)305 auth_status_t *auth_status_init(void *p, isize_t size)
306 {
307 return auth_status_init_with(p, size, 500, auth_internal_server_error);
308 }
309
310 /** Initialize a auth_status_t stucture.
311 *
312 * @retval NULL upon an error
313 * @relates auth_status_t
314 */
auth_status_init_with(void * p,isize_t size,int status,char const * phrase)315 auth_status_t *auth_status_init_with(void *p,
316 isize_t size,
317 int status,
318 char const *phrase)
319 {
320 auth_status_t *as;
321
322 if (!p || size < (sizeof *as))
323 return NULL;
324
325 if (size > INT_MAX) size = INT_MAX;
326
327 as = memset(p, 0, size);
328 as->as_home->suh_size = (int)size;
329
330 /* su_home_init(as->as_home); */
331
332 as->as_status = status, as->as_phrase = phrase;
333
334 return as;
335 }
336
337 /** Allocate a new auth_status_t structure. @relates auth_status_t */
338
auth_status_new(su_home_t * home)339 auth_status_t *auth_status_new(su_home_t *home)
340 {
341 auth_status_t *as = su_home_clone(home, (sizeof *as));
342 if (as) {
343 as->as_status = 500;
344 as->as_phrase = auth_internal_server_error;
345 }
346 return as;
347 }
348
349 /** Create a new reference to an auth_status_t structure.
350 * @relates auth_status_t
351 */
auth_status_ref(auth_status_t * as)352 auth_status_t *auth_status_ref(auth_status_t *as)
353 {
354 return (auth_status_t *)su_home_ref(as->as_home);
355 }
356
357 /** Destroy (a reference to) an auth_status_t structure. @relates auth_status_t
358 */
auth_status_unref(auth_status_t * as)359 void auth_status_unref(auth_status_t *as)
360 {
361 su_home_unref(as->as_home);
362 }
363
364 /** Authenticate user.
365 *
366 * The function auth_mod_method() invokes scheme-specific authentication
367 * operation where the user's credentials are checked using scheme-specific
368 * method. The authentication result along with an optional challenge header
369 * is stored in the @a as structure.
370 *
371 * @param am pointer to authentication module object [in]
372 * @param as pointer to authentication status structure [in/out]
373 * @param credentials pointer to a header with user's credentials [in]
374 * @param ach pointer to a structure describing challenge [in]
375 *
376 * The @a ach structure defines what kind of response and challenge header
377 * is returned to the user. For example, a server authentication is
378 * implemented with 401 response code and phrase along with WWW-Authenticate
379 * header template in the @a ach structure.
380 *
381 * The auth_mod_method() returns the authentication result in the
382 * #auth_mod_t @a as structure. The @a as->as_status describes the result
383 * as follows:
384 * - <i>as->as_status == 0</i> authentication is successful
385 * - <i>as->as_status == 100</i> authentication is pending
386 * - <i>as->as_status >= 400</i> authentication fails,
387 * return as_status as an error code to client
388 *
389 * When the authentication is left pending, the client must set the
390 * as_callback pointer in @a as structure to an appropriate callback
391 * function. The callback is invoked when the authentication is completed,
392 * either successfully or with an error.
393 *
394 * Note that the authentication module may generate a new challenge each
395 * time authentication is used (e.g., Digest using MD5 algorithm). Such a
396 * challenge header is stored in the @a as->as_response return-value field.
397 *
398 * @note The authentication plugin may use the given reference to @a as, @a
399 * credentials and @a ach structures until the asynchronous authentication
400 * completes. Therefore, they should not be allocated from stack unless
401 * application uses strictly synchronous authentication schemes only (Basic
402 * and Digest).
403 *
404 * @note This function should be called auth_mod_check().
405 */
auth_mod_verify(auth_mod_t * am,auth_status_t * as,msg_auth_t * credentials,auth_challenger_t const * ach)406 void auth_mod_verify(auth_mod_t *am,
407 auth_status_t *as,
408 msg_auth_t *credentials,
409 auth_challenger_t const *ach)
410 {
411 char const *wildcard, *host;
412
413 if (!am || !as || !ach)
414 return;
415
416 wildcard = strchr(am->am_realm, '*');
417
418 host = as->as_domain;
419
420 /* Initialize per-request realm */
421 if (as->as_realm)
422 ;
423 else if (!wildcard) {
424 as->as_realm = am->am_realm;
425 }
426 else if (!host) {
427 return; /* Internal error */
428 }
429 else if (strcmp(am->am_realm, "*") == 0) {
430 as->as_realm = host;
431 }
432 else {
433 /* Replace * with hostpart */
434 as->as_realm = su_sprintf(as->as_home, "%.*s%s%s",
435 (int)(wildcard - am->am_realm), am->am_realm,
436 host,
437 wildcard + 1);
438 }
439
440 am->am_scheme->asch_check(am, as, credentials, ach);
441 }
442
443 /** Make a challenge header.
444 *
445 * This function invokes plugin-specific member function generating a
446 * challenge header. Client uses the challenge header contents when
447 * prompting the user for a username and password then generates its
448 * credential header using the parameters given in the challenge header.
449 *
450 * @param am pointer to authentication module object
451 * @param as pointer to authentication status structure (return-value)
452 * @param ach pointer to a structure describing challenge
453 *
454 * The auth_mod_challenge() returns the challenge header, appropriate
455 * response code and reason phrase in the #auth_status_t structure. The
456 * auth_mod_challenge() is currently always synchronous function.
457 */
auth_mod_challenge(auth_mod_t * am,auth_status_t * as,auth_challenger_t const * ach)458 void auth_mod_challenge(auth_mod_t *am,
459 auth_status_t *as,
460 auth_challenger_t const *ach)
461 {
462 if (am && as && ach)
463 am->am_scheme->asch_challenge(am, as, ach);
464 }
465
466
467 /** Cancel asynchronous authentication.
468 *
469 * The auth_mod_cancel() function cancels a pending authentication.
470 * Application can reclaim the authentication status, credential and
471 * challenger objects by using auth_mod_cancel().
472 */
auth_mod_cancel(auth_mod_t * am,auth_status_t * as)473 void auth_mod_cancel(auth_mod_t *am, auth_status_t *as)
474 {
475 if (am && as)
476 am->am_scheme->asch_cancel(am, as);
477 }
478
479 /** Do-nothing cancel function.
480 *
481 * The auth_cancel_default() is the default member function called by
482 * auth_mod_cancel().
483 */
auth_cancel_default(auth_mod_t * am,auth_status_t * as)484 void auth_cancel_default(auth_mod_t *am, auth_status_t *as)
485 {
486 }
487
488 /* ====================================================================== */
489 /* Basic authentication scheme */
490
491 static void auth_method_basic_x(auth_mod_t *am,
492 auth_status_t *as,
493 msg_auth_t *au,
494 auth_challenger_t const *ach);
495
496 auth_scheme_t auth_scheme_basic[1] =
497 {{
498 "Basic", /* asch_method */
499 sizeof (auth_mod_t), /* asch_size */
500 auth_init_default, /* asch_init */
501 auth_method_basic_x, /* asch_check */
502 auth_challenge_basic, /* asch_challenge */
503 auth_cancel_default, /* asch_cancel */
504 auth_destroy_default /* asch_destroy */
505 }};
506
507 /**Authenticate a request with @b Basic authentication.
508 *
509 * This function reads user database before authentication, if needed.
510 */
511 static
auth_method_basic_x(auth_mod_t * am,auth_status_t * as,msg_auth_t * au,auth_challenger_t const * ach)512 void auth_method_basic_x(auth_mod_t *am,
513 auth_status_t *as,
514 msg_auth_t *au,
515 auth_challenger_t const *ach)
516 {
517 if (am) {
518 auth_readdb_if_needed(am);
519 auth_method_basic(am, as, au, ach);
520 }
521 }
522
523 /** Authenticate a request with @b Basic authentication scheme.
524 *
525 */
auth_method_basic(auth_mod_t * am,auth_status_t * as,msg_auth_t * au,auth_challenger_t const * ach)526 void auth_method_basic(auth_mod_t *am,
527 auth_status_t *as,
528 msg_auth_t *au,
529 auth_challenger_t const *ach)
530 {
531 char *userpass, buffer[128];
532 size_t n, upsize;
533 char *pass;
534 auth_passwd_t *apw;
535
536 if (!as->as_realm)
537 return;
538
539 userpass = buffer, upsize = sizeof buffer;
540
541 for (au = auth_mod_credentials(au, "Basic", NULL);
542 au;
543 au = auth_mod_credentials(au->au_next, "Basic", NULL)) {
544 if (!au->au_params)
545 continue;
546 n = base64_d(userpass, upsize - 1, au->au_params[0]);
547 if (n >= upsize) {
548 void *b = realloc(userpass == buffer ? NULL : userpass, upsize = n + 1);
549 if (b == NULL)
550 break;
551 base64_d(userpass = b, upsize - 1, au->au_params[0]);
552 }
553 userpass[n] = 0;
554 if (!(pass = strchr(userpass, ':')))
555 continue;
556 *pass++ = '\0';
557 SU_DEBUG_5(("auth_method_basic: %s => %s:%s\n",
558 au->au_params[0], userpass, pass));
559
560 if (!(apw = auth_mod_getpass(am, userpass, as->as_realm)))
561 continue;
562 if (strcmp(apw->apw_pass, pass))
563 continue;
564
565 as->as_user = apw->apw_user;
566 as->as_anonymous = apw == am->am_anon_user;
567 as->as_ident = apw->apw_ident;
568 as->as_match = (msg_header_t *)au;
569 as->as_status = 0; /* Successful authentication! */
570
571 break;
572 }
573
574 if (userpass != buffer)
575 free(userpass);
576
577 if (au)
578 return;
579
580 if (auth_allow_check(am, as))
581 auth_challenge_basic(am, as, ach);
582 }
583
584 /** Construct a challenge header for @b Basic authentication scheme. */
auth_challenge_basic(auth_mod_t * am,auth_status_t * as,auth_challenger_t const * ach)585 void auth_challenge_basic(auth_mod_t *am,
586 auth_status_t *as,
587 auth_challenger_t const *ach)
588 {
589 as->as_status = ach->ach_status;
590 as->as_phrase = ach->ach_phrase;
591 as->as_response = msg_header_format(as->as_home, ach->ach_header,
592 "Basic realm=\"%s\"", as->as_realm);
593 }
594
595 /* ====================================================================== */
596 /* Digest authentication scheme */
597
598 static void auth_method_digest_x(auth_mod_t *am,
599 auth_status_t *as,
600 msg_auth_t *au,
601 auth_challenger_t const *ach);
602
603 auth_scheme_t auth_scheme_digest[1] =
604 {{
605 "Digest", /* asch_method */
606 sizeof (auth_mod_t), /* asch_size */
607 auth_init_default, /* asch_init */
608 auth_method_digest_x, /* asch_check */
609 auth_challenge_digest, /* asch_challenge */
610 auth_cancel_default, /* asch_cancel */
611 auth_destroy_default /* asch_destroy */
612 }};
613
614 struct nonce {
615 msg_time_t issued;
616 uint32_t count;
617 uint16_t nextnonce;
618 uint8_t digest[6];
619 };
620
621 #define AUTH_DIGEST_NONCE_LEN (BASE64_MINSIZE(sizeof (struct nonce)) + 1)
622
623 /** Authenticate a request with @b Digest authentication scheme.
624 *
625 * This function reads user database before authentication, if needed.
626 */
627 static
auth_method_digest_x(auth_mod_t * am,auth_status_t * as,msg_auth_t * au,auth_challenger_t const * ach)628 void auth_method_digest_x(auth_mod_t *am,
629 auth_status_t *as,
630 msg_auth_t *au,
631 auth_challenger_t const *ach)
632 {
633 if (am) {
634 auth_readdb_if_needed(am);
635 auth_method_digest(am, as, au, ach);
636 }
637 }
638
639 /** Authenticate a request with @b Digest authentication scheme.
640 */
auth_method_digest(auth_mod_t * am,auth_status_t * as,msg_auth_t * au,auth_challenger_t const * ach)641 void auth_method_digest(auth_mod_t *am,
642 auth_status_t *as,
643 msg_auth_t *au,
644 auth_challenger_t const *ach)
645 {
646 as->as_allow = as->as_allow || auth_allow_check(am, as) == 0;
647
648 if (as->as_realm)
649 au = auth_digest_credentials(au, as->as_realm, am->am_opaque);
650 else
651 au = NULL;
652
653 if (as->as_allow) {
654 SU_DEBUG_5(("%s: allow unauthenticated %s\n", __func__, as->as_method));
655 as->as_status = 0, as->as_phrase = NULL;
656 as->as_match = (msg_header_t *)au;
657 return;
658 }
659
660 if (au) {
661 auth_response_t ar[1] = {{ sizeof(ar) }};
662 auth_digest_response_get(as->as_home, ar, au->au_params);
663 as->as_match = (msg_header_t *)au;
664 auth_check_digest(am, as, ar, ach);
665 }
666 else {
667 /* There was no matching credentials, send challenge */
668 SU_DEBUG_5(("%s: no credentials matched\n", __func__));
669 auth_challenge_digest(am, as, ach);
670 }
671 }
672
673 /** Verify digest authentication */
auth_check_digest(auth_mod_t * am,auth_status_t * as,auth_response_t * ar,auth_challenger_t const * ach)674 void auth_check_digest(auth_mod_t *am,
675 auth_status_t *as,
676 auth_response_t *ar,
677 auth_challenger_t const *ach)
678 {
679 char const *a1;
680 auth_hexmd5_t a1buf, response;
681 auth_passwd_t *apw;
682 char const *phrase;
683 msg_time_t now = msg_now();
684
685 if (am == NULL || as == NULL || ar == NULL || ach == NULL) {
686 if (as) {
687 as->as_status = 500, as->as_phrase = "Internal Server Error";
688 as->as_response = NULL;
689 }
690 return;
691 }
692
693 #define PA "Authorization missing "
694
695 if ((!ar->ar_username && (phrase = PA "username")) ||
696 (!ar->ar_nonce && (phrase = PA "nonce")) ||
697 (!ar->ar_uri && (phrase = PA "URI")) ||
698 (!ar->ar_response && (phrase = PA "response")) ||
699 /* (!ar->ar_opaque && (phrase = PA "opaque")) || */
700 /* Check for qop */
701 (ar->ar_qop &&
702 ((ar->ar_auth &&
703 !su_casematch(ar->ar_qop, "auth") &&
704 !su_casematch(ar->ar_qop, "\"auth\"")) ||
705 (ar->ar_auth_int &&
706 !su_casematch(ar->ar_qop, "auth-int") &&
707 !su_casematch(ar->ar_qop, "\"auth-int\"")))
708 && (phrase = PA "has invalid qop"))) {
709 assert(phrase);
710 SU_DEBUG_5(("auth_method_digest: 400 %s\n", phrase));
711 as->as_status = 400, as->as_phrase = phrase;
712 as->as_response = NULL;
713 return;
714 }
715
716 if (as->as_nonce_issued == 0 /* Already validated nonce */ &&
717 auth_validate_digest_nonce(am, as, ar, now) < 0) {
718 as->as_blacklist = am->am_blacklist;
719 auth_challenge_digest(am, as, ach);
720 return;
721 }
722
723 if (as->as_stale) {
724 auth_challenge_digest(am, as, ach);
725 return;
726 }
727
728 apw = auth_mod_getpass(am, ar->ar_username, ar->ar_realm);
729
730 if (apw && apw->apw_hash)
731 a1 = apw->apw_hash;
732 else if (apw && apw->apw_pass)
733 auth_digest_a1(ar, a1buf, apw->apw_pass), a1 = a1buf;
734 else
735 auth_digest_a1(ar, a1buf, "xyzzy"), a1 = a1buf, apw = NULL;
736
737 if (ar->ar_md5sess)
738 auth_digest_a1sess(ar, a1buf, a1), a1 = a1buf;
739
740 auth_digest_response(ar, response, a1,
741 as->as_method, as->as_body, as->as_bodylen);
742
743 if (!apw || strcmp(response, ar->ar_response)) {
744
745 if (am->am_forbidden) {
746 as->as_status = 403, as->as_phrase = "Forbidden";
747 as->as_response = NULL;
748 as->as_blacklist = am->am_blacklist;
749 }
750 else {
751 auth_challenge_digest(am, as, ach);
752 as->as_blacklist = am->am_blacklist;
753 }
754 SU_DEBUG_5(("auth_method_digest: response did not match\n" VA_NONE));
755
756 return;
757 }
758
759 assert(apw);
760
761 as->as_user = apw->apw_user;
762 as->as_anonymous = apw == am->am_anon_user;
763 as->as_ident = apw->apw_ident;
764
765 if (am->am_nextnonce || am->am_mutual)
766 auth_info_digest(am, as, ach);
767
768 if (am->am_challenge)
769 auth_challenge_digest(am, as, ach);
770
771 SU_DEBUG_7(("auth_method_digest: successful authentication\n" VA_NONE));
772
773 as->as_status = 0; /* Successful authentication! */
774 as->as_phrase = "";
775 }
776
777 /** Construct a challenge header for @b Digest authentication scheme. */
auth_challenge_digest(auth_mod_t * am,auth_status_t * as,auth_challenger_t const * ach)778 void auth_challenge_digest(auth_mod_t *am,
779 auth_status_t *as,
780 auth_challenger_t const *ach)
781 {
782 char const *u, *d;
783 char nonce[AUTH_DIGEST_NONCE_LEN];
784
785 auth_generate_digest_nonce(am, nonce, sizeof nonce, 0, msg_now());
786
787 u = as->as_uri;
788 d = as->as_pdomain;
789
790 as->as_response =
791 msg_header_format(as->as_home, ach->ach_header,
792 "Digest"
793 " realm=\"%s\","
794 "%s%s%s"
795 "%s%s%s"
796 " nonce=\"%s\","
797 "%s%s%s"
798 "%s" /* stale */
799 " algorithm=%s"
800 "%s%s%s",
801 as->as_realm,
802 u ? " uri=\"" : "", u ? u : "", u ? "\"," : "",
803 d ? " domain=\"" : "", d ? d : "", d ? "\"," : "",
804 nonce,
805 am->am_opaque ? " opaque=\"" : "",
806 am->am_opaque ? am->am_opaque : "",
807 am->am_opaque ? "\"," : "",
808 as->as_stale ? " stale=true," : "",
809 am->am_algorithm,
810 am->am_qop ? ", qop=\"" : "",
811 am->am_qop ? am->am_qop : "",
812 am->am_qop ? "\"" : "");
813
814 if (!as->as_response)
815 as->as_status = 500, as->as_phrase = auth_internal_server_error;
816 else
817 as->as_status = ach->ach_status, as->as_phrase = ach->ach_phrase;
818 }
819
820 /** Construct a info header for @b Digest authentication scheme. */
auth_info_digest(auth_mod_t * am,auth_status_t * as,auth_challenger_t const * ach)821 void auth_info_digest(auth_mod_t *am,
822 auth_status_t *as,
823 auth_challenger_t const *ach)
824 {
825 if (!ach->ach_info)
826 return;
827
828 if (am->am_nextnonce) {
829 char nonce[AUTH_DIGEST_NONCE_LEN];
830
831 auth_generate_digest_nonce(am, nonce, sizeof nonce, 1, msg_now());
832
833 as->as_info =
834 msg_header_format(as->as_home, ach->ach_info, "nextnonce=\"%s\"", nonce);
835 }
836 }
837
838
839 /* ====================================================================== */
840 /* Password database */
841
842 su_inline void
843 auth_htable_append_local(auth_htable_t *pr, auth_passwd_t *apw);
844
845 /** Get an passwd entry for user. */
auth_mod_getpass(auth_mod_t * am,char const * user,char const * realm)846 auth_passwd_t *auth_mod_getpass(auth_mod_t *am,
847 char const *user,
848 char const *realm)
849 {
850 auth_passwd_t *apw, **slot;
851 unsigned hash;
852
853 if (am == NULL || user == NULL)
854 return NULL;
855
856 hash = msg_hash_string(user);
857
858 for (slot = auth_htable_hash(am->am_users, hash);
859 (apw = *slot);
860 slot = auth_htable_next(am->am_users, slot)) {
861 if (hash != apw->apw_index)
862 continue;
863 if (strcmp(user, apw->apw_user))
864 continue;
865 if (realm && apw->apw_realm[0] && strcmp(realm, apw->apw_realm))
866 continue;
867 break; /* Found it */
868 }
869
870 return apw;
871 }
872
873 /** Add a password entry. */
auth_mod_addpass(auth_mod_t * am,char const * user,char const * realm)874 auth_passwd_t *auth_mod_addpass(auth_mod_t *am,
875 char const *user,
876 char const *realm)
877 {
878 auth_passwd_t *apw, **slot;
879 unsigned index;
880
881 if (am == NULL || user == NULL)
882 return NULL;
883
884 index = msg_hash_string(user);
885
886 for (slot = auth_htable_hash(am->am_users, index);
887 (apw = *slot);
888 slot = auth_htable_next(am->am_users, slot)) {
889 if (index != apw->apw_index)
890 continue;
891 if (strcmp(user, apw->apw_user))
892 continue;
893 if (realm && strcmp(realm, apw->apw_realm))
894 continue;
895 break; /* Found it */
896 }
897
898 if (realm == NULL)
899 realm = "";
900
901 if (!apw) {
902 size_t ulen = strlen(user) + 1, rlen = strlen(realm) + 1;
903 size_t size = sizeof *apw + ulen + rlen;
904
905 apw = su_alloc(am->am_home, size);
906
907 if (apw) {
908 memset(apw, 0, sizeof *apw);
909 apw->apw_index = index;
910 apw->apw_user = memcpy((char *)(apw + 1), user, ulen);
911 apw->apw_realm = memcpy((char *)apw->apw_user + ulen, realm, rlen);
912
913 if (!auth_htable_is_full(am->am_users)) {
914 *slot = apw, am->am_users->aht_used++;
915 } else {
916 if (auth_htable_resize(am->am_home, am->am_users, 0) < 0)
917 su_free(am->am_home, apw), apw = NULL;
918 else
919 auth_htable_append(am->am_users, apw);
920 }
921 }
922 }
923
924 return apw;
925 }
926
927 static ssize_t readfile(su_home_t *, FILE *,
928 void **contents, int add_trailing_lf);
929 static int auth_readdb_internal(auth_mod_t *am, int always);
930
931 /** Read authentication database */
auth_readdb(auth_mod_t * am)932 int auth_readdb(auth_mod_t *am)
933 {
934 return auth_readdb_internal(am, 1);
935 }
936
937 /** Read authentication database only when needed */
auth_readdb_if_needed(auth_mod_t * am)938 int auth_readdb_if_needed(auth_mod_t *am)
939 {
940 struct stat st[1];
941
942 if (!am->am_stat || !am->am_db)
943 return 0;
944
945 if (stat(am->am_db, st) != -1 &&
946 st->st_dev == am->am_stat->st_dev &&
947 st->st_ino == am->am_stat->st_ino &&
948 st->st_size == am->am_stat->st_size &&
949 memcmp(&st->st_mtime, &am->am_stat->st_mtime,
950 (sizeof st->st_mtime)) == 0)
951 /* Nothing has changed or passwd file is removed */
952 return 0;
953
954 return auth_readdb_internal(am, 0);
955 }
956
957 #if HAVE_FLOCK
958 #include <sys/file.h>
959 #endif
960
961 /* This is just a magic value */
962 #define auth_apw_local ((void *)(intptr_t)auth_readdb_internal)
963
964 /** Read authentication database */
965 static
auth_readdb_internal(auth_mod_t * am,int always)966 int auth_readdb_internal(auth_mod_t *am, int always)
967 {
968 FILE *f;
969 char *data, *s;
970 size_t len, i, n, N;
971 ssize_t slen;
972 auth_passwd_t *apw;
973
974 if (!am->am_stat)
975 am->am_stat = su_zalloc(am->am_home, sizeof (*am->am_stat));
976
977 f = fopen(am->am_db, "rb");
978
979 if (f) {
980 void *buffer = NULL;
981 auth_passwd_t *fresh = NULL;
982
983 #if HAVE_FLOCK
984 int locked;
985
986 /* Obtain shared lock on the database file */
987 if (flock(fileno(f), LOCK_SH | (always ? 0 : LOCK_NB)) == 0) {
988 locked = 1;
989 } else {
990 locked = 0;
991
992 if (errno == ENOLCK) {
993 ;
994 }
995 else if (errno == EWOULDBLOCK) {
996 SU_DEBUG_3(("auth(%s): user file \"%s\" is busy, trying again later\n",
997 am->am_scheme->asch_method, am->am_db));
998 fclose(f);
999 return always ? -1 : 0;
1000 }
1001 else {
1002 SU_DEBUG_3(("auth(%s): flock(\"%s\"): %s (%u)\n",
1003 am->am_scheme->asch_method, am->am_db,
1004 strerror(errno), errno));
1005 fclose(f);
1006 return always ? -1 : 0;
1007 }
1008 }
1009 #endif
1010 if (am->am_stat)
1011 stat(am->am_db, am->am_stat); /* too bad if this fails */
1012
1013 slen = readfile(am->am_home, f, &buffer, 1);
1014
1015 #if HAVE_FLOCK
1016 /* Release shared lock on the database file */
1017 if (locked && flock(fileno(f), LOCK_UN) == -1) {
1018 SU_DEBUG_0(("auth(%s): un-flock(\"%s\"): %s (%u)\n",
1019 am->am_scheme->asch_method, am->am_db, strerror(errno), errno));
1020 fclose(f);
1021 return -1;
1022 }
1023 #endif
1024
1025 fclose(f);
1026
1027 if (slen < 0)
1028 return -1;
1029 len = (size_t)slen;
1030
1031 /* Count number of entries in new buffer */
1032 for (i = am->am_anonymous, s = data = buffer;
1033 s < data + len;
1034 s += n + strspn(s + n, "\r\n")) {
1035 n = strcspn(s, "\r\n");
1036 if (*s != '#' && *s != '\n' && *s != '\r')
1037 i++;
1038 }
1039
1040 N = i, i = 0;
1041
1042 if (N > 0) {
1043 size_t size = (N * 5 + 3) / 4;
1044 if (auth_htable_resize(am->am_home, am->am_users, size) < 0 ||
1045 !(fresh = su_zalloc(am->am_home, sizeof(*fresh) * N))) {
1046 su_free(am->am_home, buffer);
1047 return -1;
1048 }
1049 }
1050
1051 if (am->am_anonymous) {
1052 assert(i < N);
1053
1054 apw = fresh + i++;
1055
1056 apw->apw_index = msg_hash_string("anonymous");
1057 apw->apw_user = "anonymous";
1058 apw->apw_pass = "";
1059 apw->apw_realm = "";
1060
1061 am->am_anon_user = apw;
1062
1063 if (auth_htable_is_full(am->am_users))
1064 auth_htable_resize(am->am_home, am->am_users, 0);
1065
1066 auth_htable_append_local(am->am_users, apw);
1067 }
1068
1069 apw = NULL;
1070
1071 for (data = buffer, s = data;
1072 s < data + len && i < N;
1073 s += n + strspn(s + n, "\r\n")) {
1074 char *user, *pass, *realm, *ident;
1075
1076 n = strcspn(s, "\r\n");
1077 if (*s == '#')
1078 continue;
1079
1080 user = s;
1081 s[n++] = '\0';
1082 if (!(pass = strchr(user, ':')))
1083 continue;
1084
1085 *pass++ = '\0';
1086 if (!*pass || !*user)
1087 continue;
1088
1089 if ((realm = strchr(pass, ':')))
1090 *realm++ = '\0';
1091 else
1092 realm = "";
1093
1094 if ((ident = strchr(realm, ':')))
1095 *ident++ = '\0';
1096 else
1097 ident = "";
1098
1099 apw = fresh + i++;
1100
1101 apw->apw_index = msg_hash_string(user);
1102 apw->apw_user = user;
1103 apw->apw_ident = ident;
1104
1105 /* Check for htdigest format */
1106 if (span_hexdigit(realm) == 32 && realm[32] == '\0') {
1107 apw->apw_realm = pass;
1108 apw->apw_hash = realm;
1109 } else {
1110 apw->apw_pass = pass;
1111 apw->apw_realm = realm;
1112 }
1113
1114 if (auth_htable_is_full(am->am_users))
1115 auth_htable_resize(am->am_home, am->am_users, 0);
1116
1117 auth_htable_append_local(am->am_users, apw);
1118 }
1119
1120 assert(i <= N);
1121 N = i;
1122
1123 /* Remove from hash those entries that were read from old passwd file */
1124 for (i = 0; i < am->am_local_count; i++) {
1125 if (am->am_locals[i].apw_type == auth_apw_local)
1126 auth_htable_remove(am->am_users, &am->am_locals[i]);
1127 }
1128
1129 if (am->am_locals)
1130 su_free(am->am_home, am->am_locals); /* Free old entries */
1131 if (am->am_buffer)
1132 su_free(am->am_home, am->am_buffer); /* Free old passwd file contents */
1133
1134 SU_DEBUG_5(("auth(%s): read %u entries from \"%s\"\n",
1135 am->am_scheme->asch_method, (unsigned)N, am->am_db));
1136
1137 am->am_locals = fresh;
1138 am->am_local_count = N;
1139 am->am_buffer = buffer;
1140
1141 return 0;
1142 }
1143
1144 return -1;
1145 }
1146
1147 /** Append to hash, remove existing local user */
1148 su_inline void
auth_htable_append_local(auth_htable_t * aht,auth_passwd_t * apw)1149 auth_htable_append_local(auth_htable_t *aht, auth_passwd_t *apw)
1150 {
1151 auth_passwd_t **slot;
1152
1153 apw->apw_type = auth_apw_local;
1154
1155 /* Append to hash */
1156 for (slot = auth_htable_hash(aht, apw->apw_index);
1157 *slot;
1158 slot = auth_htable_next(aht, slot)) {
1159 if (strcmp((*slot)->apw_user, apw->apw_user) == 0) {
1160 if ((*slot)->apw_type == auth_apw_local) {
1161 (*slot)->apw_type = NULL;
1162 assert(aht->aht_used > 0); aht->aht_used--;
1163 apw->apw_extended = (*slot)->apw_extended;
1164 *slot = NULL;
1165 break;
1166 }
1167 else {
1168 /* We insert local before external entry */
1169 auth_passwd_t *swap = apw;
1170 apw = *slot;
1171 *slot = swap;
1172 }
1173 }
1174 }
1175
1176 aht->aht_used++; assert(aht->aht_used <= aht->aht_size);
1177
1178 *slot = apw;
1179 }
1180
1181 static
readfile(su_home_t * home,FILE * f,void ** contents,int add_trailing_lf)1182 ssize_t readfile(su_home_t *home,
1183 FILE *f,
1184 void **contents,
1185 int add_trailing_lf)
1186 {
1187 /* Read in whole (binary!) file */
1188 char *buffer = NULL;
1189 long size;
1190 size_t len;
1191
1192 /* Read whole file in */
1193 if (fseek(f, 0, SEEK_END) < 0 ||
1194 (size = ftell(f)) < 0 ||
1195 fseek(f, 0, SEEK_SET) < 0 ||
1196 (long)(len = (size_t)size) != size ||
1197 size + 2 > SSIZE_MAX) {
1198 SU_DEBUG_1(("%s: unable to determine file size (%s)\n",
1199 __func__, strerror(errno)));
1200 return -1;
1201 }
1202
1203 if (!(buffer = su_alloc(home, len + 2)) ||
1204 fread(buffer, 1, len, f) != len) {
1205 SU_DEBUG_1(("%s: unable to read file (%s)\n", __func__, strerror(errno)));
1206 if (buffer)
1207 su_free(home, buffer);
1208 return -1;
1209 }
1210
1211 if (add_trailing_lf) {
1212 /* Make sure that the buffer has trailing newline */
1213 if (len == 0 || buffer[len - 1] != '\n')
1214 buffer[len++] = '\n';
1215 }
1216
1217 buffer[len] = '\0';
1218 *contents = buffer;
1219
1220 return (ssize_t)len;
1221 }
1222
1223 /* ====================================================================== */
1224 /* Helper functions */
1225
1226 /** Check if request method is on always-allowed list.
1227 *
1228 * @return 0 if allowed
1229 * @return 1 otherwise
1230 */
auth_allow_check(auth_mod_t * am,auth_status_t * as)1231 int auth_allow_check(auth_mod_t *am, auth_status_t *as)
1232 {
1233 char const *method = as->as_method;
1234 int i;
1235
1236 if (method && strcmp(method, "ACK") == 0) /* Hack */
1237 return as->as_status = 0;
1238
1239 if (!method || !am->am_allow)
1240 return 1;
1241
1242 if (am->am_allow[0] && strcmp(am->am_allow[0], "*") == 0)
1243 return as->as_status = 0;
1244
1245 for (i = 0; am->am_allow[i]; i++)
1246 if (strcmp(am->am_allow[i], method) == 0)
1247 return as->as_status = 0;
1248
1249 return 1;
1250 }
1251
1252 /** Find a credential header with matching scheme and realm. */
auth_mod_credentials(msg_auth_t * auth,char const * scheme,char const * realm)1253 msg_auth_t *auth_mod_credentials(msg_auth_t *auth,
1254 char const *scheme,
1255 char const *realm)
1256 {
1257 char const *arealm;
1258
1259 for (;auth; auth = auth->au_next) {
1260 if (!su_casematch(auth->au_scheme, scheme))
1261 continue;
1262
1263 if (!realm)
1264 return auth;
1265
1266 arealm = msg_header_find_param(auth->au_common, "realm=");
1267
1268 if (!arealm)
1269 continue;
1270
1271 if (arealm[0] == '"') {
1272 /* Compare quoted arealm with unquoted realm */
1273 int i, j;
1274 for (i = 1, j = 0; arealm[i] != 0; i++, j++) {
1275 if (arealm[i] == '"' && realm[j] == 0)
1276 return auth;
1277
1278 if (arealm[i] == '\\' && arealm[i + 1] != '\0')
1279 i++;
1280
1281 if (arealm[i] != realm[j])
1282 break;
1283 }
1284 } else {
1285 if (strcmp(arealm, realm) == 0)
1286 return auth;
1287 }
1288 }
1289
1290 return NULL;
1291 }
1292
1293 /** Find a Digest credential header with matching realm and opaque. */
auth_digest_credentials(msg_auth_t * auth,char const * realm,char const * opaque)1294 msg_auth_t *auth_digest_credentials(msg_auth_t *auth,
1295 char const *realm,
1296 char const *opaque)
1297 {
1298 char const *arealm, *aopaque;
1299
1300 for (;auth; auth = auth->au_next) {
1301 if (!su_casematch(auth->au_scheme, "Digest"))
1302 continue;
1303
1304 if (realm) {
1305 int cmp = 1;
1306
1307 arealm = msg_header_find_param(auth->au_common, "realm=");
1308 if (!arealm)
1309 continue;
1310
1311 if (arealm[0] == '"') {
1312 /* Compare quoted arealm with unquoted realm */
1313 int i, j;
1314 for (i = 1, j = 0, cmp = 1; arealm[i] != 0; i++, j++) {
1315 if (arealm[i] == '"' && realm[j] == 0) {
1316 cmp = 0;
1317 break;
1318 }
1319
1320 if (arealm[i] == '\\' && arealm[i + 1] != '\0')
1321 i++;
1322
1323 if (arealm[i] != realm[j])
1324 break;
1325 }
1326 }
1327 else {
1328 cmp = strcmp(arealm, realm);
1329 }
1330
1331 if (cmp)
1332 continue;
1333 }
1334
1335 if (opaque) {
1336 int cmp = 1;
1337
1338 aopaque = msg_header_find_param(auth->au_common, "opaque=");
1339 if (!aopaque)
1340 continue;
1341
1342 if (aopaque[0] == '"') {
1343 /* Compare quoted aopaque with unquoted opaque */
1344 int i, j;
1345 for (i = 1, j = 0, cmp = 1; aopaque[i] != 0; i++, j++) {
1346 if (aopaque[i] == '"' && opaque[j] == 0) {
1347 cmp = 0;
1348 break;
1349 }
1350
1351 if (aopaque[i] == '\\' && aopaque[i + 1] != '\0')
1352 i++;
1353
1354 if (aopaque[i] != opaque[j])
1355 break;
1356 }
1357 } else {
1358 cmp = strcmp(aopaque, opaque);
1359 }
1360
1361 if (cmp)
1362 continue;
1363 }
1364
1365 return auth;
1366 }
1367
1368 return NULL;
1369 }
1370
1371 /** Generate nonce parameter.
1372 *
1373 * @param am pointer to authentication module object
1374 * @param buffer string buffer for nonce [OUT]
1375 * @param bsize size of buffer [IN]
1376 * @param nextnonce true if this is a "nextnonce" [IN]
1377 * @param now current time [IN]
1378 */
auth_generate_digest_nonce(auth_mod_t * am,char buffer[],size_t bsize,int nextnonce,msg_time_t now)1379 isize_t auth_generate_digest_nonce(auth_mod_t *am,
1380 char buffer[],
1381 size_t bsize,
1382 int nextnonce,
1383 msg_time_t now)
1384 {
1385 struct nonce nonce[1] = {{ 0 }};
1386 su_md5_t md5[1];
1387
1388 am->am_count += 3730029547U; /* 3730029547 is a prime */
1389
1390 nonce->issued = now;
1391 nonce->count = am->am_count;
1392 nonce->nextnonce = nextnonce != 0;
1393
1394 /* Calculate HMAC of nonce data */
1395 auth_md5_hmac_init(am, md5);
1396 su_md5_update(md5, nonce, offsetof(struct nonce, digest));
1397 auth_md5_hmac_digest(am, md5, nonce->digest, sizeof nonce->digest);
1398
1399 return base64_e(buffer, bsize, nonce, sizeof(nonce));
1400 }
1401
1402
1403 /** Validate nonce parameter.
1404 *
1405 * @param am pointer to authentication module object
1406 * @param as authentication status structure [OUT]
1407 * @param ar decoded authentication response from client [IN]
1408 * @param now current time [IN]
1409 */
auth_validate_digest_nonce(auth_mod_t * am,auth_status_t * as,auth_response_t * ar,msg_time_t now)1410 int auth_validate_digest_nonce(auth_mod_t *am,
1411 auth_status_t *as,
1412 auth_response_t *ar,
1413 msg_time_t now)
1414 {
1415 struct nonce nonce[1] = {{ 0 }};
1416 su_md5_t md5[1];
1417 uint8_t hmac[sizeof nonce->digest];
1418 unsigned expires;
1419
1420 /* Check nonce */
1421 if (!ar->ar_nonce) {
1422 SU_DEBUG_5(("auth_method_digest: no nonce\n" VA_NONE));
1423 return -1;
1424 }
1425 if (base64_d((void*)nonce, (sizeof nonce), ar->ar_nonce) != (sizeof nonce)) {
1426 SU_DEBUG_5(("auth_method_digest: too short nonce\n" VA_NONE));
1427 return -1;
1428 }
1429
1430 /* Calculate HMAC over decoded nonce data */
1431 auth_md5_hmac_init(am, md5);
1432 su_md5_update(md5, nonce, offsetof(struct nonce, digest));
1433 auth_md5_hmac_digest(am, md5, hmac, sizeof hmac);
1434
1435 if (memcmp(nonce->digest, hmac, sizeof nonce->digest)) {
1436 SU_DEBUG_5(("auth_method_digest: bad nonce\n" VA_NONE));
1437 return -1;
1438 }
1439
1440 as->as_nonce_issued = nonce->issued;
1441 as->as_nextnonce = nonce->nextnonce != 0;
1442
1443 expires = nonce->nextnonce ? am->am_next_exp : am->am_expires;
1444
1445 if (nonce->issued > now ||
1446 (expires && nonce->issued + expires < now)) {
1447 SU_DEBUG_5(("auth_method_digest: nonce expired %lu seconds ago "
1448 "(lifetime %u)\n",
1449 now - (nonce->issued + expires), expires));
1450 as->as_stale = 1;
1451 }
1452
1453 if (am->am_max_ncount && ar->ar_nc) {
1454 unsigned long nc = strtoul(ar->ar_nc, NULL, 10);
1455
1456 if (nc == 0 || nc > am->am_max_ncount) {
1457 SU_DEBUG_5(("auth_method_digest: nonce used %s times, max %u\n",
1458 ar->ar_nc, am->am_max_ncount));
1459 as->as_stale = 1;
1460 }
1461 }
1462
1463 /* We should also check cnonce, nc... */
1464
1465 return 0;
1466 }
1467
1468
1469 /* ====================================================================== */
1470 /* HMAC routines */
1471 static
auth_md5_hmac_key(auth_mod_t * am)1472 void auth_md5_hmac_key(auth_mod_t *am)
1473 {
1474 size_t i;
1475 uint8_t ipad[SU_MD5_DIGEST_SIZE];
1476 uint8_t opad[SU_MD5_DIGEST_SIZE];
1477
1478 assert(SU_MD5_DIGEST_SIZE == sizeof am->am_master_key);
1479
1480 /* Derive HMAC ipad and opad from master key */
1481 for (i = 0; i < sizeof am->am_master_key; i++) {
1482 ipad[i] = am->am_master_key[i] ^ 0x36;
1483 opad[i] = am->am_master_key[i] ^ 0x5C;
1484 }
1485
1486 /* Pre-calculate sum of ipad */
1487 su_md5_init(&am->am_hmac_ipad);
1488 su_md5_update(&am->am_hmac_ipad, ipad, sizeof ipad);
1489
1490 /* Pre-calculate sum of opad */
1491 su_md5_init(&am->am_hmac_opad);
1492 su_md5_update(&am->am_hmac_opad, opad, sizeof opad);
1493 }
1494
auth_md5_hmac_init(auth_mod_t * am,struct su_md5_t * imd5)1495 void auth_md5_hmac_init(auth_mod_t *am, struct su_md5_t *imd5)
1496 {
1497 *imd5 = am->am_hmac_ipad;
1498 }
1499
auth_md5_hmac_digest(auth_mod_t * am,struct su_md5_t * imd5,void * hmac,size_t size)1500 void auth_md5_hmac_digest(auth_mod_t *am, struct su_md5_t *imd5,
1501 void *hmac, size_t size)
1502 {
1503 uint8_t digest[SU_MD5_DIGEST_SIZE];
1504 su_md5_t omd5[1];
1505
1506 /* inner sum */
1507 su_md5_digest(imd5, digest);
1508
1509 *omd5 = am->am_hmac_opad;
1510 su_md5_update(omd5, digest, sizeof *digest);
1511
1512 /* outer sum */
1513 if (size == sizeof digest) {
1514 su_md5_digest(omd5, hmac);
1515 }
1516 else {
1517 su_md5_digest(omd5, digest);
1518
1519 if (size > sizeof digest) {
1520 memset((char *)hmac + (sizeof digest), 0, size - sizeof digest);
1521 size = sizeof digest;
1522 }
1523
1524 memcpy(hmac, digest, size);
1525 }
1526 }
1527
1528 /* ====================================================================== */
1529 /* Compatibility interface */
1530
auth_mod_method(auth_mod_t * am,auth_status_t * as,msg_auth_t * credentials,auth_challenger_t const * ach)1531 void auth_mod_method(auth_mod_t *am,
1532 auth_status_t *as,
1533 msg_auth_t *credentials,
1534 auth_challenger_t const *ach)
1535 {
1536 auth_mod_verify(am, as, credentials, ach);
1537 }
1538
auth_mod_check_client(auth_mod_t * am,auth_status_t * as,msg_auth_t * credentials,auth_challenger_t const * ach)1539 void auth_mod_check_client(auth_mod_t *am,
1540 auth_status_t *as,
1541 msg_auth_t *credentials,
1542 auth_challenger_t const *ach)
1543 {
1544 auth_mod_verify(am, as, credentials, ach);
1545 }
1546
1547
auth_mod_challenge_client(auth_mod_t * am,auth_status_t * as,auth_challenger_t const * ach)1548 void auth_mod_challenge_client(auth_mod_t *am,
1549 auth_status_t *as,
1550 auth_challenger_t const *ach)
1551 {
1552 auth_mod_challenge(am, as, ach);
1553 }
1554