Coverage Report

Created: 2025-08-26 06:29

/src/mhd_helper.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2025 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
//
15
////////////////////////////////////////////////////////////////////////////////
16
#include "mhd_helper.h"
17
18
#include <arpa/inet.h>
19
#include <netinet/in.h>
20
#include <sys/socket.h>
21
#include <unistd.h>
22
23
#include <algorithm>
24
#include <cstring>
25
#include <fcntl.h>
26
#include <errno.h>
27
#include <poll.h>
28
29
std::unique_ptr<FuzzedDataProvider> g_fdp;
30
std::mutex g_fdp_mu;
31
32
0
std::string b64encode(const std::string &in) {
33
0
  static const char* tbl =
34
0
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
35
0
  std::string out;
36
0
  size_t i = 0;
37
0
  while (i + 2 < in.size()) {
38
0
    unsigned v = (unsigned((unsigned char)in[i]) << 16) |
39
0
                 (unsigned((unsigned char)in[i+1]) << 8) |
40
0
                 (unsigned((unsigned char)in[i+2]));
41
0
    out.push_back(tbl[(v >> 18) & 63]);
42
0
    out.push_back(tbl[(v >> 12) & 63]);
43
0
    out.push_back(tbl[(v >> 6) & 63]);
44
0
    out.push_back(tbl[(v) & 63]);
45
0
    i += 3;
46
0
  }
47
0
  if (i + 1 == in.size()) {
48
0
    unsigned v = (unsigned((unsigned char)in[i]) << 16);
49
0
    out.push_back(tbl[(v >> 18) & 63]);
50
0
    out.push_back(tbl[(v >> 12) & 63]);
51
0
    out.push_back('=');
52
0
    out.push_back('=');
53
0
  } else if (i + 2 == in.size()) {
54
0
    unsigned v = (unsigned((unsigned char)in[i]) << 16) |
55
0
                 (unsigned((unsigned char)in[i+1]) << 8);
56
0
    out.push_back(tbl[(v >> 18) & 63]);
57
0
    out.push_back(tbl[(v >> 12) & 63]);
58
0
    out.push_back(tbl[(v >> 6) & 63]);
59
0
    out.push_back('=');
60
0
  }
61
0
  return out;
62
0
}
63
64
3.73k
enum MHD_Bool ToMhdBool(bool b) {
65
3.73k
  return b ? MHD_YES : MHD_NO;
66
3.73k
}
67
68
7.27k
std::string safe_ascii(const std::string& in, bool allow_space) {
69
7.27k
  std::string out; out.reserve(in.size());
70
128k
  for (unsigned char c : in) {
71
128k
    if (!c || c=='\r' || c=='\n' || c<32 || c>=127 || (!allow_space && c==' ')) {
72
62.2k
      continue;
73
62.2k
    }
74
66.0k
    out.push_back((char)c);
75
66.0k
  }
76
7.27k
  if (out.empty()) {
77
3.65k
    out = "x";
78
3.65k
  }
79
80
7.27k
  return out;
81
7.27k
}
82
83
// Dummy functions
84
0
static enum MHD_Bool kv_cb(void*, enum MHD_ValueKind, const struct MHD_NameAndValue*) {
85
0
  return MHD_YES;
86
0
}
87
0
static enum MHD_Bool post_cb(void*, const struct MHD_PostField* pf) {
88
0
  return MHD_YES;
89
0
}
90
91
/* Start of internal helpers for sending http message to daemon through localhost socket */
92
0
static int create_socket(uint16_t port) {
93
0
  int fd = socket(AF_INET, SOCK_STREAM, 0);
94
0
  if (fd < 0) {
95
0
    return -1;
96
0
  }
97
98
  // Use flag to avoid blocking on socket
99
0
  int flags = fcntl(fd, F_GETFL, 0);
100
0
  if (flags >= 0) {
101
0
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
102
0
  }
103
0
  struct linger lg{1, 0};
104
0
  setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
105
106
  // configure the socket to target the daemon
107
0
  sockaddr_in addr{};
108
0
  addr.sin_family = AF_INET;
109
0
  addr.sin_port = htons(port);
110
0
  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
111
112
  // Try connect to the daemon on the binded port in localhost
113
0
  int rc = connect(fd, (sockaddr*)&addr, sizeof(addr));
114
0
  if (rc == 0) {
115
0
    return fd;
116
0
  }
117
118
  // Early exit for invalid connection
119
0
  if (errno != EINPROGRESS) {
120
0
    close(fd);
121
0
    return -1;
122
0
  }
123
0
  pollfd p{fd, POLLOUT, 0};
124
0
  if (poll(&p, 1, 5) <= 0) {
125
0
    close(fd);
126
0
    return -1;
127
0
  }
128
0
  int err = 0;
129
0
  socklen_t elen = sizeof(err);
130
0
  if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen) != 0 || err != 0) {
131
0
    close(fd);
132
0
    return -1;
133
0
  }
