GNU libmicrohttpd  1.0.1
gen_auth.c
Go to the documentation of this file.
1 /*
2  This file is part of libmicrohttpd
3  Copyright (C) 2022-2023 Evgeny Grin (Karlson2k)
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Lesser General Public
7  License as published by the Free Software Foundation; either
8  version 2.1 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library.
17  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
26 #include "gen_auth.h"
27 #include "internal.h"
28 #include "connection.h"
29 #include "mhd_str.h"
30 #include "mhd_assert.h"
31 
32 #ifdef BAUTH_SUPPORT
33 #include "basicauth.h"
34 #endif /* BAUTH_SUPPORT */
35 #ifdef DAUTH_SUPPORT
36 #include "digestauth.h"
37 #endif /* DAUTH_SUPPORT */
38 
39 #if ! defined(BAUTH_SUPPORT) && ! defined(DAUTH_SUPPORT)
40 #error This file requires Basic or Digest authentication support
41 #endif
42 
47 {
53 };
54 
64 static bool
65 find_auth_rq_header_ (const struct MHD_Connection *c, enum MHD_AuthType type,
66  struct _MHD_str_w_len *auth_value)
67 {
68  const struct MHD_HTTP_Req_Header *h;
69  const char *token;
70  size_t token_len;
71 
72  mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= c->state);
74  return false;
75 
76 #ifdef DAUTH_SUPPORT
77  if (MHD_AUTHTYPE_DIGEST == type)
78  {
79  token = _MHD_AUTH_DIGEST_BASE;
81  }
82  else /* combined with the next line */
83 #endif /* DAUTH_SUPPORT */
84 #ifdef BAUTH_SUPPORT
85  if (MHD_AUTHTYPE_BASIC == type)
86  {
87  token = _MHD_AUTH_BASIC_BASE;
89  }
90  else /* combined with the next line */
91 #endif /* BAUTH_SUPPORT */
92  {
93  mhd_assert (0);
94  return false;
95  }
96 
97  for (h = c->rq.headers_received; NULL != h; h = h->next)
98  {
99  if (MHD_HEADER_KIND != h->kind)
100  continue;
102  continue;
103  if (token_len > h->value_size)
104  continue;
106  h->header,
109  continue;
110  if (! MHD_str_equal_caseless_bin_n_ (h->value, token, token_len))
111  continue;
112  /* Match only if token string is full header value or token is
113  * followed by space or tab
114  * Note: RFC 9110 (and RFC 7234) allows only space character, but
115  * tab is supported here as well for additional flexibility and uniformity
116  * as tabs are supported as separators between parameters.
117  */
118  if ((token_len == h->value_size) ||
119  (' ' == h->value[token_len]) || ('\t' == h->value[token_len]))
120  {
121  if (token_len != h->value_size)
122  { /* Skip whitespace */
123  auth_value->str = h->value + token_len + 1;
124  auth_value->len = h->value_size - (token_len + 1);
125  }
126  else
127  { /* No whitespace to skip */
128  auth_value->str = h->value + token_len;
129  auth_value->len = h->value_size - token_len;
130  }
131  return true; /* Found a match */
132  }
133  }
134  return false; /* No matching header has been found */
135 }
136 
137 
138 #ifdef BAUTH_SUPPORT
139 
140 
150 static bool
151 parse_bauth_params (const char *str,
152  size_t str_len,
153  struct MHD_RqBAuth *pbauth)
154 {
155  size_t i;
156 
157  i = 0;
158 
159  /* Skip all whitespaces at start */
160  while (i < str_len && (' ' == str[i] || '\t' == str[i]))
161  i++;
162 
163  if (str_len > i)
164  {
165  size_t token68_start;
166  size_t token68_len;
167 
168  /* 'i' points to the first non-whitespace char after scheme token */
169  token68_start = i;
170  /* Find end of the token. Token cannot contain whitespace. */
171  while (i < str_len && ' ' != str[i] && '\t' != str[i])
172  {
173  if (0 == str[i])
174  return false; /* Binary zero is not allowed */
175  if ((',' == str[i]) || (';' == str[i]))
176  return false; /* Only single token68 is allowed */
177  i++;
178  }
179  token68_len = i - token68_start;
180  mhd_assert (0 != token68_len);
181 
182  /* Skip all whitespaces */
183  while (i < str_len && (' ' == str[i] || '\t' == str[i]))
184  i++;
185  /* Check whether any garbage is present at the end of the string */
186  if (str_len != i)
187  return false;
188  else
189  {
190  /* No more data in the string, only single token68. */
191  pbauth->token68.str = str + token68_start;
192  pbauth->token68.len = token68_len;
193  }
194  }
195  return true;
196 }
197 
198 
211 const struct MHD_RqBAuth *
212 MHD_get_rq_bauth_params_ (struct MHD_Connection *connection)
213 {
214  struct _MHD_str_w_len h_auth_value;
215  struct MHD_RqBAuth *bauth;
216 
217  mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= connection->state);
218 
219  if (connection->rq.bauth_tried)
220  return connection->rq.bauth;
221 
222  if (MHD_CONNECTION_HEADERS_PROCESSED > connection->state)
223  return NULL;
224 
225  if (! find_auth_rq_header_ (connection, MHD_AUTHTYPE_BASIC, &h_auth_value))
226  {
227  connection->rq.bauth_tried = true;
228  connection->rq.bauth = NULL;
229  return NULL;
230  }
231 
232  bauth =
233  (struct MHD_RqBAuth *)
234  MHD_connection_alloc_memory_ (connection, sizeof (struct MHD_RqBAuth));
235 
236  if (NULL == bauth)
237  {
238 #ifdef HAVE_MESSAGES
239  MHD_DLOG (connection->daemon,
240  _ ("Not enough memory in the connection's pool to allocate " \
241  "for Basic Authorization header parsing.\n"));
242 #endif /* HAVE_MESSAGES */
243  return NULL;
244  }
245 
246  memset (bauth, 0, sizeof(struct MHD_RqBAuth));
247  if (parse_bauth_params (h_auth_value.str, h_auth_value.len, bauth))
248  connection->rq.bauth = bauth;
249  else
250  {
251 #ifdef HAVE_MESSAGES
252  MHD_DLOG (connection->daemon,
253  _ ("The Basic Authorization client's header has "
254  "incorrect format.\n"));
255 #endif /* HAVE_MESSAGES */
256  connection->rq.bauth = NULL;
257  /* Memory in the pool remains allocated until next request */
258  }
259  connection->rq.bauth_tried = true;
260  return connection->rq.bauth;
261 }
262 
263 
264 #endif /* BAUTH_SUPPORT */
265 
266 #ifdef DAUTH_SUPPORT
267 
268 
275 static enum MHD_DigestAuthAlgo3
276 get_rq_dauth_algo (const struct MHD_RqDAuthParam *const algo_param)
277 {
278  if (NULL == algo_param->value.str)
279  return MHD_DIGEST_AUTH_ALGO3_MD5; /* Assume MD5 by default */
280 
281  if (algo_param->quoted)
282  {
283  if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
284  algo_param->value.len, \
287  if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
288  algo_param->value.len, \
291  if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
292  algo_param->value.len, \
295  if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
296  algo_param->value.len, \
299 
300  /* Algorithms below are not supported by MHD for authentication */
301 
303  if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
304  algo_param->value.len, \
308  if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \
309  algo_param->value.len, \
312 
313  /* No known algorithm has been detected */
315  }
316  /* The algorithm value is not quoted */
318  algo_param->value.str, \
319  algo_param->value.len))
322  algo_param->value.str, \
323  algo_param->value.len))
326  algo_param->value.str, \
327  algo_param->value.len))
329 
330  /* Algorithms below are not supported by MHD for authentication */
331 
333  algo_param->value.str, \
334  algo_param->value.len))
337  algo_param->value.str, \
338  algo_param->value.len))
341  algo_param->value.str, \
342  algo_param->value.len))
344 
345  /* No known algorithm has been detected */
347 }
348 
349 
355 static enum MHD_DigestAuthQOP
356 get_rq_dauth_qop (const struct MHD_RqDAuthParam *const qop_param)
357 {
358  if (NULL == qop_param->value.str)
360  if (qop_param->quoted)
361  {
362  if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \
363  qop_param->value.len, \
366  if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \
367  qop_param->value.len, \
370  }
371  else
372  {
374  qop_param->value.str, \
375  qop_param->value.len))
378  qop_param->value.str, \
379  qop_param->value.len))
381  }
382  /* No know QOP has been detected */
384 }
385 
386 
396 static bool
397 parse_dauth_params (const char *str,
398  const size_t str_len,
399  struct MHD_RqDAuth *pdauth)
400 {
401  /* The tokens */
402  static const struct _MHD_cstr_w_len nonce_tk = _MHD_S_STR_W_LEN ("nonce");
403  static const struct _MHD_cstr_w_len opaque_tk = _MHD_S_STR_W_LEN ("opaque");
404  static const struct _MHD_cstr_w_len algorithm_tk =
405  _MHD_S_STR_W_LEN ("algorithm");
406  static const struct _MHD_cstr_w_len response_tk =
407  _MHD_S_STR_W_LEN ("response");
408  static const struct _MHD_cstr_w_len username_tk =
409  _MHD_S_STR_W_LEN ("username");
410  static const struct _MHD_cstr_w_len username_ext_tk =
411  _MHD_S_STR_W_LEN ("username*");
412  static const struct _MHD_cstr_w_len realm_tk = _MHD_S_STR_W_LEN ("realm");
413  static const struct _MHD_cstr_w_len uri_tk = _MHD_S_STR_W_LEN ("uri");
414  static const struct _MHD_cstr_w_len qop_tk = _MHD_S_STR_W_LEN ("qop");
415  static const struct _MHD_cstr_w_len cnonce_tk = _MHD_S_STR_W_LEN ("cnonce");
416  static const struct _MHD_cstr_w_len nc_tk = _MHD_S_STR_W_LEN ("nc");
417  static const struct _MHD_cstr_w_len userhash_tk =
418  _MHD_S_STR_W_LEN ("userhash");
419  /* The locally processed parameters */
420  struct MHD_RqDAuthParam userhash;
421  struct MHD_RqDAuthParam algorithm;
422  /* Indexes */
423  size_t i;
424  size_t p;
425  /* The list of the tokens.
426  The order of the elements matches the next array. */
427  static const struct _MHD_cstr_w_len *const tk_names[] = {
428  &nonce_tk, /* 0 */
429  &opaque_tk, /* 1 */
430  &algorithm_tk, /* 2 */
431  &response_tk, /* 3 */
432  &username_tk, /* 4 */
433  &username_ext_tk, /* 5 */
434  &realm_tk, /* 6 */
435  &uri_tk, /* 7 */
436  &qop_tk, /* 8 */
437  &cnonce_tk, /* 9 */
438  &nc_tk, /* 10 */
439  &userhash_tk /* 11 */
440  };
441  /* The list of the parameters.
442  The order of the elements matches the previous array. */
443  struct MHD_RqDAuthParam *params[sizeof(tk_names) / sizeof(tk_names[0])];
444 
445  params[0 ] = &(pdauth->nonce); /* 0 */
446  params[1 ] = &(pdauth->opaque); /* 1 */
447  params[2 ] = &algorithm; /* 2 */
448  params[3 ] = &(pdauth->response); /* 3 */
449  params[4 ] = &(pdauth->username); /* 4 */
450  params[5 ] = &(pdauth->username_ext); /* 5 */
451  params[6 ] = &(pdauth->realm); /* 6 */
452  params[7 ] = &(pdauth->uri); /* 7 */
453  params[8 ] = &(pdauth->qop_raw); /* 8 */
454  params[9 ] = &(pdauth->cnonce); /* 9 */
455  params[10] = &(pdauth->nc); /* 10 */
456  params[11] = &userhash; /* 11 */
457 
458  mhd_assert ((sizeof(tk_names) / sizeof(tk_names[0])) == \
459  (sizeof(params) / sizeof(params[0])));
460  memset (&userhash, 0, sizeof(userhash));
461  memset (&algorithm, 0, sizeof(algorithm));
462  i = 0;
463 
464  /* Skip all whitespaces at start */
465  while (i < str_len && (' ' == str[i] || '\t' == str[i]))
466  i++;
467 
468  while (str_len > i)
469  {
470  size_t left;
471  mhd_assert (' ' != str[i]);
472  mhd_assert ('\t' != str[i]);
473 
474  left = str_len - i;
475  if ('=' == str[i])
476  return false; /* The equal sign is not allowed as the first character */
477  for (p = 0; p < (sizeof(tk_names) / sizeof(tk_names[0])); ++p)
478  {
479  const struct _MHD_cstr_w_len *const tk_name = tk_names[p];
480  struct MHD_RqDAuthParam *const param = params[p];
481  if ( (tk_name->len <= left) &&
482  MHD_str_equal_caseless_bin_n_ (str + i, tk_name->str,
483  tk_name->len) &&
484  ((tk_name->len == left) ||
485  ('=' == str[i + tk_name->len]) ||
486  (' ' == str[i + tk_name->len]) ||
487  ('\t' == str[i + tk_name->len]) ||
488  (',' == str[i + tk_name->len]) ||
489  (';' == str[i + tk_name->len])) )
490  {
491  size_t value_start;
492  size_t value_len;
493  bool quoted; /* Only mark as "quoted" if backslash-escape used */
494 
495  if (tk_name->len == left)
496  return false; /* No equal sign after parameter name, broken data */
497 
498  quoted = false;
499  i += tk_name->len;
500  /* Skip all whitespaces before '=' */
501  while (str_len > i && (' ' == str[i] || '\t' == str[i]))
502  i++;
503  if ((i == str_len) || ('=' != str[i]))
504  return false; /* No equal sign, broken data */
505  i++;
506  /* Skip all whitespaces after '=' */
507  while (str_len > i && (' ' == str[i] || '\t' == str[i]))
508  i++;
509  if ((str_len > i) && ('"' == str[i]))
510  { /* Value is in quotation marks */
511  i++; /* Advance after the opening quote */
512  value_start = i;
513  while (str_len > i && '"' != str[i])
514  {
515  if ('\\' == str[i])
516  {
517  i++;
518  quoted = true; /* Have escaped chars */
519  }
520  if (0 == str[i])
521  return false; /* Binary zero in parameter value */
522  i++;
523  }
524  if (str_len <= i)
525  return false; /* No closing quote */
526  mhd_assert ('"' == str[i]);
527  value_len = i - value_start;
528  i++; /* Advance after the closing quote */
529  }
530  else
531  {
532  value_start = i;
533  while (str_len > i && ',' != str[i] &&
534  ' ' != str[i] && '\t' != str[i] && ';' != str[i])
535  {
536  if (0 == str[i])
537  return false; /* Binary zero in parameter value */
538  i++;
539  }
540  if (';' == str[i])
541  return false; /* Semicolon in parameter value */
542  value_len = i - value_start;
543  }
544  /* Skip all whitespaces after parameter value */
545  while (str_len > i && (' ' == str[i] || '\t' == str[i]))
546  i++;
547  if ((str_len > i) && (',' != str[i]))
548  return false; /* Garbage after parameter value */
549 
550  /* Have valid parameter name and value */
551  mhd_assert (! quoted || 0 != value_len);
552  param->value.str = str + value_start;
553  param->value.len = value_len;
554  param->quoted = quoted;
555 
556  break; /* Found matching parameter name */
557  }
558  }
559  if (p == (sizeof(tk_names) / sizeof(tk_names[0])))
560  {
561  /* No matching parameter name */
562  while (str_len > i && ',' != str[i])
563  {
564  if ((0 == str[i]) || (';' == str[i]))
565  return false; /* Not allowed characters */
566  if ('"' == str[i])
567  { /* Skip quoted part */
568  i++; /* Advance after the opening quote */
569  while (str_len > i && '"' != str[i])
570  {
571  if (0 == str[i])
572  return false; /* Binary zero is not allowed */
573  if ('\\' == str[i])
574  i++; /* Skip escaped char */
575  i++;
576  }
577  if (str_len <= i)
578  return false; /* No closing quote */
579  mhd_assert ('"' == str[i]);
580  }
581  i++;
582  }
583  }
584  mhd_assert (str_len == i || ',' == str[i]);
585  if (str_len > i)
586  i++; /* Advance after ',' */
587  /* Skip all whitespaces before next parameter name */
588  while (i < str_len && (' ' == str[i] || '\t' == str[i]))
589  i++;
590  }
591 
592  /* Postprocess values */
593 
594  if (NULL != userhash.value.str)
595  {
596  if (userhash.quoted)
597  pdauth->userhash =
598  MHD_str_equal_caseless_quoted_s_bin_n (userhash.value.str, \
599  userhash.value.len, \
600  "true");
601  else
602  pdauth->userhash =
603  MHD_str_equal_caseless_s_bin_n_ ("true", userhash.value.str, \
604  userhash.value.len);
605 
606  }
607  else
608  pdauth->userhash = false;
609 
610  pdauth->algo3 = get_rq_dauth_algo (&algorithm);
611  pdauth->qop = get_rq_dauth_qop (&pdauth->qop_raw);
612 
613  return true;
614 }
615 
616 
629 const struct MHD_RqDAuth *
630 MHD_get_rq_dauth_params_ (struct MHD_Connection *connection)
631 {
632  struct _MHD_str_w_len h_auth_value;
633  struct MHD_RqDAuth *dauth;
634 
635  mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= connection->state);
636 
637  if (connection->rq.dauth_tried)
638  return connection->rq.dauth;
639 
640  if (MHD_CONNECTION_HEADERS_PROCESSED > connection->state)
641  return NULL;
642 
643  if (! find_auth_rq_header_ (connection, MHD_AUTHTYPE_DIGEST, &h_auth_value))
644  {
645  connection->rq.dauth_tried = true;
646  connection->rq.dauth = NULL;
647  return NULL;
648  }
649 
650  dauth =
651  (struct MHD_RqDAuth *)
652  MHD_connection_alloc_memory_ (connection, sizeof (struct MHD_RqDAuth));
653 
654  if (NULL == dauth)
655  {
656 #ifdef HAVE_MESSAGES
657  MHD_DLOG (connection->daemon,
658  _ ("Not enough memory in the connection's pool to allocate " \
659  "for Digest Authorization header parsing.\n"));
660 #endif /* HAVE_MESSAGES */
661  return NULL;
662  }
663 
664  memset (dauth, 0, sizeof(struct MHD_RqDAuth));
665  if (parse_dauth_params (h_auth_value.str, h_auth_value.len, dauth))
666  connection->rq.dauth = dauth;
667  else
668  {
669 #ifdef HAVE_MESSAGES
670  MHD_DLOG (connection->daemon,
671  _ ("The Digest Authorization client's header has "
672  "incorrect format.\n"));
673 #endif /* HAVE_MESSAGES */
674  connection->rq.dauth = NULL;
675  /* Memory in the pool remains allocated until next request */
676  }
677  connection->rq.dauth_tried = true;
678  return connection->rq.dauth;
679 }
680 
681 
682 #endif /* DAUTH_SUPPORT */
#define _MHD_AUTH_BASIC_BASE
Definition: basicauth.h:36
void * MHD_connection_alloc_memory_(struct MHD_Connection *connection, size_t size)
Definition: connection.c:651
Methods for managing connections.
#define MHD_TOKEN_AUTH_INT_
Definition: digestauth.h:80
#define _MHD_SHA256_TOKEN
Definition: digestauth.h:60
#define _MHD_MD5_TOKEN
Definition: digestauth.h:55
#define _MHD_SHA512_256_TOKEN
Definition: digestauth.h:65
#define MHD_TOKEN_AUTH_
Definition: digestauth.h:75
#define _MHD_SESS_TOKEN
Definition: digestauth.h:70
#define _MHD_AUTH_DIGEST_BASE
Definition: digestauth.h:50
static bool find_auth_rq_header_(const struct MHD_Connection *c, enum MHD_AuthType type, struct _MHD_str_w_len *auth_value)
Definition: gen_auth.c:65
MHD_AuthType
Definition: gen_auth.c:47
@ MHD_AUTHTYPE_BASIC
Definition: gen_auth.c:49
@ MHD_AUTHTYPE_UNKNOWN
Definition: gen_auth.c:51
@ MHD_AUTHTYPE_DIGEST
Definition: gen_auth.c:50
@ MHD_AUTHTYPE_INVALID
Definition: gen_auth.c:52
@ MHD_AUTHTYPE_NONE
Definition: gen_auth.c:48
Declarations for HTTP authorisation general functions.
#define MHD_HTTP_HEADER_AUTHORIZATION
Definition: microhttpd.h:578
#define mhd_assert(CHK)
Definition: mhd_assert.h:39
#define MHD_STATICSTR_LEN_(macro)
Definition: mhd_str.h:45
#define NULL
Definition: reason_phrase.c:30
#define _(String)
Definition: mhd_options.h:42
#define _MHD_S_STR_W_LEN(str)
Definition: mhd_str_types.h:66
MHD internal shared structures.
@ MHD_CONNECTION_HEADERS_PROCESSED
Definition: internal.h:646
macros for mhd_assert()
bool MHD_str_equal_caseless_bin_n_(const char *const str1, const char *const str2, size_t len)
Definition: mhd_str.c:749
Header for string manipulating helpers.
#define MHD_str_equal_caseless_s_bin_n_(a, s, l)
Definition: mhd_str.h:122
MHD_DigestAuthAlgo3
Definition: microhttpd.h:4756
@ MHD_DIGEST_AUTH_ALGO3_MD5_SESSION
Definition: microhttpd.h:4774
@ MHD_DIGEST_AUTH_ALGO3_MD5
Definition: microhttpd.h:4767
@ MHD_DIGEST_AUTH_ALGO3_SHA256
Definition: microhttpd.h:4780
@ MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION
Definition: microhttpd.h:4800
@ MHD_DIGEST_AUTH_ALGO3_INVALID
Definition: microhttpd.h:4762
@ MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION
Definition: microhttpd.h:4787
@ MHD_DIGEST_AUTH_ALGO3_SHA512_256
Definition: microhttpd.h:4793
MHD_DigestAuthQOP
Definition: microhttpd.h:5099
@ MHD_DIGEST_AUTH_QOP_AUTH
Definition: microhttpd.h:5120
@ MHD_DIGEST_AUTH_QOP_INVALID
Definition: microhttpd.h:5105
@ MHD_DIGEST_AUTH_QOP_NONE
Definition: microhttpd.h:5115
@ MHD_DIGEST_AUTH_QOP_AUTH_INT
Definition: microhttpd.h:5126
@ MHD_HEADER_KIND
Definition: microhttpd.h:2260
struct MHD_Request rq
Definition: internal.h:1365
enum MHD_CONNECTION_STATE state
Definition: internal.h:1565
struct MHD_Daemon * daemon
Definition: internal.h:675
enum MHD_ValueKind kind
Definition: internal.h:396
const char * value
Definition: internal.h:386
struct MHD_HTTP_Req_Header * next
Definition: internal.h:366
const char * header
Definition: internal.h:376
struct MHD_HTTP_Header * headers_received
Definition: internal.h:388
struct _MHD_str_w_len token68
Definition: basicauth.h:40
const char *const str
Definition: mhd_str_types.h:41
const size_t len
Definition: mhd_str_types.h:42
const char * str
Definition: mhd_str_types.h:50