Declare hextab as an array
[freeradius.git] / src / main / radattr.c
1 /*
2  * radattr.c    RADIUS Attribute debugging tool.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2010  Alan DeKok <aland@freeradius.org>
21  */
22
23 RCSID("$Id$")
24
25 #include <freeradius-devel/libradius.h>
26 #include <freeradius-devel/parser.h>
27 #include <freeradius-devel/conf.h>
28 #include <freeradius-devel/radpaths.h>
29
30 #include <ctype.h>
31
32 #ifdef HAVE_GETOPT_H
33 #       include <getopt.h>
34 #endif
35
36 #include <assert.h>
37
38 typedef struct REQUEST REQUEST;
39
40 #include <freeradius-devel/log.h>
41 log_debug_t debug_flag = 0;
42
43 /**********************************************************************
44  *      Hacks for xlat
45  */
46 typedef size_t (*RADIUS_ESCAPE_STRING)(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
47 typedef ssize_t (*RAD_XLAT_FUNC)(void *instance, REQUEST *, char const *, char *, size_t);
48 int            xlat_register(char const *module, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING escape,
49                              void *instance);
50 #include <sys/wait.h>
51 pid_t rad_fork(void);
52 pid_t rad_waitpid(pid_t pid, int *status);
53
54 pid_t rad_fork(void)
55 {
56         return fork();
57 }
58
59 pid_t rad_waitpid(pid_t pid, int *status)
60 {
61         return waitpid(pid, status, 0);
62 }
63
64 static ssize_t xlat_test(UNUSED void *instance, UNUSED REQUEST *request,
65                          UNUSED char const *fmt, UNUSED char *out, UNUSED size_t outlen)
66 {
67         return 0;
68 }
69
70 /*
71  *      End of hacks for xlat
72  *
73  **********************************************************************/
74
75 static int encode_tlv(char *buffer, uint8_t *output, size_t outlen);
76
77 static char const hextab[] = "0123456789abcdef";
78
79 static int encode_data_string(char *buffer,
80                               uint8_t *output, size_t outlen)
81 {
82         int length = 0;
83         char *p;
84
85         p = buffer + 1;
86
87         while (*p && (outlen > 0)) {
88                 if (*p == '"') {
89                         return length;
90                 }
91
92                 if (*p != '\\') {
93                         *(output++) = *(p++);
94                         outlen--;
95                         length++;
96                         continue;
97                 }
98
99                 switch (p[1]) {
100                 default:
101                         *(output++) = p[1];
102                         break;
103
104                 case 'n':
105                         *(output++) = '\n';
106                         break;
107
108                 case 'r':
109                         *(output++) = '\r';
110                         break;
111
112                 case 't':
113                         *(output++) = '\t';
114                         break;
115                 }
116
117                 outlen--;
118                 length++;
119         }
120
121         fprintf(stderr, "String is not terminated\n");
122         return 0;
123 }
124
125 static int encode_data_tlv(char *buffer, char **endptr,
126                            uint8_t *output, size_t outlen)
127 {
128         int depth = 0;
129         int length;
130         char *p;
131
132         for (p = buffer; *p != '\0'; p++) {
133                 if (*p == '{') depth++;
134                 if (*p == '}') {
135                         depth--;
136                         if (depth == 0) break;
137                 }
138         }
139
140         if (*p != '}') {
141                 fprintf(stderr, "No trailing '}' in string starting "
142                         "with \"%s\"\n",
143                         buffer);
144                 return 0;
145         }
146
147         *endptr = p + 1;
148         *p = '\0';
149
150         p = buffer + 1;
151         while (isspace((int) *p)) p++;
152
153         length = encode_tlv(p, output, outlen);
154         if (length == 0) return 0;
155
156         return length;
157 }
158
159 static int encode_hex(char *p, uint8_t *output, size_t outlen)
160 {
161         int length = 0;
162         while (*p) {
163                 char *c1, *c2;
164
165                 while (isspace((int) *p)) p++;
166
167                 if (!*p) break;
168
169                 if(!(c1 = memchr(hextab, tolower((int) p[0]), 16)) ||
170                    !(c2 = memchr(hextab, tolower((int)  p[1]), 16))) {
171                         fprintf(stderr, "Invalid data starting at "
172                                 "\"%s\"\n", p);
173                         return 0;
174                 }
175
176                 *output = ((c1 - hextab) << 4) + (c2 - hextab);
177                 output++;
178                 length++;
179                 p += 2;
180
181                 outlen--;
182                 if (outlen == 0) {
183                         fprintf(stderr, "Too much data\n");
184                         return 0;
185                 }
186         }
187
188         return length;
189 }
190
191
192 static int encode_data(char *p, uint8_t *output, size_t outlen)
193 {
194         int length;
195
196         if (!isspace((int) *p)) {
197                 fprintf(stderr, "Invalid character following attribute "
198                         "definition\n");
199                 return 0;
200         }
201
202         while (isspace((int) *p)) p++;
203
204         if (*p == '{') {
205                 int sublen;
206                 char *q;
207
208                 length = 0;
209
210                 do {
211                         while (isspace((int) *p)) p++;
212                         if (!*p) {
213                                 if (length == 0) {
214                                         fprintf(stderr, "No data\n");
215                                         return 0;
216                                 }
217
218                                 break;
219                         }
220
221                         sublen = encode_data_tlv(p, &q, output, outlen);
222                         if (sublen == 0) return 0;
223
224                         length += sublen;
225                         output += sublen;
226                         outlen -= sublen;
227                         p = q;
228                 } while (*q);
229
230                 return length;
231         }
232
233         if (*p == '"') {
234                 length = encode_data_string(p, output, outlen);
235                 return length;
236         }
237
238         length = encode_hex(p, output, outlen);
239
240         if (length == 0) {
241                 fprintf(stderr, "Empty string\n");
242                 return 0;
243         }
244
245         return length;
246 }
247
248 static int decode_attr(char *buffer, char **endptr)
249 {
250         long attr;
251
252         attr = strtol(buffer, endptr, 10);
253         if (*endptr == buffer) {
254                 fprintf(stderr, "No valid number found in string "
255                         "starting with \"%s\"\n", buffer);
256                 return 0;
257         }
258
259         if (!**endptr) {
260                 fprintf(stderr, "Nothing follows attribute number\n");
261                 return 0;
262         }
263
264         if ((attr <= 0) || (attr > 256)) {
265                 fprintf(stderr, "Attribute number is out of valid "
266                         "range\n");
267                 return 0;
268         }
269
270         return (int) attr;
271 }
272
273 static int decode_vendor(char *buffer, char **endptr)
274 {
275         long vendor;
276
277         if (*buffer != '.') {
278                 fprintf(stderr, "Invalid separator before vendor id\n");
279                 return 0;
280         }
281
282         vendor = strtol(buffer + 1, endptr, 10);
283         if (*endptr == (buffer + 1)) {
284                 fprintf(stderr, "No valid vendor number found\n");
285                 return 0;
286         }
287
288         if (!**endptr) {
289                 fprintf(stderr, "Nothing follows vendor number\n");
290                 return 0;
291         }
292
293         if ((vendor <= 0) || (vendor > (1 << 24))) {
294                 fprintf(stderr, "Vendor number is out of valid range\n");
295                 return 0;
296         }
297
298         if (**endptr != '.') {
299                 fprintf(stderr, "Invalid data following vendor number\n");
300                 return 0;
301         }
302         (*endptr)++;
303
304         return (int) vendor;
305 }
306
307 static int encode_tlv(char *buffer, uint8_t *output, size_t outlen)
308 {
309         int attr;
310         int length;
311         char *p;
312
313         attr = decode_attr(buffer, &p);
314         if (attr == 0) return 0;
315
316         output[0] = attr;
317         output[1] = 2;
318
319         if (*p == '.') {
320                 p++;
321                 length = encode_tlv(p, output + 2, outlen - 2);
322
323         } else {
324                 length = encode_data(p, output + 2, outlen - 2);
325         }
326
327         if (length == 0) return 0;
328         if (length > (255 - 2)) {
329                 fprintf(stderr, "TLV data is too long\n");
330                 return 0;
331         }
332
333         output[1] += length;
334
335         return length + 2;
336 }
337
338 static int encode_vsa(char *buffer, uint8_t *output, size_t outlen)
339 {
340         int vendor;
341         int length;
342         char *p;
343
344         vendor = decode_vendor(buffer, &p);
345         if (vendor == 0) return 0;
346
347         output[0] = 0;
348         output[1] = (vendor >> 16) & 0xff;
349         output[2] = (vendor >> 8) & 0xff;
350         output[3] = vendor & 0xff;
351
352         length = encode_tlv(p, output + 4, outlen - 4);
353         if (length == 0) return 0;
354         if (length > (255 - 6)) {
355                 fprintf(stderr, "VSA data is too long\n");
356                 return 0;
357         }
358
359
360         return length + 4;
361 }
362
363 static int encode_evs(char *buffer, uint8_t *output, size_t outlen)
364 {
365         int vendor;
366         int attr;
367         int length;
368         char *p;
369
370         vendor = decode_vendor(buffer, &p);
371         if (vendor == 0) return 0;
372
373         attr = decode_attr(p, &p);
374         if (attr == 0) return 0;
375
376         output[0] = 0;
377         output[1] = (vendor >> 16) & 0xff;
378         output[2] = (vendor >> 8) & 0xff;
379         output[3] = vendor & 0xff;
380         output[4] = attr;
381
382         length = encode_data(p, output + 5, outlen - 5);
383         if (length == 0) return 0;
384
385         return length + 5;
386 }
387
388 static int encode_extended(char *buffer,
389                            uint8_t *output, size_t outlen)
390 {
391         int attr;
392         int length;
393         char *p;
394
395         attr = decode_attr(buffer, &p);
396         if (attr == 0) return 0;
397
398         output[0] = attr;
399
400         if (attr == 26) {
401                 length = encode_evs(p, output + 1, outlen - 1);
402         } else {
403                 length = encode_data(p, output + 1, outlen - 1);
404         }
405         if (length == 0) return 0;
406         if (length > (255 - 3)) {
407                 fprintf(stderr, "Extended Attr data is too long\n");
408                 return 0;
409         }
410
411         return length + 1;
412 }
413
414 static int encode_long_extended(char *buffer,
415                                  uint8_t *output, size_t outlen)
416 {
417         int attr;
418         int length, total;
419         char *p;
420
421         attr = decode_attr(buffer, &p);
422         if (attr == 0) return 0;
423
424         /* output[0] is the extended attribute */
425         output[1] = 4;
426         output[2] = attr;
427         output[3] = 0;
428
429         if (attr == 26) {
430                 length = encode_evs(p, output + 4, outlen - 4);
431                 if (length == 0) return 0;
432
433                 output[1] += 5;
434                 length -= 5;
435         } else {
436                 length = encode_data(p, output + 4, outlen - 4);
437         }
438         if (length == 0) return 0;
439
440         total = 0;
441         while (1) {
442                 int sublen = 255 - output[1];
443
444                 if (length <= sublen) {
445                         output[1] += length;
446                         total += output[1];
447                         break;
448                 }
449
450                 length -= sublen;
451
452                 memmove(output + 255 + 4, output + 255, length);
453                 memcpy(output + 255, output, 4);
454
455                 output[1] = 255;
456                 output[3] |= 0x80;
457
458                 output += 255;
459                 output[1] = 4;
460                 total += 255;
461         }
462
463         return total;
464 }
465
466 static int encode_rfc(char *buffer, uint8_t *output, size_t outlen)
467 {
468         int attr;
469         int length, sublen;
470         char *p;
471
472         attr = decode_attr(buffer, &p);
473         if (attr == 0) return 0;
474
475         length = 2;
476         output[0] = attr;
477         output[1] = 2;
478
479         if (attr == 26) {
480                 sublen = encode_vsa(p, output + 2, outlen - 2);
481
482         } else if ((attr < 241) || (attr > 246)) {
483                 sublen = encode_data(p, output + 2, outlen - 2);
484
485         } else {
486                 if (*p != '.') {
487                         fprintf(stderr, "Invalid data following "
488                                 "attribute number\n");
489                         return 0;
490                 }
491
492                 if (attr < 245) {
493                         sublen = encode_extended(p + 1,
494                                                  output + 2, outlen - 2);
495                 } else {
496
497                         /*
498                          *      Not like the others!
499                          */
500                         return encode_long_extended(p + 1, output, outlen);
501                 }
502         }
503         if (sublen == 0) return 0;
504         if (sublen > (255 -2)) {
505                 fprintf(stderr, "RFC Data is too long\n");
506                 return 0;
507         }
508
509         output[1] += sublen;
510         return length + sublen;
511 }
512
513 static void parse_condition(char const *input, char *output, size_t outlen)
514 {
515         ssize_t slen;
516         char const *error = NULL;
517         fr_cond_t *cond;
518
519         slen = fr_condition_tokenize(NULL, NULL, input, &cond, &error, FR_COND_ONE_PASS);
520         if (slen <= 0) {
521                 snprintf(output, outlen, "ERROR offset %d %s", (int) -slen, error);
522                 return;
523         }
524
525         input += slen;
526         if (*input != '\0') {
527                 talloc_free(cond);
528                 snprintf(output, outlen, "ERROR offset %d 'Too much text'", (int) slen);
529                 return;
530         }
531
532         fr_cond_sprint(output, outlen, cond);
533
534         talloc_free(cond);
535 }
536
537 static void parse_xlat(char const *input, char *output, size_t outlen)
538 {
539         ssize_t slen;
540         char const *error = NULL;
541         char *fmt = talloc_typed_strdup(NULL, input);
542         xlat_exp_t *head;
543
544         slen = xlat_tokenize(fmt, fmt, &head, &error);
545         if (slen <= 0) {
546                 snprintf(output, outlen, "ERROR offset %d '%s'", (int) -slen, error);
547                 return;
548         }
549
550         if (input[slen] != '\0') {
551                 snprintf(output, outlen, "ERROR offset %d 'Too much text'", (int) slen);
552                 return;
553         }
554
555         xlat_sprint(output, outlen, head);
556         talloc_free(fmt);
557 }
558
559 static void process_file(const char *root_dir, char const *filename)
560 {
561         int lineno;
562         size_t i, outlen;
563         ssize_t len, data_len;
564         FILE *fp;
565         char input[8192], buffer[8192];
566         char output[8192];
567         char directory[8192];
568         uint8_t *attr, data[2048];
569
570         if (strcmp(filename, "-") == 0) {
571                 fp = stdin;
572                 directory[0] = '\0';
573
574         } else {
575                 if (root_dir && *root_dir) {
576                         snprintf(directory, sizeof(directory), "%s/%s", root_dir, filename);
577                 } else {
578                         strlcpy(directory, filename, sizeof(directory));
579                 }
580
581                 fp = fopen(directory, "r");
582                 if (!fp) {
583                         fprintf(stderr, "Error opening %s: %s\n",
584                                 directory, fr_syserror(errno));
585                         exit(1);
586                 }
587
588                 filename = directory;
589         }
590
591         lineno = 0;
592         *output = '\0';
593         data_len = 0;
594
595         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
596                 char *p = strchr(buffer, '\n');
597                 VALUE_PAIR *vp, *head = NULL;
598                 VALUE_PAIR **tail = &head;
599
600                 lineno++;
601
602                 if (!p) {
603                         if (!feof(fp)) {
604                                 fprintf(stderr, "Line %d too long in %s\n",
605                                         lineno, directory);
606                                 exit(1);
607                         }
608                 } else {
609                         *p = '\0';
610                 }
611
612                 /*
613                  *      Comments, with hacks for User-Name[#]
614                  */
615                 p = strchr(buffer, '#');
616                 if (p && ((p == buffer) ||
617                           ((p > buffer) && (p[-1] != '[')))) *p = '\0';
618
619                 p = buffer;
620                 while (isspace((int) *p)) p++;
621                 if (!*p) continue;
622
623                 DEBUG2("%s[%d]: %s\n", filename, lineno, buffer);
624
625                 strlcpy(input, p, sizeof(input));
626
627                 if (strncmp(p, "raw ", 4) == 0) {
628                         outlen = encode_rfc(p + 4, data, sizeof(data));
629                         if (outlen == 0) {
630                                 fprintf(stderr, "Parse error in line %d of %s\n",
631                                         lineno, directory);
632                                 exit(1);
633                         }
634
635                 print_hex:
636                         if (outlen == 0) {
637                                 output[0] = 0;
638                                 continue;
639                         }
640
641                         if (outlen > sizeof(data)) outlen = sizeof(data);
642
643                         if (outlen >= (sizeof(output) / 2)) {
644                                 outlen = (sizeof(output) / 2) - 1;
645                         }
646
647                         data_len = outlen;
648                         for (i = 0; i < outlen; i++) {
649                                 if (sizeof(output) < (3*i)) break;
650
651                                 snprintf(output + 3*i, sizeof(output) - (3*i) - 1,
652                                          "%02x ", data[i]);
653                         }
654                         outlen = strlen(output);
655                         output[outlen - 1] = '\0';
656                         continue;
657                 }
658
659                 if (strncmp(p, "data ", 5) == 0) {
660                         if (strcmp(p + 5, output) != 0) {
661                                 fprintf(stderr, "Mismatch in line %d of %s, got: %s expected: %s\n",
662                                         lineno, directory, output, p + 5);
663                                 exit(1);
664                         }
665                         continue;
666                 }
667
668                 if (strncmp(p, "encode ", 7) == 0) {
669                         if (strcmp(p + 7, "-") == 0) {
670                                 p = output;
671                         } else {
672                                 p += 7;
673                         }
674
675                         if (userparse(NULL, p, &head) != T_EOL) {
676                                 strlcpy(output, fr_strerror(), sizeof(output));
677                                 continue;
678                         }
679
680                         attr = data;
681                         vp = head;
682                         len = 0;
683                         while (vp) {
684                                 len = rad_vp2attr(NULL, NULL, NULL, (VALUE_PAIR const **)(void **)&vp,
685                                                   attr, sizeof(data) - (attr - data));
686                                 if (len < 0) {
687                                         fprintf(stderr, "Failed encoding %s: %s\n",
688                                                 vp->da->name, fr_strerror());
689                                         exit(1);
690                                 }
691
692                                 attr += len;
693                                 if (len == 0) break;
694                         }
695
696                         pairfree(&head);
697                         outlen = len;
698                         goto print_hex;
699                 }
700
701                 if (strncmp(p, "decode ", 7) == 0) {
702                         ssize_t my_len;
703
704                         if (strcmp(p + 7, "-") == 0) {
705                                 attr = data;
706                                 len = data_len;
707                         } else {
708                                 attr = data;
709                                 len = encode_hex(p + 7, data, sizeof(data));
710                                 if (len == 0) {
711                                         fprintf(stderr, "Failed decoding hex string at line %d of %s\n", lineno, directory);
712                                         exit(1);
713                                 }
714                         }
715
716                         my_len = 0;
717                         while (len > 0) {
718                                 vp = NULL;
719                                 my_len = rad_attr2vp(NULL, NULL, NULL, NULL, attr, len, &vp);
720                                 if (my_len < 0) {
721                                         pairfree(&head);
722                                         break;
723                                 }
724
725                                 if (my_len > len) {
726                                         fprintf(stderr, "Internal sanity check failed at %d\n", __LINE__);
727                                         exit(1);
728                                 }
729
730                                 *tail = vp;
731                                 while (vp) {
732                                         tail = &(vp->next);
733                                         vp = vp->next;
734                                 }
735
736                                 attr += my_len;
737                                 len -= my_len;
738                         }
739
740                         /*
741                          *      Output may be an error, and we ignore
742                          *      it if so.
743                          */
744                         if (head) {
745                                 vp_cursor_t cursor;
746                                 p = output;
747                                 for (vp = fr_cursor_init(&cursor, &head);
748                                      vp;
749                                      vp = fr_cursor_next(&cursor)) {
750                                         vp_prints(p, sizeof(output) - (p - output), vp);
751                                         p += strlen(p);
752
753                                         if (vp->next) {strcpy(p, ", ");
754                                                 p += 2;
755                                         }
756                                 }
757
758                                 pairfree(&head);
759                         } else if (my_len < 0) {
760                                 strlcpy(output, fr_strerror(), sizeof(output));
761
762                         } else { /* zero-length attribute */
763                                 *output = '\0';
764                         }
765                         continue;
766                 }
767
768                 if (strncmp(p, "attribute ", 10) == 0) {
769                         p += 10;
770
771                         if (userparse(NULL, p, &head) != T_EOL) {
772                                 strlcpy(output, fr_strerror(), sizeof(output));
773                                 continue;
774                         }
775
776                         vp_prints(output, sizeof(output), head);
777                         continue;
778                 }
779
780                 if (strncmp(p, "$INCLUDE ", 9) == 0) {
781                         char *q;
782
783                         p += 9;
784                         while (isspace((int) *p)) p++;
785
786                         q = strrchr(directory, '/');
787                         if (q) {
788                                 *q = '\0';
789                                 process_file(directory, p);
790                                 *q = '/';
791                         } else {
792                                 process_file(NULL, p);
793                         }
794                         continue;
795                 }
796
797                 if (strncmp(p, "condition ", 10) == 0) {
798                         p += 10;
799                         parse_condition(p, output, sizeof(output));
800                         continue;
801                 }
802
803                 if (strncmp(p, "xlat ", 5) == 0) {
804                         p += 5;
805                         parse_xlat(p, output, sizeof(output));
806                         continue;
807                 }
808
809                 fprintf(stderr, "Unknown input at line %d of %s\n",
810                         lineno, directory);
811                 exit(1);
812         }
813
814         if (fp != stdin) fclose(fp);
815 }
816
817 int main(int argc, char *argv[])
818 {
819         int c;
820         bool report = false;
821         char const *radius_dir = RADDBDIR;
822         char const *dict_dir = DICTDIR;
823
824 #ifndef NDEBUG
825         if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
826                 fr_perror("radattr");
827                 exit(EXIT_FAILURE);
828         }
829 #endif
830
831         while ((c = getopt(argc, argv, "d:D:xM")) != EOF) switch(c) {
832                 case 'd':
833                         radius_dir = optarg;
834                         break;
835                 case 'D':
836                         dict_dir = optarg;
837                         break;
838                 case 'x':
839                         fr_debug_flag++;
840                         debug_flag = fr_debug_flag;
841                         break;
842                 case 'M':
843                         report = true;
844                         break;
845                 default:
846                         fprintf(stderr, "usage: radattr [OPTS] filename\n");
847                         exit(1);
848         }
849         argc -= (optind - 1);
850         argv += (optind - 1);
851
852         /*
853          *      Mismatch between the binary and the libraries it depends on
854          */
855         if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
856                 fr_perror("radattr");
857                 return 1;
858         }
859
860         if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
861                 fr_perror("radattr");
862                 return 1;
863         }
864
865         if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
866                 fr_perror("radattr");
867                 return 1;
868         }
869
870         if (xlat_register("test", xlat_test, NULL, NULL) < 0) {
871                 fprintf(stderr, "Failed registering xlat");
872                 return 1;
873         }
874
875         if (argc < 2) {
876                 process_file(NULL, "-");
877
878         } else {
879                 process_file(NULL, argv[1]);
880         }
881
882         if (report) {
883                 dict_free();
884                 fr_log_talloc_report(NULL);
885         }
886
887         return 0;
888 }