134
135
  // Return the created socket
136
0
  return fd;
137
0
}
138
139
0
static void generate_daemon_options(const std::string& method, DaemonOpts& opts) {
140
0
  std::lock_guard<std::mutex> lk(g_fdp_mu);
141
0
  if (!g_fdp) {
142
0
    return;
143
0
  }
144
145
  // Generate general daemon options
146
0
  opts.omit_host = g_fdp->ConsumeBool();
147
0
  opts.bad_cl = g_fdp->ConsumeBool() && g_fdp->ConsumeBool();
148
0
  opts.keep_alive = g_fdp->ConsumeBool();
149
0
  opts.extra_headers = g_fdp->ConsumeBool();
150
0
  opts.use_digest = g_fdp->ConsumeBool();
151
0
  opts.send_malformed_digest = g_fdp->ConsumeBool();
152
0
  if (g_fdp->ConsumeBool()) {
153
0
    opts.realm_hint = g_fdp->ConsumeRandomLengthString(16);
154
0
    if (opts.realm_hint.empty()) opts.realm_hint = "hint";
155
0
  }
156
157
  // Generate specific daemon options with specific method
158
0
  if (!method.empty() && (method == "POST" || g_fdp->ConsumeBool())) {
159
0
    opts.te_chunked = g_fdp->ConsumeBool();
160
0
    opts.as_multipart = g_fdp->ConsumeBool();
161
0
    if (g_fdp->ConsumeBool()) {
162
0
      opts.boundary = safe_ascii(g_fdp->ConsumeRandomLengthString(24), false);
163
0
      if (opts.boundary.empty()) opts.boundary = "b";
164
0
    }
165
0
  }
166
0
}
167
168
static std::string generate_auth_header(const DaemonOpts& opts,
169
                                        const std::string& method,
170
                                        const std::string& path,
171
                                        const std::string& auth_user,
172
                                        const std::string& auth_pass,
173
0
                                        bool garble_auth) {
174
  // For basic auth only request
175
0
  if (!opts.use_digest) {
176
0
    if (!garble_auth) {
177
0
      std::string up = auth_user + ":" + auth_pass;
178
0
      return "Authorization: Basic " + b64encode(up) + "\r\n";
179
0
    }
180
0
    static const char* kBad[] = {
181
0
      "Authorization: Basic\r\n",
182
0
      "Authorization: Basic =\r\n",
183
0
      "Authorization: Bearer ???\r\n",
184
0
      "Authorization:\r\n"
185
0
    };
186
0
    unsigned idx = (auth_user.empty() ? 0u : (unsigned char)auth_user[0]) %
187
0
                   (unsigned)(sizeof(kBad)/sizeof(kBad[0]));
188
0
    return std::string(kBad[idx]);
189
0
  }
190
191
  // For digest auth with malformed headers
192
0
  if (!opts.send_malformed_digest) {
193
0
    std::string u = auth_user.empty() ? "user" : auth_user;
194
0
    std::string r = opts.realm_hint;
195
0
    std::string uri = (path.empty() || path[0] != '/') ? ("/" + path) : path;
196
0
    if (uri.empty()) uri = "/";
197
0
    std::string h = "Authorization: Digest ";
198
0
    h += "username=\"" + u + "\", ";
199
0
    h += "realm=\""    + r + "\", ";
200
0
    h += "nonce=\"deadbeef\", ";
201
0
    h += "uri=\""      + uri + "\", ";
202
0
    h += "response=\"00000000000000000000000000000000\", ";
203
0
    h += "opaque=\"cafebabe\", ";
204
0
    h += "qop=auth, ";
205
0
    h += "nc=00000001, cnonce=\"0123456789abcdef\"\r\n";
206
0
    return h;
207
0
  }
208
209
  // For digest auth with correctly formatted headers with random data
210
0
  static const char* kBadDigest[] = {
211
0
    "Authorization: Digest\r\n",
212
0
    "Authorization: Digest username=\r\n",
213
0
    "Authorization: Digest realm=\"\", uri=/, response=\r\n",
214
0
    "Authorization: Digest nonce=,opaque=\r\n"
215
0
  };
216
0
  unsigned idx = (unsigned char)(auth_user.empty()?0:auth_user[0]) %
217
0
                 (unsigned)(sizeof(kBadDigest)/sizeof(kBadDigest[0]));
218
219
0
  return std::string(kBadDigest[idx]);
220
0
}
221
222
static void append_headers(std::string& req,
223
                           const DaemonOpts& opts,
224
0
                           const std::string& auth_header) {
225
  // Set host
226
0
  if (!opts.omit_host) {
227
0
    req += "Host: 127.0.0.1\r\n";
228
0
  }
229
230
  // Append auth headers
231
0
  req += auth_header;
232
233
  // Append general headers
234
0
  if (opts.extra_headers) {
235
0
    req += "User-Agent: fuzz\r\n";
236
0
    req += "Accept: */*\r\n";
237
0
    req += "X-Fuzz: 1\r\n";
238
0
    req += "X-Dup: a\r\nX-Dup: b\r\n";
239
0
  }
240
0
}
241
242
static std::string make_multipart(const DaemonOpts& opts,
243
                                  const std::string& body,
244
0
                                  std::string& content_type_line_out) {
245
  // Do nothing for non-multipart iteration
246
0
  if (!opts.as_multipart) {
247
0
    content_type_line_out.clear();
248
0
    return body;
249
0
  }
250
251
  // Configure the request body to be multipart format
252
0
  std::string b = opts.boundary.empty() ? "b" : opts.boundary;
253
0
  std::string mp;
254
0
  mp += "--" + b + "\r\n";
255
0
  mp += "Content-Disposition: form-data; name=\"f\"; filename=\"x\"\r\n";
256
0
  if (!body.empty()) mp += "Content-Type: application/octet-stream\r\n";
257
0
  mp += "\r\n";
258
0
  mp += body;
259
0
  mp += "\r\n--" + b + "--\r\n";
260
0
  content_type_line_out = "Content-Type: multipart/form-data; boundary=" + b + "\r\n";
261
262
0
  return mp;
263
0
}
264
265
266
static void append_request_headers(std::string& req,
267
                                   const DaemonOpts& opts,
268
                                   size_t payload_size,
269
0
                                   const std::string& content_type_line) {
270
  // Append content type header
271
0
  if (!content_type_line.empty()) {
272
0
    req += content_type_line;
273
0
  }
274
0
  if (payload_size == 0) {
275
0
    return;
276
0
  }
277
278
  // Append encoding and payload size headers
279
0
  if (opts.te_chunked) {
280
0
    req += "Transfer-Encoding: chunked\r\n";
281
0
  } else {
282
0
    if (!opts.bad_cl) {
283
0
      req += "Content-Length: " + std::to_string(payload_size) + "\r\n";
284
0
    } else {
285
0
      req += "Content-Length: " + std::to_string(payload_size + 5) + "\r\n";
286
0
    }
287
0
  }
288
289
  // Append connection lifeline header
290
0
  req += opts.keep_alive ? "Connection: keep-alive\r\n" : "Connection: close\r\n";
291
0
}
292
293
0
static void append_chunked_payload(std::string& req, const std::string& payload) {
294
  // End the request body gracefully for empty payload
295
0
  if (payload.empty()) {
296
0
    req += "0\r\n\r\n";
297
0
    return;
298
0
  }
299
300
  // Continue to write all the body data chunk by chunk to the request
301
0
  size_t off = 0;
302
0
  while (off < payload.size()) {
303
0
    char szbuf[32];
304
305
0
    size_t remain = payload.size() - off;
306
0
    size_t chunk = std::min(remain > 1 ? remain / 2 : 1, size_t(4096));
307
308
0
    int n = snprintf(szbuf, sizeof(szbuf), "%zx\r\n", chunk);
309
0
    if (n > 0) {
310
0
      req.append(szbuf, (size_t)n);
311
0
    }
312
0
    req.append(payload.data() + off, chunk);
313
0
    req += "\r\n";
314
0
    off += chunk;
315
0
  }
316
0
  req += "0\r\n\r\n";
317
0
}
318
319
320
0
static void send_request(int fd, const std::string& req) {
321
  // Configure sent parameters and max timeout
322
0
  size_t off = 0;
323
0
  const int kSendStepMs = 2;
324
0
  const int kSendMaxMs = 8;
325
0
  int waited = 0;
326
327
  // Continue to send data though the socket until all data is sent or timeout is reached
328
0
  while (off < req.size()) {
329
0
    ssize_t s = send(fd, req.data() + off, req.size() - off, 0);
330
0
    if (s > 0) {
331
0
      off += (size_t)s;
332
0
      continue;
333
0
    }
334
335
0
    if (s < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) {
336
0
      pollfd p{fd, POLLOUT, 0};
337
0
      if (poll(&p, 1, kSendStepMs) <= 0) {
338
0
        waited += kSendStepMs;
339
0
        if (waited >= kSendMaxMs) {
340
0
          break;
341
0
        }
342
0
      }
343
0
      continue;
344
0
    }
345
0
    break;
346
0
  }
347
348
0
  shutdown(fd, SHUT_WR);
349
0
}
350
351
0
static void wait_for_response(int fd) {
352
  // Configure response waiting parameters and timeout
353
0
  static const int kStepMs = 2;
354
0
  static const int kMaxMs = 8;
355
0
  static const size_t kMaxRead = 64 * 1024;
356
0
  int waited = 0;
357
0
  size_t total = 0;
358
359
  // Continue to receive data from daemon throug the socket until max data threshold or timeout is reached
360
0
  while (true) {
361
0
    pollfd p{fd, POLLIN, 0};
362
0
    int pr = poll(&p, 1, kStepMs);
363
0
    if (pr <= 0) {
364
0
      waited += kStepMs;
365
0
      if (waited >= kMaxMs) {
366
0
        break;
367
0
      }
368
369
0
      continue;
370
0
    }
371
372
0
    char buf[1024];
373
0
    ssize_t r = recv(fd, buf, sizeof(buf), 0);
374
0
    if (r > 0) {
375
0
      total += (size_t)r;
376
0
      if (total >= kMaxRead) break;
377
0
      continue;
378
0
    }
379
0
    if (r == 0) {
380
0
      break;
381
0
    }
382
0
    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
383
0
      continue;
384
0
    }
385
386
0
    break;
387
0
  }
