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, &current_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