1 /*
2 * $Id: conf.c 191 2007-03-30 23:26:38Z boote $
3 */
4 /************************************************************************
5 * *
6 * Copyright (C) 2003 *
7 * Internet2 *
8 * All Rights Reserved *
9 * *
10 ************************************************************************/
11 /*
12 * File: conf.c
13 *
14 * Author: Jeff W. Boote
15 * Internet2
16 *
17 * Date: Tue Sep 9 16:13:25 MDT 2003
18 *
19 * Description:
20 */
21 #include <I2util/utilP.h>
22
23 #include <stdlib.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <errno.h>
29
30 /*
31 * Function: I2GetConfLine
32 *
33 * Description:
34 * Read a single line from a file fp. remove leading whitespace,
35 * skip blank lines and comment lines. Put the result in the
36 * char buffer pointed at by lbuf, growing it as necessary.
37 *
38 * In Args:
39 *
40 * Out Args:
41 *
42 * Scope:
43 * Returns:
44 * Side Effect:
45 */
46 int
I2GetConfLine(I2ErrHandle eh,FILE * fp,int rc,char ** lbuf,size_t * lbuf_max)47 I2GetConfLine(
48 I2ErrHandle eh,
49 FILE *fp,
50 int rc,
51 char **lbuf,
52 size_t *lbuf_max
53 )
54 {
55 int c;
56 char *line = *lbuf;
57 size_t i=0;
58
59 while((c = fgetc(fp)) != EOF){
60
61 /*
62 * If c is a newline - increment the row-counter.
63 * If lbuf already has content - break out, otherwise
64 * this was a leading blank line, continue until there
65 * is content.
66 */
67 if(c == '\n'){
68 rc++;
69 if(i) break;
70 continue;
71 }
72
73 /*
74 * swallow comment lines
75 */
76 if(!i && c == '#'){
77 while((c = fgetc(fp)) != EOF){
78 if(c == '\n'){
79 rc++;
80 break;
81 }
82 }
83 continue;
84 }
85
86 /*
87 * swallow leading whitespace
88 */
89 if(!i && isspace((int)c)){
90 continue;
91 }
92
93 /*
94 * Check for line continuation.
95 */
96 if(c == '\\'){
97 if(fgetc(fp) == '\n'){
98 rc++;
99 continue;
100 }
101 I2ErrLogP(eh,EINVAL,"Invalid use of \'\\\'");
102 return -rc;
103 }
104
105 /*
106 * make sure lbuf is large enough for this content
107 */
108 if(i+2 > *lbuf_max){
109 while(i+2 > *lbuf_max){
110 *lbuf_max += I2LINEBUFINC;
111 }
112 *lbuf = realloc(line,sizeof(char) * *lbuf_max);
113 if(!*lbuf){
114 if(line){
115 free(line);
116 }
117 I2ErrLog(eh,"realloc(%u): %M",*lbuf_max);
118 return -rc;
119 }
120 line = *lbuf;
121 }
122
123 /*
124 * copy char
125 */
126 line[i++] = c;
127 }
128
129 if(!i){
130 return 0;
131 }
132
133 line[i] = '\0';
134
135 if(c == EOF){
136 rc++;
137 }
138
139 return rc;
140 }
141
142 /*
143 * Function: I2ReadConfVar
144 *
145 * Description:
146 * Read the next non-comment line from the config file. The file
147 * should be in the format of:
148 * key [value] [#blah comment]
149 *
150 * key and value are delineated by whitespace. All leading and
151 * trailing whitespace is ignored. A trailing comment is legal and
152 * all charactors between a # and the trailing \n are ignored.
153 *
154 * In Args:
155 *
156 * Out Args:
157 *
158 * Scope:
159 * Returns:
160 * Side Effect:
161 */
162 int
I2ReadConfVar(FILE * fp,int rc,char * key,char * val,size_t max,char ** lbuf,size_t * lbuf_max)163 I2ReadConfVar(
164 FILE *fp,
165 int rc,
166 char *key,
167 char *val,
168 size_t max,
169 char **lbuf,
170 size_t *lbuf_max
171 )
172 {
173 char *line;
174
175 if((rc = I2GetConfLine(NULL,fp,rc,lbuf,lbuf_max)) > 0){
176
177 /*
178 * Pull off key.
179 */
180 if(!(line = strtok(*lbuf,I2WSPACESET))){
181 rc = -rc;
182 goto DONE;
183 }
184
185 /*
186 * Ensure key is not too long.
187 */
188 if(strlen(line)+1 > max){
189 rc = -rc;
190 goto DONE;
191 }
192 strcpy(key,line);
193
194 if((line = strtok(NULL,I2WSPACESET))){
195 /*
196 * If there is no "value" for this key, then
197 * a comment is valid.
198 */
199 if(*line == '#'){
200 val[0] = '\0';
201 goto DONE;
202 }
203
204 /*
205 * Ensure value is not too long.
206 */
207 if(strlen(line)+1 > max){
208 rc = -rc;
209 goto DONE;
210 }
211 strcpy(val,line);
212 }
213 else{
214 val[0] = '\0';
215 }
216
217 /*
218 * Ensure there is no trailing data
219 */
220 if((line = strtok(NULL,I2WSPACESET))){
221 /*
222 * Comments are the only valid token.
223 */
224 if(*line != '#'){
225 rc = -rc;
226 }
227 }
228 }
229
230 DONE:
231 return rc;
232 }
233
234 /*
235 * This is very similar to I2GetConfLine but does not allow line
236 * continuation - and copies comment/blank lines to tofp.
237 */
238 static int
parsefileline(I2ErrHandle eh,FILE * fp,int rc,char ** lbuf,size_t * lbuf_max,FILE * tofp)239 parsefileline(
240 I2ErrHandle eh,
241 FILE *fp,
242 int rc,
243 char **lbuf,
244 size_t *lbuf_max,
245 FILE *tofp
246 )
247 {
248 int c;
249 size_t i;
250 char *line = *lbuf;
251 size_t nc=0; /* number of "significant" chars in the line */
252 size_t ns=0; /* number of leading spaces */
253
254 while((c = fgetc(fp)) != EOF){
255
256 /*
257 * If c is a newline - increment the row-counter.
258 * If lbuf already has content - break out, otherwise
259 * this was a blank line, continue until there
260 * is content.
261 */
262 if(c == '\n'){
263 rc++;
264 if(nc) break;
265 /* don't worry about leading spaces for blank lines */
266 if(tofp) fprintf(tofp,"\n");
267 ns=0;
268 continue;
269 }
270
271 /*
272 * swallow comment lines
273 */
274 if(!nc && c == '#'){
275 if(tofp){
276 /* preserve leading spaces */
277 for(i=0;i<ns;i++) fprintf(tofp," ");
278 fprintf(tofp,"#");
279 }
280 while((c = fgetc(fp)) != EOF){
281 if(tofp)
282 fprintf(tofp,"%c",c);
283 if(c == '\n'){
284 rc++;
285 break;
286 }
287 }
288 continue;
289 }
290
291 /*
292 * swallow leading whitespace
293 * (These will be preserved for comment lines - and removed
294 * for all other lines.)
295 */
296 if(!nc && isspace((int)c)){
297 ns++;
298 continue;
299 }
300
301 /*
302 * Leading spaces are only allowed in comment lines
303 */
304 if(ns){
305 I2ErrLog(eh,"Leading spaces not allowed: line %d",rc);
306 return -rc;
307 }
308
309 /*
310 * make sure lbuf is large enough for this content
311 */
312 if(nc+2 > *lbuf_max){
313 while(nc+2 > *lbuf_max){
314 *lbuf_max += I2LINEBUFINC;
315 }
316 *lbuf = realloc(line,sizeof(char) * *lbuf_max);
317 if(!*lbuf){
318 if(line){
319 free(line);
320 }
321 I2ErrLog(eh,"realloc(%u): %M",*lbuf_max);
322 return -rc;
323 }
324 line = *lbuf;
325 }
326
327 /*
328 * copy char
329 */
330 line[nc++] = c;
331 }
332
333 if(!nc){
334 return 0;
335 }
336
337 line[nc] = '\0';
338
339 if(c == EOF){
340 rc++;
341 }
342
343 return rc;
344 }
345
346 /*
347 * Function: I2ParseKeyFile
348 *
349 * Description:
350 * Read a single line from a file fp. remove leading whitespace,
351 * skip blank lines and comment lines. Put the result in the
352 * char buffer pointed at by lbuf, growing it as necessary.
353 *
354 * Read a single identity/key from the keyfile. If tofp is set,
355 * then copy all "unmatched" lines from fp to tofp while parsing
356 * the file. If id_query is set, only return the entry that
357 * matches (if any does) skipping all others - and copying them
358 * to tofp if needed. A quick way to simply copy all remaining
359 * records to the tofp is to specify an id_query to a 0 length
360 * string (i.e. id_query[0] == '\0').
361 *
362 * In Args:
363 *
364 * Out Args:
365 *
366 * Scope:
367 * Returns:
368 * Side Effect:
369 */
370 int
I2ParseKeyFile(I2ErrHandle eh,FILE * fp,int rc,char ** lbuf,size_t * lbuf_max,FILE * tofp,const char * id_query,char * id_ret,uint8_t * key_ret)371 I2ParseKeyFile(
372 I2ErrHandle eh,
373 FILE *fp,
374 int rc,
375 char **lbuf,
376 size_t *lbuf_max,
377 FILE *tofp,
378 const char *id_query,
379 char *id_ret, /* [I2MAXIDENTITYLEN+1] or null */
380 uint8_t *key_ret /* [I2KEYLEN] or null */
381 )
382 {
383 char *line;
384 int i;
385 char rbuf[I2MAXIDENTITYLEN+1]; /* add one extra byte */
386 char *keystart;
387 uint8_t kbuf[I2KEYLEN];
388
389 /*
390 * If there is no keyfile, parsing is very, very fast.
391 */
392 if(!fp){
393 return 0;
394 }
395
396 /*
397 * Fetch each non-blank, non-comment line from the keyfile.
398 * completely validate each line and then determine at the
399 * end of the loop if the caller is interested in this line or not.
400 * (This strict interpretation of the syntax of the keyfile should
401 * help find errors as quickly as possible instead of letting them
402 * hide until they actually bite someone.)
403 */
404 while((rc = parsefileline(eh,fp,rc,lbuf,lbuf_max,tofp)) > 0){
405
406 line = *lbuf;
407
408 i=0;
409 /*
410 * Can potentially copy I2MAXIDENTITYLEN+1 bytes: rbuf is
411 * sized to handle this and the next 'if' is setup to
412 * detect this error condition.
413 */
414 while(i <= I2MAXIDENTITYLEN){
415 if(isspace((int)*line) || (*line == '\0')){
416 break;
417 }
418 rbuf[i++] = *line++;
419 }
420
421 if( i > I2MAXIDENTITYLEN){
422 I2ErrLogP(eh,EINVAL,"Invalid identity name (too long)");
423 return -rc;
424 }
425 rbuf[i] = '\0';
426
427 /*
428 * Get the hexkey
429 */
430 while(isspace((int)*line)){
431 line++;
432 }
433
434 keystart = line;
435 i=0;
436 while(*line != '\0'){
437 if(isspace((int)*line)){
438 break;
439 }
440 i++;
441 line++;
442 }
443
444 /*
445 * If i is not equal to the hex-encoded length of a key...
446 */
447 if(i != (I2KEYLEN*2)){
448 I2ErrLogP(eh,EINVAL,"Invalid key length");
449 return -rc;
450 }
451
452 /*
453 * Make sure the only thing trailing the key is a comment
454 * or whitespace.
455 */
456 while(*line != '\0'){
457 if(*line == '#'){
458 break;
459 }
460 if(!isspace((int)*line)){
461 I2ErrLogP(eh,EINVAL,"Invalid chars after key");
462 return -rc;
463 }
464 line++;
465 }
466
467 if(!I2HexDecode(keystart,kbuf,I2KEYLEN)){
468 I2ErrLogP(eh,EINVAL,"Invalid key: not hex?");
469 return -rc;
470 }
471
472 /*
473 * If a specific "identity" is being searched for: skip/copy
474 * lines that don't match and continue parsing the file.
475 */
476 if(id_query && strncmp(rbuf,id_query,I2MAXIDENTITYLEN)){
477 /*
478 * Write line to tofp, then 'continue'
479 */
480 if(tofp) fprintf(tofp,"%s\n",*lbuf);
481 continue;
482 }
483
484 /*
485 * caller is interested in this record - return the values.
486 */
487 if(id_ret){
488 strncpy(id_ret,rbuf,sizeof(rbuf));
489 }
490
491 if(key_ret){
492 memcpy(key_ret,kbuf,I2KEYLEN);
493 }
494
495 break;
496 }
497
498 return rc;
499 }
500
501 /*
502 * Function: I2WriteKeyLine
503 *
504 * Description:
505 *
506 * In Args:
507 *
508 * Out Args:
509 *
510 * Scope:
511 * Returns:
512 * Side Effect:
513 */
514 int
I2WriteKeyLine(I2ErrHandle eh,FILE * fp,const char * id,const uint8_t * key)515 I2WriteKeyLine(
516 I2ErrHandle eh,
517 FILE *fp,
518 const char *id,
519 const uint8_t *key
520 )
521 {
522 int ret;
523 char hbuf[(I2KEYLEN*2)+1]; /* size for hex version */
524
525 if(!id || (id[0] == '\0') || (strlen(id) > I2MAXIDENTITYLEN)){
526 I2ErrLogP(eh,EINVAL,"I2WriteKeyLine(): Invalid identity name");
527 return -1;
528 }
529
530 I2HexEncode(hbuf,key,I2KEYLEN);
531
532 /*
533 * if fprintf has an error, set ret < 0 for a failure return.
534 */
535 ret = fprintf(fp,"%s\t%s\n",id,hbuf);
536
537 if(ret > 0) ret = 0;
538
539 return ret;
540 }
541
542 /*
543 * Function: I2ParsePFFile
544 *
545 * Description:
546 * Read a single line from a file fp. remove leading whitespace,
547 * skip blank lines and comment lines. Put the result in the
548 * char buffer pointed at by lbuf, growing it as necessary.
549 *
550 * Read a single identity/pass-phrase from the pffile. If tofp
551 * is set, then copy all "unmatched" lines from fp to tofp while
552 * parsing the file. If id_query is set, only return the entry that
553 * matches (if any does) skipping all others - and copying them
554 * to tofp if needed. A quick way to simply copy all remaining
555 * records to the tofp is to specify an id_query to a 0 length
556 * string (i.e. id_query[0] == '\0').
557 *
558 * The entry that is returned currently breaks the line, so
559 * *lbuf can not just be written back to the file directly.
560 *
561 * In Args:
562 *
563 * Out Args:
564 *
565 * Scope:
566 * Returns:
567 * Side Effect:
568 */
569 int
I2ParsePFFile(I2ErrHandle eh,FILE * filep,FILE * tofilep,int rc,const char * id_query,char ** id_ret,char ** pf_ret,size_t * pf_len,char ** lbuf,size_t * lbuf_max)570 I2ParsePFFile(
571 I2ErrHandle eh,
572 FILE *filep,
573 FILE *tofilep,
574 int rc,
575 const char *id_query,
576 char **id_ret, /* points in lbuf */
577 char **pf_ret, /* points in lbuf */
578 size_t *pf_len,
579 char **lbuf,
580 size_t *lbuf_max
581 )
582 {
583 char *line;
584 char *ts;
585 char *hex;
586 char *pf;
587 size_t id_len;
588 size_t hex_len;
589 size_t idq_len;
590 size_t hex_oset;
591
592 /*
593 * If there is no pffile, parsing is very, very fast.
594 */
595 if(!filep){
596 return 0;
597 }
598
599 if(id_query){
600 idq_len = strlen(id_query);
601 }
602
603 /*
604 * Fetch each non-blank, non-comment line from the pffile.
605 *
606 * Completely validate each line and then determine at the
607 * end of the loop if the caller is interested in this line or not.
608 * (This strict interpretation of the syntax of the pffile should
609 * help find errors as quickly as possible instead of letting them
610 * hide until they actually bite someone.)
611 *
612 * Valid syntax is:
613 *
614 */
615 while((rc = parsefileline(eh,filep,rc,lbuf,lbuf_max,tofilep)) > 0){
616
617 size_t used,needed;
618
619 /*
620 * line starts with id
621 */
622 line = *lbuf;
623
624 /*
625 * Find beginning of hex-pf
626 */
627 id_len = strcspn(line,I2WSPACESET);
628 hex = line + id_len;
629
630 /*
631 * Must be at least one space separator
632 */
633 if(!isspace((int)*hex)){
634 return -rc;
635 }
636
637 /*
638 * If a specific "identity" is being searched for: skip/copy
639 * lines that don't match and continue parsing the file.
640 */
641 if(id_query &&
642 ((idq_len == 0) || strncmp(line,id_query,MIN(id_len,idq_len)))){
643 /*
644 * Write line to tofilep, then 'continue'
645 */
646 if(tofilep) fprintf(tofilep,"%s\n",*lbuf);
647 continue;
648 }
649
650 /* Terminate id, then eat trailing white-space */
651 *hex = '\0';
652 hex++;
653 while(isspace((int)*hex)){
654 hex++;
655 }
656 hex_oset = hex - line;
657
658 /* how long is the hex-pf */
659 hex_len = strcspn(hex,I2WSPACESET);
660
661 /*
662 * eat trailing white-space - and terminate hex-pf
663 */
664 ts = hex + hex_len;
665 while(isspace((int)*ts)){
666 *ts = '\0';
667 ts++;
668 }
669
670 /*
671 * hex can not be nul, must be multiple of 2,
672 * and must be the last thing on the line.
673 */
674 if(!hex_len || (hex_len % 2) || (*ts != '\0')){
675 return -rc;
676 }
677
678 /*
679 * Make sure there is enough room after full line in lbuf to hold
680 * return pf. (ts currently points at the terminating nul
681 * byte for the full line.)
682 */
683 used = (ts - *lbuf) + 1;
684 needed = (hex_len / 2);
685
686 /*
687 * make sure lbuf is large enough for this content
688 */
689 if((used + needed) > *lbuf_max){
690 while((used + needed) > *lbuf_max){
691 *lbuf_max += I2LINEBUFINC;
692 }
693 *lbuf = realloc(line,sizeof(char) * *lbuf_max);
694 if(!*lbuf){
695 if(line){
696 free(line);
697 }
698 I2ErrLog(eh,"realloc(%u): %M",*lbuf_max);
699 return -rc;
700 }
701 line = *lbuf;
702 }
703
704 /*
705 * Terminate id, and point pf to beginning of binary pf
706 * to be returned.
707 */
708 hex = line + hex_oset;
709 pf = line + used;
710
711 if(!I2HexDecode(hex,(uint8_t *)pf,hex_len/2)){
712 I2ErrLogP(eh,EINVAL,"Invalid key: not hex?");
713 return -rc;
714 }
715
716 if(id_ret){
717 *id_ret = line;
718 }
719 if(pf_ret){
720 *pf_ret = pf;
721 }
722 if(pf_len){
723 *pf_len = hex_len/2;
724 }
725
726 break;
727 }
728
729 return rc;
730 }
731
732 /*
733 * Function: I2WritePFLine
734 *
735 * Description:
736 *
737 * In Args:
738 *
739 * Out Args:
740 *
741 * Scope:
742 * Returns:
743 * Side Effect:
744 */
745 int
I2WritePFLine(I2ErrHandle eh,FILE * fp,const char * id,const uint8_t * bytes,size_t nbytes,char ** lbuf,size_t * lbuf_max)746 I2WritePFLine(
747 I2ErrHandle eh,
748 FILE *fp,
749 const char *id, /* nul terminated */
750 const uint8_t *bytes,
751 size_t nbytes,
752 char **lbuf, /* memory for hex string */
753 size_t *lbuf_max
754 )
755 {
756 int ret;
757 char *line = *lbuf;
758
759 if(!id || (id[0] == '\0')){
760 I2ErrLogP(eh,EINVAL,"I2WriteKeyLine(): Invalid identity name");
761 return -1;
762 }
763
764 /*
765 * make sure lbuf is large enough for this content
766 */
767 if((nbytes*2 + 1) > *lbuf_max){
768 while((nbytes*2 + 1) > *lbuf_max){
769 *lbuf_max += I2LINEBUFINC;
770 }
771 *lbuf = realloc(line,sizeof(char) * *lbuf_max);
772 if(!*lbuf){
773 if(line){
774 free(line);
775 }
776 I2ErrLog(eh,"realloc(%u): %M",*lbuf_max);
777 return -1;
778 }
779 line = *lbuf;
780 }
781
782
783 I2HexEncode(line,bytes,nbytes);
784
785 /*
786 * if fprintf has an error, set ret < 0 for a failure return.
787 */
788 ret = fprintf(fp,"%s\t%s\n",id,line);
789
790 if(ret > 0) ret = 0;
791
792 return ret;
793 }
794
795 /*
796 * Function: I2StrToNum
797 *
798 * Description:
799 *
800 * In Args:
801 *
802 * Out Args:
803 *
804 * Scope:
805 * Returns:
806 * Side Effect:
807 */
808 int
I2StrToNum(I2numT * limnum,char * limstr)809 I2StrToNum(
810 I2numT *limnum,
811 char *limstr
812 )
813 {
814 size_t silen=0;
815 size_t len;
816 char *endptr;
817 I2numT ret, mult=1;
818
819 while(isdigit((int)limstr[silen])){
820 silen++;
821 }
822 len = strlen(limstr);
823
824 if(len != silen){
825 /*
826 * Ensure that there is at most one non-digit and that it
827 * is the last char.
828 */
829 if((len - silen) > 1){
830 return -1;
831 }
832
833 switch (tolower(limstr[silen])){
834 case 'e':
835 mult *= 1000; /* 1e18 */
836 case 'p':
837 mult *= 1000; /* 1e15 */
838 case 't':
839 mult *= 1000; /* 1e12 */
840 case 'g':
841 mult *= 1000; /* 1e9 */
842 case 'm':
843 mult *= 1000; /* 1e6 */
844 case 'k':
845 mult *= 1000; /* 1e3 */
846 break;
847 default:
848 return -1;
849 /* UNREACHED */
850 }
851 limstr[silen] = '\0';
852 }
853 ret = strtoull(limstr, &endptr, 10);
854 if(endptr != &limstr[silen]){
855 return -1;
856 }
857
858 if(ret == 0){
859 *limnum = 0;
860 return 0;
861 }
862
863 /* Check for overflow. */
864 *limnum = ret * mult;
865 return (*limnum < ret || *limnum < mult)? (-1) : 0;
866 }
867
868 /*
869 * Function: I2StrToByte
870 *
871 * Description:
872 *
873 * In Args:
874 *
875 * Out Args:
876 *
877 * Scope:
878 * Returns:
879 * Side Effect:
880 */
881 int
I2Str2Byte(I2numT * limnum,char * limstr)882 I2Str2Byte(
883 I2numT *limnum,
884 char *limstr
885 )
886 {
887 size_t silen=0;
888 size_t len;
889 char *endptr;
890 I2numT ret, mult=1;
891
892 while(isdigit((int)limstr[silen])){
893 silen++;
894 }
895 len = strlen(limstr);
896
897 if(len != silen){
898 /*
899 * Ensure that there is at most one non-digit and that it
900 * is the last char.
901 */
902 if((len - silen) > 1){
903 return -1;
904 }
905
906 switch (tolower(limstr[silen])){
907 case 'e':
908 mult <<= 10;
909 case 'p':
910 mult <<= 10;
911 case 't':
912 mult <<= 10;
913 case 'g':
914 mult <<= 10;
915 case 'm':
916 mult <<= 10;
917 case 'k':
918 mult <<= 10;
919 break;
920 default:
921 return -1;
922 /* UNREACHED */
923 }
924 limstr[silen] = '\0';
925 }
926 ret = strtoull(limstr, &endptr, 10);
927 if(endptr != &limstr[silen]){
928 return -1;
929 }
930
931 if(ret == 0){
932 *limnum = 0;
933 return 0;
934 }
935
936 /* Check for overflow. */
937 *limnum = ret * mult;
938 return (*limnum < ret || *limnum < mult)? (-1) : 0;
939 }
940