388
0
}
389
/* End of internal helpers for sending http message to daemon through localhost socket */
390
391
void send_http_request_blocking(uint16_t port,
392
                                const std::string& method,
393
                                const std::string& path,
394
                                const std::string& auth_user,
395
                                const std::string& auth_pass,
396
                                const std::string& body,
397
0
                                bool garble_auth) {
398
  // Create and connect to the daemon with HTTP localhost socket
399
0
  int fd = create_socket(port);
400
0
  if (fd < 0) {
401
0
    return;
402
0
  }
403
404
  // Generate options for the daemon connection and request
405
0
  DaemonOpts opts;
406
0
  generate_daemon_options(method, opts);
407
408
  // Build the request body for the random request
409
0
  std::string req;
410
0
  req.reserve(512 + body.size());
411
0
  const std::string& m = method.empty() ? std::string("GET") : method;
412
0
  req += m;
413
0
  req += " ";
414
0
  std::string full_path = (path.empty() || path[0] != '/') ? ("/" + path) : path;
415
0
  if (full_path.empty()) full_path = "/";
416
0
  req += full_path;
417
0
  req += " HTTP/1.1\r\n";
418
419
  // Generate auth headers
420
0
  std::string auth_header = generate_auth_header(opts, m, path, auth_user, auth_pass, garble_auth);
421
422
  // Append headers into the daemon request
423
0
  append_headers(req, opts, auth_header);
424
425
  // Randomly make the request with multipart data
426
0
  std::string content_type_line;
427
0
  std::string payload = make_multipart(opts, body, content_type_line);
428
429
  // Append additional request specific headers
430
0
  append_request_headers(req, opts, payload.size(), content_type_line);
431
0
  req += "\r\n";
432
433
  // Add random body content to the request object depends on the content type chosen
434
0
  if (opts.te_chunked) {
435
0
    append_chunked_payload(req, payload);
436
0
  } else {
437
0
    req += payload;
438
0
  }
439
440
  // Sending the request to the daemon through the localhost socket
441
0
  send_request(fd, req);
442
443
  // Wait for a while for daemon response
444
0
  wait_for_response(fd);
445
446
  // Close the socket
447
0
  close(fd);
448
0
}
449
450
/* Start of internal helpers for daemon request handling */
451
static void generate_daemon_req_opts(DaemonReqOpts& o,
452
0
                                     enum MHD_HTTP_Method method) {
453
0
  std::lock_guard<std::mutex> lk(g_fdp_mu);
454
0
  if (!g_fdp) {
455
0
    o.realm = "realm";
456
0
    return;
457
0
  }
458
459
  // Generate basic daemon request options
460
0
  o.realm = g_fdp->ConsumeRandomLengthString(16);
461
0
  if (o.realm.empty()) {
462
0
    o.realm = "realm";
463
0
  }
464
0
  o.allowed_user = g_fdp->ConsumeRandomLengthString(12);
465
0
  o.allowed_pass = g_fdp->ConsumeRandomLengthString(12);
466
0
  o.force_challenge = g_fdp->ConsumeBool();
467
0
  o.force_bad = g_fdp->ConsumeBool();
468
0
  o.flip_ok_to_forbidden = g_fdp->ConsumeBool();
469
0
  o.prefer_utf8 = ToMhdBool(g_fdp->ConsumeBool());
470
0
  o.use_digest = g_fdp->ConsumeBool();
471
472
0
  o.qop  = g_fdp->ConsumeBool() ? MHD_DIGEST_AUTH_MULT_QOP_AUTH
473
0
                                : MHD_DIGEST_AUTH_MULT_QOP_AUTH_INT;
474
0
  o.algo = g_fdp->ConsumeBool() ? MHD_DIGEST_AUTH_MULT_ALGO_ANY
475
0
                                : MHD_DIGEST_AUTH_MULT_ALGO_MD5;
476
477
0
  o.send_stale = g_fdp->ConsumeBool() ? MHD_YES : MHD_NO;
478
0
  o.allow_userhash = g_fdp->ConsumeBool() ? MHD_YES : MHD_NO;
479
0
  o.use_opaque = g_fdp->ConsumeBool();
480
481
  // Generate daemon request options for form encoded request
482
0
  o.buf_sz = 64 + g_fdp->ConsumeIntegralInRange<size_t>(0, 8192);
483
0
  o.max_nonstream = g_fdp->ConsumeIntegralInRange<size_t>(0, 64 * 1024);
484
0
  o.enc = g_fdp->PickValueInArray<enum MHD_HTTP_PostEncoding>({
485
0
    MHD_HTTP_POST_ENCODING_FORM_URLENCODED,
486
0
    MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA,
487
0
    MHD_HTTP_POST_ENCODING_TEXT_PLAIN,
488
0
    MHD_HTTP_POST_ENCODING_OTHER
489
0
  });
490
0
  o.do_parse_post = g_fdp->ConsumeBool() || (method == MHD_HTTP_METHOD_POST);
491
0
  o.do_process_upload = g_fdp->ConsumeBool();
492
0
}
493
494
static const struct MHD_UploadAction*
495
0
post_done_cb(struct MHD_Request *req, void *cls, enum MHD_PostParseResult pr) {
496
0
  return MHD_upload_action_continue(req);
497
0
}
498
499
static const struct MHD_UploadAction*
500
post_reader(struct MHD_Request *req,
501
            void *cls,
502
            const struct MHD_String *name,
503
            const struct MHD_StringNullable *filename,
504
            const struct MHD_StringNullable *content_type,
505
            const struct MHD_StringNullable *encoding,
506
            size_t size,
507
            const void *data,
508
            uint_fast64_t off,
509
0
            enum MHD_Bool final_data) {
510
0
  return MHD_upload_action_continue(req);
511
0
}
512
513
static const struct MHD_UploadAction*
514
upload_cb(void *upload_cls,
515
          struct MHD_Request *request,
516
          size_t content_data_size,
517
0
          void *content_data) {
518
0
  enum MHD_HTTP_StatusCode sc = content_data_size ? MHD_HTTP_STATUS_OK : MHD_HTTP_STATUS_NO_CONTENT;
519
0
  struct MHD_Response *r = MHD_response_from_empty(sc);
520
0
  if (!r) {
521
0
    return NULL;
522
0
  }
523
524
0
  return MHD_upload_action_from_response(request, r);
525
0
}
526
527
0
static const struct MHD_Action* request_parsing(struct MHD_Request* request, const DaemonReqOpts& o) {
528
  // Handle parsing of post request with form data or general parameters
529
0
  if (o.do_parse_post) {
530
0
    const struct MHD_Action *a =
531
0
      MHD_action_parse_post(request,
532
0
                            o.buf_sz,
533
0
                            o.max_nonstream,
534
0
                            o.enc,
535
0
                            &post_reader, nullptr,
536
0
                            &post_done_cb, nullptr);
537
0
    if (a) {
538
0
      MHD_request_get_post_data_cb(
539
0
        request,
540
0
        [](void *cls, const struct MHD_PostField *pf)->enum MHD_Bool {
541
0
          std::lock_guard<std::mutex> lk(g_fdp_mu);
542
0
          if (!g_fdp) {
543
0
            return MHD_YES;
544
0
          }
545
0
          return ToMhdBool(g_fdp->ConsumeBool());
546
0
        },
547
0
        nullptr);
548
0
      return a;
549
0
    }
550
0
  }
551
552
  // Handle parsing of upload request
553
0
  if (o.do_process_upload) {
554
0
    const struct MHD_Action *a =
555
0
      MHD_action_process_upload(request,
556
0
                                o.buf_sz,
557
0
                                &upload_cb, nullptr,
558
0
                                &upload_cb,  nullptr);
559
0
    if (a) {
560
0
      return a;
561
0
    }
562
0
  }
563
564
0
  return nullptr;
565
0
}
566
567
static const struct MHD_Action*
568
handle_basic_auth(struct MHD_Request* request,
569
0
                  const DaemonReqOpts& o) {
570
0
  union MHD_RequestInfoDynamicData req_data;
571
0
  enum MHD_StatusCode res = MHD_request_get_info_dynamic(
572
0
      request, MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS, &req_data);
573
574
  // Send bad header response
575
0
  if (o.force_bad || (MHD_SC_REQ_AUTH_DATA_BROKEN == res)) {
576
0
    return MHD_action_from_response(
577
0
      request,
578
0
      MHD_response_from_buffer_static(
579
0
        MHD_HTTP_STATUS_BAD_REQUEST, 10, "bad_header"));
580
0
  }
581
582
  // Send unauthorized response
583
0
  if (o.force_challenge || (MHD_SC_AUTH_ABSENT == res)) {
584
0
    const char* realm_cstr = o.realm.c_str();
585
0
    return MHD_action_basic_auth_challenge_a(
586
0
      request,
587
0
      realm_cstr,
588
0
      o.prefer_utf8,
589
0
      MHD_response_from_buffer_static(
590
0
        MHD_HTTP_STATUS_UNAUTHORIZED, 4, "auth"));
591
0
  }
592
593
  // Fail safe to abort request if unknown request status is provided
594
0
  if (MHD_SC_OK != res) {
595
0
    return MHD_action_abort_request(request);
596
0
  }
597
598
  // Prepeare and perform basic auth check
599
0
  const struct MHD_AuthBasicCreds *creds = req_data.v_auth_basic_creds;
600
0
  bool user_ok = (creds->username.len == o.allowed_user.size()) &&
601
0
                 (0 == memcmp(o.allowed_user.data(),
602
0
                              creds->username.cstr,
603
0
                              creds->username.len));
604
0
  bool pass_ok = (creds->password.len == o.allowed_pass.size()) &&
605
0
                 (0 == memcmp(o.allowed_pass.data(),
606
0
                              creds->password.cstr,
607
0
                              creds->password.len));
608
0
  bool ok = user_ok && pass_ok;
609
610
  // Try randomly flip the result
611
0
  if (o.flip_ok_to_forbidden) {
612
0
    ok = false;
613
0
  }
614
615
  // Return result with status in response
616
0
  if (ok) {
617
0
    return MHD_action_from_response(
618
0
      request,
619
0
      MHD_response_from_buffer_static(
620
0
        MHD_HTTP_STATUS_OK, 2, "OK"));
621
0
  }
622
623
0
  return MHD_action_from_response(
624
0
    request,
625
0
    MHD_response_from_buffer_static(
626
0
      MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN"));
627
0
}
628
629
static const struct MHD_Action*
630
handle_digest_auth(struct MHD_Request* request,
631
0
                   const DaemonReqOpts& o) {
632
0
  union MHD_RequestInfoDynamicData req_data;
633
0
  enum MHD_StatusCode res = MHD_request_get_info_dynamic(
634
0
      request, MHD_REQUEST_INFO_DYNAMIC_AUTH_DIGEST_INFO, &req_data);
635
636
0
  const char* opaque_opt = o.use_opaque ? "opaque-token" : nullptr;
637
638
  // Early exit for missing auth in request
639
0
  if (MHD_SC_AUTH_ABSENT == res || o.force_challenge) {
640
0
    return MHD_action_digest_auth_challenge_a(
641
0
      request,
642
0
      o.realm.c_str(),
643
0
      "0",
644
0
      opaque_opt,
645
0
      o.send_stale,
646
0
      o.qop,
647
0
      o.algo,
648
0
      o.allow_userhash,
649
0
      MHD_YES,
650
0
      MHD_response_from_buffer_static(
651
0
        MHD_HTTP_STATUS_UNAUTHORIZED, 4, "auth"));
652
0
  }
653
654
  // Early exit for invalid or malformed request headers
655
0
  if (MHD_SC_REQ_AUTH_DATA_BROKEN == res || o.force_bad) {
656
0
    return MHD_action_from_response(
657
0
      request,
658
0
      MHD_response_from_buffer_static(
659
0
        MHD_HTTP_STATUS_BAD_REQUEST, 15, "Header Invalid."));
660
0
  }
661
662
  // Fail safe to abort request if unknown response status is provided
663
0
  if (MHD_SC_OK != res) {
664
0
    return MHD_action_abort_request(request);
665
0
  }
666
667
  // Prepeare and perform digest auth check
668
0
  const struct MHD_AuthDigestInfo *di = req_data.v_auth_digest_info;
669
0
  bool user_ok = (di->username.len == o.allowed_user.size()) &&
670
0
                 (0 == memcmp(o.allowed_user.data(),
671
0
                              di->username.cstr,
672
0
                              di->username.len));
673
674
0
  if (user_ok) {
675
0
    enum MHD_DigestAuthResult auth_res =
676
0
      MHD_digest_auth_check(request,
677
0
                            o.realm.c_str(),
678
0
                            o.allowed_user.c_str(),
679
0
                            o.allowed_pass.c_str(),
680
0
                            0,
681
0
                            o.qop,
682
0
                            o.algo);
683
684
    // Return auth result or randomly flip the result
685
0
    if (MHD_DAUTH_OK == auth_res) {
686
0
      if (!o.flip_ok_to_forbidden) {
687
0
        return MHD_action_from_response(
688
0
          request,
689
0
          MHD_response_from_buffer_static(
690
0
            MHD_HTTP_STATUS_OK, 2, "OK"));
691
0
      }
692
0
      return MHD_action_from_response(
693
0
        request,
694
0
        MHD_response_from_buffer_static(
695
0
          MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN"));
696
0
    }
697
698
0
    if (MHD_DAUTH_NONCE_STALE == auth_res) {
699
0
      return MHD_action_digest_auth_challenge_a(
700
0
        request,
701
0
        o.realm.c_str(),
702
0
        "0",
703
0
        opaque_opt,
704
0
        MHD_YES,
705
0
        o.qop,
706
0
        o.algo,
707
0
        o.allow_userhash,
708
0
        MHD_YES,
709
0
        MHD_response_from_buffer_static(
710
0
          MHD_HTTP_STATUS_UNAUTHORIZED, 4, "auth"));
711
0
    }
712
713
0
    return MHD_action_from_response(
714
0
      request,
715
0
      MHD_response_from_buffer_static(
716
0
        MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN"));
717
0
  }
718
719
0
  return MHD_action_from_response(
720
0
    request,
721
0
    MHD_response_from_buffer_static(
722
0
      MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN"));
723
0
}
724
/* End of internal helpers for daemon request handling */
725
726
MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3)
727
const struct MHD_Action*
728
req_cb(void* cls,
729
       struct MHD_Request* MHD_RESTRICT request,
730
       const struct MHD_String* MHD_RESTRICT path,
731
       enum MHD_HTTP_Method method,
732
0
       uint_fast64_t upload_size) {
733
0
  DaemonReqOpts opts;
734
0
  generate_daemon_req_opts(opts, method);
735
736
  // Try parinsg or streaming request
737
0
  if (const struct MHD_Action* a = request_parsing(request, opts)) {
738
0
    return a;
739
0
  }
740
741
  // Perform basic or digest auth on the request
742
0
  if (!opts.use_digest) {
743
0
    return handle_basic_auth(request, opts);
744
0
  }
745
0
  return handle_digest_auth(request, opts);
746
0
}
747
748
MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3)
749
const struct MHD_Action*
750
req_cb_stream(void*,
751
              struct MHD_Request* MHD_RESTRICT request,
752
              const struct MHD_String* MHD_RESTRICT path,
753
              enum MHD_HTTP_Method method,
754
0
              uint_fast64_t upload_size) {
755
  // Fuzz MHD_request_get_value for different parameters on random request
756
0
  MHD_request_get_value(request, MHD_VK_HEADER, "host");
757
0
  MHD_request_get_value(request, MHD_VK_HEADER, "content-type");
758
0
  MHD_request_get_value(request, MHD_VK_COOKIE, "cookie");
759
0
  MHD_request_get_value(request, MHD_VK_GET_ARGUMENT, "q");
760
0
  MHD_request_get_values_cb(request, MHD_VK_HEADER, kv_cb, nullptr);
761
0
  MHD_request_get_values_cb(request, MHD_VK_COOKIE, kv_cb, nullptr);
762
0
  MHD_request_get_values_cb(request, MHD_VK_GET_ARGUMENT, kv_cb, nullptr);
763
764
  // Fuzz MHD_request_get_post_data_cb on random request
765
0
  MHD_request_get_post_data_cb(request, post_cb, nullptr);
766
767
768
  // Fuzz MHD_request_get_info_fixed for different parameters on random request
769
0
  union MHD_RequestInfoFixedData fix;
770
0
  MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_HTTP_VER, &fix);
771
0
  MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_HTTP_METHOD, &fix);
772
0
  MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_DAEMON, &fix);
773
0
  MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_CONNECTION, &fix);
774
0
  MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_STREAM, &fix);
775
0
  MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_APP_CONTEXT, &fix);
776
777
  // Fuzz MHD_request_get_info_dynamic for different parameters on random request
778
0
  union MHD_RequestInfoDynamicData dyn;
779
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_HTTP_METHOD_STRING, &dyn);
780
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_URI, &dyn);
781
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_URI_PARAMS, &dyn);
782
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_COOKIES, &dyn);
783
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_HEADER_SIZE, &dyn);
784
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_POST_PARAMS, &dyn);
785
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_PRESENT, &dyn);
786
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_CHUNKED, &dyn);
787
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_SIZE_TOTAL, &dyn);
788
0
  MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_SIZE_RECIEVED, &dyn);
