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