1 /** @file
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 */
18
19 #define min(a, b) \
20 ({ \
21 __typeof__(a) _a = (a); \
22 __typeof__(b) _b = (b); \
23 _a < _b ? _a : _b; \
24 })
25
26 #include "url_sig.h"
27
28 #include <inttypes.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <time.h>
34 #include <openssl/hmac.h>
35 #include <openssl/evp.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <arpa/inet.h>
39 #include <limits.h>
40 #include <ctype.h>
41 #include <stdint.h>
42 #include <stdbool.h>
43
44 #ifdef HAVE_PCRE_PCRE_H
45 #include <pcre/pcre.h>
46 #else
47 #include <pcre.h>
48 #endif
49
50 #include <ts/ts.h>
51 #include <ts/remap.h>
52
53 static const char PLUGIN_NAME[] = "url_sig";
54
55 struct config {
56 TSHttpStatus err_status;
57 char *err_url;
58 char keys[MAX_KEY_NUM][MAX_KEY_LEN];
59 pcre *regex;
60 pcre_extra *regex_extra;
61 int pristine_url_flag;
62 char *sig_anchor;
63 bool ignore_expiry;
64 };
65
66 static void
free_cfg(struct config * cfg)67 free_cfg(struct config *cfg)
68 {
69 TSError("[url_sig] Cleaning up");
70 TSfree(cfg->err_url);
71 TSfree(cfg->sig_anchor);
72
73 if (cfg->regex_extra) {
74 #ifndef PCRE_STUDY_JIT_COMPILE
75 pcre_free(cfg->regex_extra);
76 #else
77 pcre_free_study(cfg->regex_extra);
78 #endif
79 }
80
81 if (cfg->regex) {
82 pcre_free(cfg->regex);
83 }
84
85 TSfree(cfg);
86 }
87
88 TSReturnCode
TSRemapInit(TSRemapInterface * api_info,char * errbuf,int errbuf_size)89 TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
90 {
91 if (!api_info) {
92 snprintf(errbuf, errbuf_size, "[tsremap_init] - Invalid TSRemapInterface argument");
93 return TS_ERROR;
94 }
95
96 if (api_info->tsremap_version < TSREMAP_VERSION) {
97 snprintf(errbuf, errbuf_size, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
98 (api_info->tsremap_version & 0xffff));
99 return TS_ERROR;
100 }
101
102 TSDebug(PLUGIN_NAME, "plugin is successfully initialized");
103 return TS_SUCCESS;
104 }
105
106 // To force a config file reload touch remap.config and do a "traffic_ctl config reload"
107 TSReturnCode
TSRemapNewInstance(int argc,char * argv[],void ** ih,char * errbuf,int errbuf_size)108 TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size)
109 {
110 char config_filepath_buf[PATH_MAX], *config_file;
111 struct config *cfg;
112
113 if ((argc < 3) || (argc > 4)) {
114 snprintf(errbuf, errbuf_size,
115 "[TSRemapNewInstance] - Argument count wrong (%d)... config file path is required first pparam, \"pristineurl\" is"
116 "optional second pparam.",
117 argc);
118 return TS_ERROR;
119 }
120 TSDebug(PLUGIN_NAME, "Initializing remap function of %s -> %s with config from %s", argv[0], argv[1], argv[2]);
121
122 if (argv[2][0] == '/') {
123 config_file = argv[2];
124 } else {
125 snprintf(config_filepath_buf, sizeof(config_filepath_buf), "%s/%s", TSConfigDirGet(), argv[2]);
126 config_file = config_filepath_buf;
127 }
128 TSDebug(PLUGIN_NAME, "config file name: %s", config_file);
129 FILE *file = fopen(config_file, "r");
130 if (file == NULL) {
131 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Error opening file %s", config_file);
132 return TS_ERROR;
133 }
134
135 char line[300];
136 int line_no = 0;
137 int keynum;
138 bool eat_comment = false;
139
140 cfg = TSmalloc(sizeof(struct config));
141 memset(cfg, 0, sizeof(struct config));
142
143 while (fgets(line, sizeof(line), file) != NULL) {
144 TSDebug(PLUGIN_NAME, "LINE: %s (%d)", line, (int)strlen(line));
145 line_no++;
146
147 if (eat_comment) {
148 // Check if final char is EOL, if so we are done eating
149 if (line[strlen(line) - 1] == '\n') {
150 eat_comment = false;
151 }
152 continue;
153 }
154 if (line[0] == '#' || strlen(line) <= 1) {
155 // Check if we have a comment longer than the full buffer if no EOL
156 if (line[strlen(line) - 1] != '\n') {
157 eat_comment = true;
158 }
159 continue;
160 }
161 char *pos = strchr(line, '=');
162 if (pos == NULL) {
163 TSError("[url_sig] Error parsing line %d of file %s (%s)", line_no, config_file, line);
164 continue;
165 }
166 *pos = '\0';
167 char *value = pos + 1;
168 while (isspace(*value)) { // remove whitespace
169 value++;
170 }
171 pos = strchr(value, '\n'); // remove the new line, terminate the string
172 if (pos != NULL) {
173 *pos = '\0';
174 }
175 if (pos == NULL || strlen(value) >= MAX_KEY_LEN) {
176 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Maximum key length (%d) exceeded on line %d", MAX_KEY_LEN - 1, line_no);
177 fclose(file);
178 free_cfg(cfg);
179 return TS_ERROR;
180 }
181 if (strncmp(line, "key", 3) == 0) {
182 if (strncmp(line + 3, "0", 1) == 0) {
183 keynum = 0;
184 } else {
185 TSDebug(PLUGIN_NAME, ">>> %s <<<", line + 3);
186 keynum = atoi(line + 3);
187 if (keynum == 0) {
188 keynum = -1; // Not a Number
189 }
190 }
191 TSDebug(PLUGIN_NAME, "key number %d == %s", keynum, value);
192 if (keynum >= MAX_KEY_NUM || keynum < 0) {
193 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Key number (%d) >= MAX_KEY_NUM (%d) or NaN", keynum, MAX_KEY_NUM);
194 fclose(file);
195 free_cfg(cfg);
196 return TS_ERROR;
197 }
198 snprintf(&cfg->keys[keynum][0], MAX_KEY_LEN, "%s", value);
199 } else if (strncmp(line, "error_url", 9) == 0) {
200 if (atoi(value)) {
201 cfg->err_status = atoi(value);
202 }
203 value += 3;
204 while (isspace(*value)) {
205 value++;
206 }
207 if (cfg->err_status == TS_HTTP_STATUS_MOVED_TEMPORARILY) {
208 cfg->err_url = TSstrndup(value, strlen(value));
209 } else {
210 cfg->err_url = NULL;
211 }
212 } else if (strncmp(line, "sig_anchor", 10) == 0) {
213 cfg->sig_anchor = TSstrndup(value, strlen(value));
214 } else if (strncmp(line, "excl_regex", 10) == 0) {
215 // compile and study regex
216 const char *errptr;
217 int erroffset, options = 0;
218
219 if (cfg->regex) {
220 TSDebug(PLUGIN_NAME, "Skipping duplicate excl_regex");
221 continue;
222 }
223
224 cfg->regex = pcre_compile(value, options, &errptr, &erroffset, NULL);
225 if (cfg->regex == NULL) {
226 TSDebug(PLUGIN_NAME, "Regex compilation failed with error (%s) at character %d", errptr, erroffset);
227 } else {
228 #ifdef PCRE_STUDY_JIT_COMPILE
229 options = PCRE_STUDY_JIT_COMPILE;
230 #endif
231 cfg->regex_extra = pcre_study(
232 cfg->regex, options, &errptr); // We do not need to check the error here because we can still run without the studying?
233 }
234 } else if (strncmp(line, "ignore_expiry", 13) == 0) {
235 if (strncmp(value, "true", 4) == 0) {
236 cfg->ignore_expiry = true;
237 TSError("[url_sig] Plugin IGNORES sig expiration");
238 }
239 } else {
240 TSError("[url_sig] Error parsing line %d of file %s (%s)", line_no, config_file, line);
241 }
242 }
243
244 fclose(file);
245
246 if (argc > 3) {
247 if (strcasecmp(argv[3], "pristineurl") == 0) {
248 cfg->pristine_url_flag = 1;
249
250 } else {
251 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - second pparam (if present) must be pristineurl");
252 free_cfg(cfg);
253 return TS_ERROR;
254 }
255 }
256
257 switch (cfg->err_status) {
258 case TS_HTTP_STATUS_MOVED_TEMPORARILY:
259 if (cfg->err_url == NULL) {
260 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Invalid config, err_status == 302, but err_url == NULL");
261 free_cfg(cfg);
262 return TS_ERROR;
263 }
264 break;
265 case TS_HTTP_STATUS_FORBIDDEN:
266 if (cfg->err_url != NULL) {
267 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Invalid config, err_status == 403, but err_url != NULL");
268 free_cfg(cfg);
269 return TS_ERROR;
270 }
271 break;
272 default:
273 snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Return code %d not supported", cfg->err_status);
274 free_cfg(cfg);
275 return TS_ERROR;
276 }
277
278 *ih = (void *)cfg;
279 return TS_SUCCESS;
280 }
281
282 void
TSRemapDeleteInstance(void * ih)283 TSRemapDeleteInstance(void *ih)
284 {
285 free_cfg((struct config *)ih);
286 }
287
288 static void
err_log(const char * url,const char * msg)289 err_log(const char *url, const char *msg)
290 {
291 if (msg && url) {
292 TSDebug(PLUGIN_NAME, "[URL=%s]: %s", url, msg);
293 TSError("[url_sig] [URL=%s]: %s", url, msg); // This goes to error.log
294 } else {
295 TSError("[url_sig] Invalid err_log request");
296 }
297 }
298
299 // See the README. All Signing parameters must be concatenated to the end
300 // of the url and any application query parameters.
301 static char *
getAppQueryString(const char * query_string,int query_length)302 getAppQueryString(const char *query_string, int query_length)
303 {
304 int done = 0;
305 char *p;
306 char buf[MAX_QUERY_LEN + 1];
307
308 if (query_length > MAX_QUERY_LEN) {
309 TSDebug(PLUGIN_NAME, "Cannot process the query string as the length exceeds %d bytes", MAX_QUERY_LEN);
310 return NULL;
311 }
312 memset(buf, 0, sizeof(buf));
313 memcpy(buf, query_string, query_length);
314 p = buf;
315
316 TSDebug(PLUGIN_NAME, "query_string: %s, query_length: %d", query_string, query_length);
317
318 do {
319 switch (*p) {
320 case 'A':
321 case 'C':
322 case 'E':
323 case 'K':
324 case 'P':
325 case 'S':
326 done = 1;
327 if ((p > buf) && (*(p - 1) == '&')) {
328 *(p - 1) = '\0';
329 } else {
330 (*p = '\0');
331 }
332 break;
333 default:
334 p = strchr(p, '&');
335 if (p == NULL) {
336 done = 1;
337 } else {
338 p++;
339 }
340 break;
341 }
342 } while (!done);
343
344 if (strlen(buf) > 0) {
345 p = TSstrdup(buf);
346 return p;
347 } else {
348 return NULL;
349 }
350 }
351
352 /** fixedBufferWrite safely writes no more than *dest_len bytes to *dest_end
353 * from src. If copying src_len bytes to *dest_len would overflow, it returns
354 * zero. *dest_end is advanced and *dest_len is decremented to account for the
355 * written data. No null-terminators are written automatically (though they
356 * could be copied with data).
357 */
358 static int
fixedBufferWrite(char ** dest_end,int * dest_len,const char * src,int src_len)359 fixedBufferWrite(char **dest_end, int *dest_len, const char *src, int src_len)
360 {
361 if (src_len > *dest_len) {
362 return 0;
363 }
364 memcpy(*dest_end, src, src_len);
365 *dest_end += src_len;
366 *dest_len -= src_len;
367 return 1;
368 }
369
370 static char *
urlParse(char const * const url_in,char * anchor,char * new_path_seg,int new_path_seg_len,char * signed_seg,unsigned int signed_seg_len)371 urlParse(char const *const url_in, char *anchor, char *new_path_seg, int new_path_seg_len, char *signed_seg,
372 unsigned int signed_seg_len)
373 {
374 char *segment[MAX_SEGMENTS];
375 char url[8192] = {'\0'};
376 unsigned char decoded_string[2048] = {'\0'};
377 char new_url[8192]; /* new_url is not null_terminated */
378 char *p = NULL, *sig_anchor = NULL, *saveptr = NULL;
379 int i = 0, numtoks = 0, decoded_len = 0, sig_anchor_seg = 0;
380
381 strncat(url, url_in, sizeof(url) - strlen(url) - 1);
382
383 char *new_url_end = new_url;
384 int new_url_len_left = sizeof(new_url);
385
386 char *new_path_seg_end = new_path_seg;
387 int new_path_seg_len_left = new_path_seg_len;
388
389 char *skip = strchr(url, ':');
390 if (!skip || skip[1] != '/' || skip[2] != '/') {
391 return NULL;
392 }
393 skip += 3;
394 // preserve the scheme in the new_url.
395 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, url, skip - url)) {
396 TSError("insufficient space to copy schema into new_path_seg buffer.");
397 return NULL;
398 }
399 TSDebug(PLUGIN_NAME, "%s:%d - new_url: %.*s\n", __FILE__, __LINE__, (int)(new_url_end - new_url), new_url);
400
401 // parse the url.
402 if ((p = strtok_r(skip, "/", &saveptr)) != NULL) {
403 segment[numtoks++] = p;
404 do {
405 p = strtok_r(NULL, "/", &saveptr);
406 if (p != NULL) {
407 segment[numtoks] = p;
408 if (anchor != NULL && sig_anchor_seg == 0) {
409 // look for the signed anchor string.
410 if ((sig_anchor = strcasestr(segment[numtoks], anchor)) != NULL) {
411 // null terminate this segment just before he signing anchor, this should be a ';'.
412 *(sig_anchor - 1) = '\0';
413 if ((sig_anchor = strstr(sig_anchor, "=")) != NULL) {
414 *sig_anchor = '\0';
415 sig_anchor++;
416 sig_anchor_seg = numtoks;
417 }
418 }
419 }
420 numtoks++;
421 }
422 } while (p != NULL && numtoks < MAX_SEGMENTS);
423 } else {
424 return NULL;
425 }
426 if ((numtoks >= MAX_SEGMENTS) || (numtoks < 3)) {
427 return NULL;
428 }
429
430 // create a new path string for later use when dealing with query parameters.
431 // this string will not contain the signing parameters. skips the fqdn by
432 // starting with segment 1.
433 for (i = 1; i < numtoks; i++) {
434 // if no signing anchor is found, skip the signed parameters segment.
435 if (sig_anchor == NULL && i == numtoks - 2) {
436 // the signing parameters when no signature anchor is found, should be in the
437 // last path segment so skip them.
438 continue;
439 }
440 if (!fixedBufferWrite(&new_path_seg_end, &new_path_seg_len_left, segment[i], strlen(segment[i]))) {
441 TSError("insufficient space to copy into new_path_seg buffer.");
442 return NULL;
443 }
444 if (i != numtoks - 1) {
445 if (!fixedBufferWrite(&new_path_seg_end, &new_path_seg_len_left, "/", 1)) {
446 TSError("insufficient space to copy into new_path_seg buffer.");
447 return NULL;
448 }
449 }
450 }
451 *new_path_seg_end = '\0';
452 TSDebug(PLUGIN_NAME, "new_path_seg: %s", new_path_seg);
453
454 // save the encoded signing parameter data
455 if (sig_anchor != NULL) { // a signature anchor string was found.
456 if (strlen(sig_anchor) < signed_seg_len) {
457 memcpy(signed_seg, sig_anchor, strlen(sig_anchor));
458 } else {
459 TSError("insufficient space to copy into new_path_seg buffer.");
460 }
461 } else { // no signature anchor string was found, assume it is in the last path segment.
462 if (strlen(segment[numtoks - 2]) < signed_seg_len) {
463 memcpy(signed_seg, segment[numtoks - 2], strlen(segment[numtoks - 2]));
464 } else {
465 TSError("insufficient space to copy into new_path_seg buffer.");
466 return NULL;
467 }
468 }
469 TSDebug(PLUGIN_NAME, "signed_seg: %s", signed_seg);
470
471 // no signature anchor was found so decode and save the signing parameters assumed
472 // to be in the last path segment.
473 if (sig_anchor == NULL) {
474 if (TSBase64Decode(segment[numtoks - 2], strlen(segment[numtoks - 2]), decoded_string, sizeof(decoded_string),
475 (size_t *)&decoded_len) != TS_SUCCESS) {
476 TSDebug(PLUGIN_NAME, "Unable to decode the path parameter string.");
477 }
478 } else {
479 if (TSBase64Decode(sig_anchor, strlen(sig_anchor), decoded_string, sizeof(decoded_string), (size_t *)&decoded_len) !=
480 TS_SUCCESS) {
481 TSDebug(PLUGIN_NAME, "Unable to decode the path parameter string.");
482 }
483 }
484 TSDebug(PLUGIN_NAME, "decoded_string: %s", decoded_string);
485
486 {
487 int oob = 0; /* Out Of Buffer */
488
489 for (i = 0; i < numtoks; i++) {
490 // cp the base64 decoded string.
491 if (i == sig_anchor_seg && sig_anchor != NULL) {
492 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, segment[i], strlen(segment[i]))) {
493 oob = 1;
494 break;
495 }
496 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, (char *)decoded_string, strlen((char *)decoded_string))) {
497 oob = 1;
498 break;
499 }
500 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, "/", 1)) {
501 oob = 1;
502 break;
503 }
504
505 continue;
506 } else if (i == numtoks - 2 && sig_anchor == NULL) {
507 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, (char *)decoded_string, strlen((char *)decoded_string))) {
508 oob = 1;
509 break;
510 }
511 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, "/", 1)) {
512 oob = 1;
513 break;
514 }
515 continue;
516 }
517 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, segment[i], strlen(segment[i]))) {
518 oob = 1;
519 break;
520 }
521 if (i < numtoks - 1) {
522 if (!fixedBufferWrite(&new_url_end, &new_url_len_left, "/", 1)) {
523 oob = 1;
524 break;
525 }
526 }
527 }
528 if (oob) {
529 TSError("insufficient space to copy into new_url.");
530 }
531 }
532 return TSstrndup(new_url, new_url_end - new_url);
533 }
534
535 TSRemapStatus
TSRemapDoRemap(void * ih,TSHttpTxn txnp,TSRemapRequestInfo * rri)536 TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri)
537 {
538 const struct config *cfg = (const struct config *)ih;
539
540 int url_len = 0;
541 int current_url_len = 0;
542 uint64_t expiration = 0;
543 int algorithm = -1;
544 int keyindex = -1;
545 int cmp_res;
546 int rval;
547 unsigned int i = 0;
548 int j = 0;
549 unsigned int sig_len = 0;
550 bool has_path_params = false;
551
552 /* all strings are locally allocated except url... about 25k per instance */
553 char *const current_url = TSUrlStringGet(rri->requestBufp, rri->requestUrl, ¤t_url_len);
554 char *url = current_url;
555 char path_params[8192] = {'\0'}, new_path[8192] = {'\0'};
556 char signed_part[8192] = {'\0'}; // this initializes the whole array and is needed
557 char urltokstr[8192] = {'\0'};
558 char client_ip[INET6_ADDRSTRLEN] = {'\0'}; // chose the larger ipv6 size
559 char ipstr[INET6_ADDRSTRLEN] = {'\0'}; // chose the larger ipv6 size
560 unsigned char sig[MAX_SIG_SIZE + 1];
561 char sig_string[2 * MAX_SIG_SIZE + 1];
562
563 if (current_url_len >= MAX_REQ_LEN - 1) {
564 err_log(current_url, "Request Url string too long");
565 goto deny;
566 }
567
568 if (cfg->pristine_url_flag) {
569 TSMBuffer mbuf;
570 TSMLoc ul;
571 TSReturnCode rc = TSHttpTxnPristineUrlGet(txnp, &mbuf, &ul);
572 if (rc != TS_SUCCESS) {
573 TSError("[url_sig] Failed call to TSHttpTxnPristineUrlGet()");
574 goto deny;
575 }
576 url = TSUrlStringGet(mbuf, ul, &url_len);
577 if (url_len >= MAX_REQ_LEN - 1) {
578 err_log(url, "Pristine URL string too long.");
579 goto deny;
580 }
581 } else {
582 url_len = current_url_len;
583 }
584
585 TSDebug(PLUGIN_NAME, "%s", url);
586
587 if (cfg->regex) {
588 const int offset = 0, options = 0;
589 int ovector[30];
590
591 /* Only search up to the first ? or # */
592 const char *base_url_end = url;
593 while (*base_url_end && !(*base_url_end == '?' || *base_url_end == '#')) {
594 ++base_url_end;
595 }
596 const int len = base_url_end - url;
597
598 if (pcre_exec(cfg->regex, cfg->regex_extra, url, len, offset, options, ovector, 30) >= 0) {
599 goto allow;
600 }
601 }
602
603 const char *query = strchr(url, '?');
604
605 // check for path params.
606 if (query == NULL || strstr(query, "E=") == NULL) {
607 char *const parsed = urlParse(url, cfg->sig_anchor, new_path, 8192, path_params, 8192);
608 if (parsed == NULL) {
609 err_log(url, "Unable to parse/decode new url path parameters");
610 goto deny;
611 }
612
613 has_path_params = true;
614 query = strstr(parsed, ";");
615
616 if (query == NULL) {
617 err_log(url, "Has no signing query string or signing path parameters.");
618 TSfree(parsed);
619 goto deny;
620 }
621
622 if (url != current_url) {
623 TSfree(url);
624 }
625
626 url = parsed;
627 }
628
629 /* first, parse the query string */
630 if (!has_path_params) {
631 query++; /* get rid of the ? */
632 }
633 TSDebug(PLUGIN_NAME, "Query string is:%s", query);
634
635 // Client IP - this one is optional
636 const char *cp = strstr(query, CIP_QSTRING "=");
637 const char *pp = NULL;
638 if (cp != NULL) {
639 cp += (strlen(CIP_QSTRING) + 1);
640 struct sockaddr const *ip = TSHttpTxnClientAddrGet(txnp);
641 if (ip == NULL) {
642 TSError("Can't get client ip address.");
643 goto deny;
644 } else {
645 switch (ip->sa_family) {
646 case AF_INET:
647 TSDebug(PLUGIN_NAME, "ip->sa_family: AF_INET");
648 has_path_params == false ? (pp = strstr(cp, "&")) : (pp = strstr(cp, ";"));
649 if ((pp - cp) > INET_ADDRSTRLEN - 1 || (pp - cp) < 4) {
650 err_log(url, "IP address string too long or short.");
651 goto deny;
652 }
653 strncpy(client_ip, cp, (pp - cp));
654 client_ip[pp - cp] = '\0';
655 TSDebug(PLUGIN_NAME, "CIP: -%s-", client_ip);
656 inet_ntop(AF_INET, &(((struct sockaddr_in *)ip)->sin_addr), ipstr, sizeof ipstr);
657 TSDebug(PLUGIN_NAME, "Peer address: -%s-", ipstr);
658 if (strcmp(ipstr, client_ip) != 0) {
659 err_log(url, "Client IP doesn't match signature.");
660 goto deny;
661 }
662 break;
663 case AF_INET6:
664 TSDebug(PLUGIN_NAME, "ip->sa_family: AF_INET6");
665 has_path_params == false ? (pp = strstr(cp, "&")) : (pp = strstr(cp, ";"));
666 if ((pp - cp) > INET6_ADDRSTRLEN - 1 || (pp - cp) < 4) {
667 err_log(url, "IP address string too long or short.");
668 goto deny;
669 }
670 strncpy(client_ip, cp, (pp - cp));
671 client_ip[pp - cp] = '\0';
672 TSDebug(PLUGIN_NAME, "CIP: -%s-", client_ip);
673 inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)ip)->sin6_addr), ipstr, sizeof ipstr);
674 TSDebug(PLUGIN_NAME, "Peer address: -%s-", ipstr);
675 if (strcmp(ipstr, client_ip) != 0) {
676 err_log(url, "Client IP doesn't match signature.");
677 goto deny;
678 }
679 break;
680 default:
681 TSError("%s: Unknown address family %d", PLUGIN_NAME, ip->sa_family);
682 goto deny;
683 break;
684 }
685 }
686 }
687
688 // Expiration
689 if (!cfg->ignore_expiry) {
690 cp = strstr(query, EXP_QSTRING "=");
691 if (cp != NULL) {
692 cp += strlen(EXP_QSTRING) + 1;
693 if (sscanf(cp, "%" SCNu64, &expiration) != 1 || (time_t)expiration < time(NULL)) {
694 err_log(url, "Invalid expiration, or expired");
695 goto deny;
696 }
697 TSDebug(PLUGIN_NAME, "Exp: %" PRIu64, expiration);
698 } else {
699 err_log(url, "Expiration query string not found");
700 goto deny;
701 }
702 }
703 // Algorithm
704 cp = strstr(query, ALG_QSTRING "=");
705 if (cp != NULL) {
706 cp += strlen(ALG_QSTRING) + 1;
707 algorithm = atoi(cp);
708 // The check for a valid algorithm is later.
709 TSDebug(PLUGIN_NAME, "Algorithm: %d", algorithm);
710 } else {
711 err_log(url, "Algorithm query string not found");
712 goto deny;
713 }
714 // Key index
715 cp = strstr(query, KIN_QSTRING "=");
716 if (cp != NULL) {
717 cp += strlen(KIN_QSTRING) + 1;
718 keyindex = atoi(cp);
719 if (keyindex < 0 || keyindex >= MAX_KEY_NUM || 0 == cfg->keys[keyindex][0]) {
720 err_log(url, "Invalid key index");
721 goto deny;
722 }
723 TSDebug(PLUGIN_NAME, "Key Index: %d", keyindex);
724 } else {
725 err_log(url, "KeyIndex query string not found");
726 goto deny;
727 }
728 // Parts
729 const char *parts = NULL;
730 cp = strstr(query, PAR_QSTRING "=");
731 if (cp != NULL) {
732 cp += strlen(PAR_QSTRING) + 1;
733 parts = cp; // NOTE parts is not NULL terminated it is terminated by "&" of next param
734 has_path_params == false ? (cp = strstr(parts, "&")) : (cp = strstr(parts, ";"));
735 if (cp) {
736 TSDebug(PLUGIN_NAME, "Parts: %.*s", (int)(cp - parts), parts);
737 } else {
738 TSDebug(PLUGIN_NAME, "Parts: %s", parts);
739 }
740 } else {
741 err_log(url, "PartsSigned query string not found");
742 goto deny;
743 }
744 // And finally, the sig (has to be last)
745 const char *signature = NULL;
746 cp = strstr(query, SIG_QSTRING "=");
747 if (cp != NULL) {
748 cp += strlen(SIG_QSTRING) + 1;
749 signature = cp;
750 if ((algorithm == USIG_HMAC_SHA1 && strlen(signature) < SHA1_SIG_SIZE) ||
751 (algorithm == USIG_HMAC_MD5 && strlen(signature) < MD5_SIG_SIZE)) {
752 err_log(url, "Signature query string too short (< 20)");
753 goto deny;
754 }
755 } else {
756 err_log(url, "Signature query string not found");
757 goto deny;
758 }
759
760 /* have the query string, and parameters passed initial checks */
761 TSDebug(PLUGIN_NAME, "Found all needed parameters: C=%s E=%" PRIu64 " A=%d K=%d P=%s S=%s", client_ip, expiration, algorithm,
762 keyindex, parts, signature);
763
764 /* find the string that was signed - cycle through the parts letters, adding the part of the fqdn/path if it is 1 */
765 has_path_params == false ? (cp = strchr(url, '?')) : (cp = strchr(url, ';'));
766 // Skip scheme and initial forward slashes.
767 const char *skip = strchr(url, ':');
768 if (!skip || skip[1] != '/' || skip[2] != '/') {
769 goto deny;
770 }
771 skip += 3;
772 memcpy(urltokstr, skip, cp - skip);
773 char *strtok_r_p;
774 const char *part = strtok_r(urltokstr, "/", &strtok_r_p);
775 while (part != NULL) {
776 if (parts[j] == '1') {
777 strncat(signed_part, part, sizeof(signed_part) - strlen(signed_part) - 1);
778 strncat(signed_part, "/", sizeof(signed_part) - strlen(signed_part) - 1);
779 }
780 if (parts[j + 1] == '0' ||
781 parts[j + 1] == '1') { // This remembers the last part, meaning, if there are no more valid letters in parts
782 j++; // will keep repeating the value of the last one
783 }
784 part = strtok_r(NULL, "/", &strtok_r_p);
785 }
786
787 // chop off the last /, replace with '?' or ';' as appropriate.
788 has_path_params == false ? (signed_part[strlen(signed_part) - 1] = '?') : (signed_part[strlen(signed_part) - 1] = '\0');
789 cp = strstr(query, SIG_QSTRING "=");
790 TSDebug(PLUGIN_NAME, "cp: %s, query: %s, signed_part: %s", cp, query, signed_part);
791 strncat(signed_part, query, (cp - query) + strlen(SIG_QSTRING) + 1);
792
793 TSDebug(PLUGIN_NAME, "Signed string=\"%s\"", signed_part);
794
795 /* calculate the expected the signature with the right algorithm */
796 switch (algorithm) {
797 case USIG_HMAC_SHA1:
798 HMAC(EVP_sha1(), (const unsigned char *)cfg->keys[keyindex], strlen(cfg->keys[keyindex]), (const unsigned char *)signed_part,
799 strlen(signed_part), sig, &sig_len);
800 if (sig_len != SHA1_SIG_SIZE) {
801 TSDebug(PLUGIN_NAME, "sig_len: %d", sig_len);
802 err_log(url, "Calculated sig len != SHA1_SIG_SIZE !");
803 goto deny;
804 }
805
806 break;
807 case USIG_HMAC_MD5:
808 HMAC(EVP_md5(), (const unsigned char *)cfg->keys[keyindex], strlen(cfg->keys[keyindex]), (const unsigned char *)signed_part,
809 strlen(signed_part), sig, &sig_len);
810 if (sig_len != MD5_SIG_SIZE) {
811 TSDebug(PLUGIN_NAME, "sig_len: %d", sig_len);
812 err_log(url, "Calculated sig len != MD5_SIG_SIZE !");
813 goto deny;
814 }
815 break;
816 default:
817 err_log(url, "Algorithm not supported");
818 goto deny;
819 }
820
821 for (i = 0; i < sig_len; i++) {
822 sprintf(&(sig_string[i * 2]), "%02x", sig[i]);
823 }
824
825 TSDebug(PLUGIN_NAME, "Expected signature: %s", sig_string);
826
827 /* and compare to signature that was sent */
828 cmp_res = strncmp(sig_string, signature, sig_len * 2);
829 if (cmp_res != 0) {
830 err_log(url, "Signature check failed");
831 goto deny;
832 } else {
833 TSDebug(PLUGIN_NAME, "Signature check passed");
834 goto allow;
835 }
836
837 /* ********* Deny ********* */
838 deny:
839 if (url != current_url) {
840 TSfree((void *)url);
841 }
842 TSfree((void *)current_url);
843
844 switch (cfg->err_status) {
845 case TS_HTTP_STATUS_MOVED_TEMPORARILY:
846 TSDebug(PLUGIN_NAME, "Redirecting to %s", cfg->err_url);
847 char *start, *end;
848 start = cfg->err_url;
849 end = start + strlen(cfg->err_url);
850 if (TSUrlParse(rri->requestBufp, rri->requestUrl, (const char **)&start, end) != TS_PARSE_DONE) {
851 err_log("url", "Error inn TSUrlParse!");
852 }
853 rri->redirect = 1;
854 break;
855 default:
856 TSHttpTxnErrorBodySet(txnp, TSstrdup("Authorization Denied"), sizeof("Authorization Denied") - 1, TSstrdup("text/plain"));
857 break;
858 }
859 /* Always set the return status */
860 TSHttpTxnStatusSet(txnp, cfg->err_status);
861
862 return TSREMAP_DID_REMAP;
863
864 /* ********* Allow ********* */
865 allow:
866 if (url != current_url) {
867 TSfree((void *)url);
868 }
869
870 const char *current_query = strchr(current_url, '?');
871 const char *app_qry = NULL;
872 if (current_query != NULL) {
873 current_query++;
874 app_qry = getAppQueryString(current_query, strlen(current_query));
875 }
876 TSDebug(PLUGIN_NAME, "has_path_params: %d", has_path_params);
877 if (has_path_params) {
878 if (*new_path) {
879 TSUrlPathSet(rri->requestBufp, rri->requestUrl, new_path, strlen(new_path));
880 }
881 TSUrlHttpParamsSet(rri->requestBufp, rri->requestUrl, NULL, 0);
882 }
883
884 TSfree((void *)current_url);
885
886 /* drop the query string so we can cache-hit */
887 if (app_qry != NULL) {
888 rval = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, app_qry, strlen(app_qry));
889 TSfree((void *)app_qry);
890 } else {
891 rval = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, NULL, 0);
892 }
893 if (rval != TS_SUCCESS) {
894 TSError("[url_sig] Error setting the query string: %d", rval);
895 }
896
897 return TSREMAP_NO_REMAP;
898 }
899