789
790
  // Fuzz response creation from random request processing
791
0
  struct MHD_Response* resp = MHD_response_from_empty(MHD_HTTP_STATUS_NO_CONTENT);
792
0
  if (!resp) {
793
0
    return MHD_action_abort_request(request);
794
0
  }
795
796
  // Fuzz response and request abortion
797
0
  MHD_response_add_header(resp, "x-fuzz", "values");
798
0
  const struct MHD_Action* act = MHD_action_from_response(request, resp);
799
0
  MHD_response_destroy(resp);
800
0
  return act ? act : MHD_action_abort_request(request);
801
0
}
802
803
MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3)
804
const struct MHD_Action*
805
req_cb_process(void*,
806
               struct MHD_Request* MHD_RESTRICT request,
807
               const struct MHD_String* MHD_RESTRICT path,
808
               enum MHD_HTTP_Method method,
809
0
               uint_fast64_t upload_size) {
810
  // Create info unions
811
0
  union MHD_RequestInfoFixedData f;
812
0
  union MHD_RequestInfoDynamicData d;
813
814
  // Fuzz MHD_request_get_info_fixed_sz for different parameters on random request
815
0
  MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_HTTP_VER, &f, sizeof(f));
816
0
  MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_HTTP_METHOD, &f, sizeof(f));
817
0
  MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_DAEMON, &f, sizeof(f));
818
0
  MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_CONNECTION, &f, sizeof(f));
819
0
  MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_STREAM, &f, sizeof(f));
820
0
  MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_APP_CONTEXT, &f, sizeof(f));
821
822
  // Fuzz MHD_request_get_info_dynamic_sz for different parameters on random request
823
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_HTTP_METHOD_STRING, &d, sizeof(d));
824
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_URI, &d, sizeof(d));
825
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_URI_PARAMS, &d, sizeof(d));
826
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_COOKIES, &d, sizeof(d));
827
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_HEADER_SIZE, &d, sizeof(d));
828
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_AUTH_DIGEST_INFO, &d, sizeof(d));
829
0
  MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS, &d, sizeof(d));
830
831
0
  {
832
0
    static const char realm[] = "fuzz-realm";
833
0
    static const char user[]  = "u";
834
0
    static const char pass[]  = "p";
835
836
0
    enum MHD_DigestAuthAlgo algos[] = {
837
0
      MHD_DIGEST_AUTH_ALGO_MD5,
838
0
      MHD_DIGEST_AUTH_ALGO_SHA256,
839
0
      MHD_DIGEST_AUTH_ALGO_SHA512_256
840
0
    };
841
842
0
    for (unsigned i = 0; i < (unsigned)(sizeof(algos)/sizeof(algos[0])); ++i) {
843
0
      size_t sz = MHD_digest_get_hash_size(algos[i]);
844
0
      if (sz == 0 || sz > 64) {
845
0
        continue;
846
0
      }
847
0
      unsigned char ha1[64];
848
0
      if (MHD_SC_OK == MHD_digest_auth_calc_userdigest(algos[i], user, realm, pass, sz, ha1)) {
849
0
        MHD_digest_auth_check_digest(
850
0
            request, realm, user, sz, ha1,
851
0
            0, MHD_DIGEST_AUTH_MULT_QOP_AUTH_ANY,
852
0
            MHD_DIGEST_AUTH_MULT_ALGO_ANY_NON_SESSION);
853
0
      }
854
0
    }
855
0
  }
856
857
  // Force OK response
858
0
  struct MHD_Response* r = MHD_response_from_empty(MHD_HTTP_STATUS_OK);
859
0
  return MHD_action_from_response(request, r);
860
0
}