98e48fbd90d73e7aa6ae4c029ca9765f1c86d3f5
[krb5.git] / src / lib / krb4 / kparse.c
1 /*
2  * kparse.c
3  *
4  * Copyright 1988 by the Massachusetts Institute of Technology.
5  *
6  * For copying and distribution information, please see the file
7  * <mit-copyright.h>.
8  *
9  * Purpose:
10  * This module was developed to parse the "~/.klogin" files for
11  * Kerberos-authenticated rlogin/rcp/rsh services.  However, it is
12  * general purpose and can be used to parse any such parameter file.
13  *
14  * The parameter file should consist of one or more entries, with each
15  * entry on a separate line and consisting of zero or more
16  * "keyword=value" combinations.  The keyword is case insensitive, but
17  * the value is not.  Any string may be enclosed in quotes, and
18  * c-style "\" literals are supported.  A comma may be used to
19  * separate the k/v combinations, and multiple commas are ignored.
20  * Whitespace (blank or tab) may be used freely and is ignored.
21  *
22  * Full error processing is available.  When PS_BAD_KEYWORD or
23  * PS_SYNTAX is returned from fGetParameterSet(), the string ErrorMsg
24  * contains a meaningful error message.
25  *
26  * Keywords and their default values are programmed by an external
27  * table.
28  *
29  * Routines:
30  * fGetParameterSet()      parse one line of the parameter file
31  * fGetKeywordValue()      parse one "keyword=value" combo
32  * fGetToken()             parse one token
33  */
34
35 #include "mit-copyright.h"
36 #include "krb.h"
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <kparse.h>
40 #include <string.h>
41 #ifdef HAVE_STDLIB_H
42 #include <stdlib.h>
43 #endif
44
45 #ifndef FALSE
46 #define FALSE 0
47 #define TRUE 1
48 #endif
49
50 #define void int
51
52 #define MAXKEY          80
53 #define MAXVALUE        80
54
55 static char *strutol();
56
57 #ifndef HAVE_STRSAVE
58 static char *strsave();
59 #endif
60 #ifndef HAVE_STDLIB_H
61 extern char *malloc();
62 #endif
63
64 static int sLineNbr=1;          /* current line nbr in parameter file */
65 static char ErrorMsg[80];       /* meaningful only when KV_SYNTAX, PS_SYNTAX,
66                                  * or PS_BAD_KEYWORD is returned by
67                                  * fGetKeywordValue or fGetParameterSet */
68 \f
69 int fGetParameterSet( fp,parm,parmcount )
70     FILE *fp;
71     parmtable parm[];
72     int parmcount;
73 {
74     int rc,i;
75     char keyword[MAXKEY];
76     char value[MAXVALUE];
77
78     while (TRUE) {
79         rc=fGetKeywordValue(fp,keyword,MAXKEY,value,MAXVALUE);
80
81         switch (rc) {
82
83         case KV_EOF:
84             return(PS_EOF);
85
86         case KV_EOL:
87             return(PS_OKAY);
88
89         case KV_SYNTAX:
90             return(PS_SYNTAX);
91
92         case KV_OKAY:
93             /*
94              * got a reasonable keyword/value pair.  Search the
95              * parameter table to see if we recognize the keyword; if
96              * not, return an error.  If we DO recognize it, make sure
97              * it has not already been given.  If not already given,
98              * save the value.
99              */
100             for (i=0; i<parmcount; i++) {
101                 if (strcmp(strutol(keyword),parm[i].keyword)==0) {
102                     if (parm[i].value) {
103                         sprintf(ErrorMsg,"duplicate keyword \"%s\" found",
104                                 keyword);
105                         return(PS_BAD_KEYWORD);
106                     }
107                     parm[i].value = strsave( value );
108                     break;
109                 }
110             }
111             if (i >= parmcount) {
112                 sprintf(ErrorMsg, "unrecognized keyword \"%s\" found",
113                         keyword);
114                 return(PS_BAD_KEYWORD);
115             }
116             break;
117
118         default:
119             sprintf(ErrorMsg,
120                     "panic: bad return (%d) from fGetToken()",rc);
121             break;
122         }
123     }
124 }
125 \f
126 /*
127  * Routine: ParmCompare
128  *
129  * Purpose:
130  * ParmCompare checks a specified value for a particular keyword.
131  * fails if keyword not found or keyword found but the value was
132  * different. Like strcmp, ParmCompare returns 0 for a match found, -1
133  * otherwise
134  */
135 int ParmCompare( parm, parmcount, keyword, value )
136     parmtable parm[];
137     int parmcount;
138     char *keyword;
139     char *value;
140 {
141     int i;
142
143     for (i=0; i<parmcount; i++) {
144         if (strcmp(parm[i].keyword,keyword)==0) {
145             if (parm[i].value) {
146                 return(strcmp(parm[i].value,value));
147             } else {
148                 return(strcmp(parm[i].defvalue,value));
149             }
150         }
151     }
152     return(-1);
153 }
154
155 void FreeParameterSet(parm,parmcount)
156     parmtable parm[];
157     int parmcount;
158 {
159     int i;
160
161     for (i=0; i<parmcount; i++) {
162         if (parm[i].value) {
163             free(parm[i].value);
164             parm[i].value = (char *)NULL;
165         }
166     }
167 }
168 \f
169 int fGetKeywordValue( fp, keyword, klen, value, vlen )
170     FILE *fp;
171     char *keyword;
172     int klen;
173     char *value;
174     int vlen;
175 {
176     int rc;
177     int gotit;
178
179     *keyword = *value = '\0';   /* preset strings to NULL */
180
181     /*
182      * Looking for a keyword.
183      *          return an exception for EOF or BAD_QSTRING
184      *          ignore leading WHITEspace
185      *          ignore any number of leading commas
186      *          newline means we have all the parms for this
187      *                  statement; give an indication that there is
188      *                  nothing more on this line.
189      *          stop looking if we find QSTRING, STRING, or NUMBER
190      *          return syntax error for any other PUNKtuation
191      */
192     gotit = FALSE;
193     do {
194         rc = fGetToken(fp,keyword,klen);
195
196         switch (rc) {
197
198         case GTOK_WHITE:
199             break;
200
201         case GTOK_EOF:
202             return(KV_EOF);
203
204         case GTOK_BAD_QSTRING:
205             sprintf(ErrorMsg,"unterminated string \"%s found",keyword);
206             return(KV_SYNTAX);
207
208         case GTOK_PUNK:
209             if (strcmp("\n",keyword)==0) {
210                 return(KV_EOL);
211             } else if (strcmp(",",keyword)!=0) {
212                 sprintf(ErrorMsg,"expecting rvalue, found \'%s\'",keyword);
213             }
214             break;
215
216         case GTOK_STRING:
217         case GTOK_QSTRING:
218         case GTOK_NUMBER:
219             gotit = TRUE;
220             break;
221
222         default:
223             sprintf(ErrorMsg,"panic: bad return (%d) from fGetToken()",rc);
224             return(KV_SYNTAX);
225         }
226
227     } while (!gotit);
228
229     /*
230      * now we expect an equal sign.
231      *          skip any whitespace
232      *          stop looking if we find an equal sign
233      *          anything else causes a syntax error
234      */
235     gotit = FALSE;
236     do {
237         rc = fGetToken(fp,value,vlen);
238
239         switch (rc) {
240
241         case GTOK_WHITE:
242             break;
243
244         case GTOK_BAD_QSTRING:
245             sprintf(ErrorMsg,
246                     "expecting \'=\', found unterminated string \"%s",
247                     value);
248             return(KV_SYNTAX);
249
250         case GTOK_PUNK:
251             if (strcmp("=",value)==0) {
252                 gotit = TRUE;
253             } else {
254                 if (strcmp("\n",value)==0) {
255                     sprintf(ErrorMsg,"expecting \"=\", found newline");
256                     fUngetChar('\n',fp);
257                 } else {
258                     sprintf(ErrorMsg,
259                             "expecting rvalue, found \'%s\'",keyword);
260                 }
261                 return(KV_SYNTAX);
262             }
263             break;
264
265         case GTOK_STRING:
266         case GTOK_QSTRING:
267         case GTOK_NUMBER:
268             sprintf(ErrorMsg,"expecting \'=\', found \"%s\"",value);
269             return(KV_SYNTAX);
270
271         case GTOK_EOF:
272             sprintf(ErrorMsg,"expecting \'=\', found EOF");
273             return(KV_SYNTAX);
274
275         default:
276             sprintf(ErrorMsg,
277                     "panic: bad return (%d) from fGetToken()",rc);
278             return(KV_SYNTAX);
279         }
280
281     } while ( !gotit );
282
283     /*
284      * got the keyword and equal sign, now get a value.
285      *          ignore any whitespace
286      *          any punctuation is a syntax error
287      */
288     gotit = FALSE;
289     do {
290         rc = fGetToken(fp,value,vlen);
291
292         switch (rc) {
293
294         case GTOK_WHITE:
295             break;
296
297         case GTOK_EOF:
298             sprintf(ErrorMsg,"expecting rvalue, found EOF");
299             return(KV_SYNTAX);
300
301         case GTOK_BAD_QSTRING:
302             sprintf(ErrorMsg,"unterminated quoted string \"%s",value);
303             return(KV_SYNTAX);
304
305         case GTOK_PUNK:
306             if (strcmp("\n",value)==0) {
307                 sprintf(ErrorMsg,"expecting rvalue, found newline");
308                 fUngetChar('\n',fp);
309             } else {
310                 sprintf(ErrorMsg,
311                         "expecting rvalue, found \'%s\'",value);
312             }
313             return(KV_SYNTAX);
314             break;
315
316         case GTOK_STRING:
317         case GTOK_QSTRING:
318         case GTOK_NUMBER:
319             gotit = TRUE;
320             return(KV_OKAY);
321
322         default:
323             sprintf(ErrorMsg,
324                     "panic: bad return (%d) from fGetToken()",rc);
325             return(KV_SYNTAX);
326         }
327
328     } while ( !gotit );
329     /*NOTREACHED*/
330 }
331 \f
332 /*
333  * Routine Name: fGetToken
334  *
335  * Function: read the next token from the specified file.
336  * A token is defined as a group of characters
337  * terminated by a white space char (SPACE, CR,
338  * LF, FF, TAB). The token returned is stripped of
339  * both leading and trailing white space, and is
340  * terminated by a NULL terminator.  An alternate
341  * definition of a token is a string enclosed in
342  * single or double quotes.
343  *
344  * Explicit Parameters:
345  * fp              pointer to the input FILE
346  * dest    pointer to destination buffer
347  * maxlen  length of the destination buffer. The buffer
348  * length INCLUDES the NULL terminator.
349  *
350  * Implicit Parameters: stderr  where the "token too long" message goes
351  *
352  * External Procedures: fgetc
353  *
354  * Side Effects:                None
355  *
356  * Return Value:                A token classification value, as
357  *                              defined in kparse.h. Note that the
358  *                              classification for end of file is
359  *                              always zero.
360  */
361 int fGetToken(fp, dest, maxlen)
362     FILE *fp;
363     char *dest;
364     int  maxlen;
365 {
366     int ch='\0';
367     int len=0;
368     char *p = dest;
369     int digits;
370
371     ch=fGetChar(fp);
372
373     /*
374      * check for a quoted string.  If found, take all characters
375      * that fit until a closing quote is found.  Note that this
376      * algorithm will not behave well for a string which is too long.
377      */
378     if (ISQUOTE(ch)) {
379         int done = FALSE;
380         do {
381             ch = fGetChar(fp);
382             done = ((maxlen<++len)||ISLINEFEED(ch)||(ch==EOF)
383                     ||ISQUOTE(ch));
384             if (ch=='\\')
385                 ch = fGetLiteral(fp);
386             if (!done)
387                 *p++ = ch;
388             else if ((ch!=EOF) && !ISQUOTE(ch))
389                 fUngetChar(ch,fp);
390         } while (!done);
391         *p = '\0';
392         if (ISLINEFEED(ch)) return(GTOK_BAD_QSTRING);
393         return(GTOK_QSTRING);
394     }
395
396     /*
397      * Not a quoted string.  If its a token character (rules are
398      * defined via the ISTOKENCHAR macro, in kparse.h) take it and all
399      * token chars following it until we run out of space.
400      */
401     digits=TRUE;
402     if (ISTOKENCHAR(ch)) {
403         while ( (ISTOKENCHAR(ch)) && len<maxlen-1 ) {
404             if (!isdigit(ch)) digits=FALSE;
405             *p++ = ch;
406             len++;
407             ch = fGetChar(fp);
408         };
409         *p = '\0';
410
411         if (ch!=EOF) {
412             fUngetChar(ch,fp);
413         }
414         if (digits) {
415             return(GTOK_NUMBER);
416         } else {
417             return(GTOK_STRING);
418         }
419     }
420
421     /*
422      * Neither a quoted string nor a token character.  Return a string
423      * with just that one character in it.
424      */
425     if (ch==EOF) {
426         return(GTOK_EOF);
427     }
428     if (!ISWHITESPACE(ch)) {
429         *p++ = ch;
430         *p='\0';
431     } else {
432         *p++ = ' ';             /* white space is always the
433                                  * blank character */
434         *p='\0';
435         /*
436          * The character is a white space. Flush all additional white
437          * space.
438          */
439         while (ISWHITESPACE(ch) && ((ch=fGetChar(fp)) != EOF))
440             ;
441         if (ch!=EOF) {
442             fUngetChar(ch,fp);
443         }
444         return(GTOK_WHITE);
445     }
446     return(GTOK_PUNK);
447 }
448 \f
449 /*
450  * fGetLiteral is called after we find a '\' in the input stream.  A
451  * string of numbers following the backslash are converted to the
452  * appropriate value; hex (0xn), octal (0n), and decimal (otherwise)
453  * are all supported.  If the char after the \ is not a number, we
454  * special case certain values (\n, \f, \r, \b) or return a literal
455  * otherwise (useful for \", for example).
456  */
457 int fGetLiteral(fp)
458     FILE *fp;
459 {
460     int ch;
461     int n=0;
462     int base;
463
464     ch = fGetChar(fp);
465
466     if (!isdigit(ch)) {
467         switch (ch) {
468         case 'n':       return('\n');
469         case 'f':       return('\f');
470         case 'r':       return('\r');
471         case 'b':       return('\b');
472         default:        return(ch);
473         }
474     }
475
476     /*
477      * got a number.  might be decimal (no prefix), octal (prefix 0),
478      * or hexadecimal (prefix 0x).  Set the base appropriately.
479      */
480     if (ch!='0') {
481         base=10;                /* its a decimal number */
482     } else {
483         /*
484          * found a zero, its either hex or octal
485          */
486         ch = fGetChar(fp);
487         if ((ch!='x') && (ch!='X')) {
488             base=010;
489         } else {
490             ch = fGetChar(fp);
491             base=0x10;
492         }
493     }
494
495     switch (base) {
496
497     case 010:                   /* octal */
498         while (ISOCTAL(ch)) {
499             n = (n*base) + ch - '0';
500             ch = fGetChar(fp);
501         }
502         break;
503
504     case 10:                    /* decimal */
505         while (isdigit(ch)) {
506             n = (n*base) + ch - '0';
507             ch = fGetChar(fp);
508         }
509         break;
510     case 0x10:                  /* hexadecimal */
511         while (isxdigit(ch)) {
512             if (isdigit(ch)) {
513                 n = (n*base) + ch - '0';
514             } else {
515                 n = (n*base) + toupper(ch) - 'A' + 0xA ;
516             }
517             ch = fGetChar(fp);
518         }
519         break;
520     default:
521 #ifdef DEBUG
522         fprintf(stderr,"fGetLiteral() died real bad. Fix kparse.c.");
523 #endif
524         break;
525     }
526     fUngetChar(ch,fp);
527     return(n);
528 }
529 \f
530 /*
531  * exactly the same as ungetc(3) except that the line number of the
532  * input file is maintained.
533  */
534 int fUngetChar(ch,fp)
535     int ch;
536     FILE *fp;
537 {
538     if (ch=='\n') sLineNbr--;
539     return(ungetc(ch,fp));
540 }
541
542
543 /*
544  * exactly the same as fgetc(3) except that the line number of the
545  * input file is maintained.
546  */
547 int fGetChar(fp)
548     FILE *fp;
549 {
550     int ch = fgetc(fp);
551     if (ch=='\n') sLineNbr++;
552     return(ch);
553 }
554
555
556 /*
557  * Routine Name: strsave
558  *
559  * Function: return a pointer to a saved copy of the
560  * input string. the copy will be allocated
561  * as large as necessary.
562  *
563  * Explicit Parameters: pointer to string to save
564  *
565  * Implicit Parameters: None
566  *
567  * External Procedures: malloc,strcpy,strlen
568  *
569  * Side Effects: None
570  *
571  * Return Value: pointer to copied string
572  *
573  */
574 #ifndef HAVE_STRSAVE
575 static char * strsave(p)
576     char *p;
577 {
578     return(strcpy(malloc(strlen(p)+1),p));
579 }
580 #endif
581
582
583 /*
584  * strutol changes all characters in a string to lower case, in place.
585  * the pointer to the beginning of the string is returned.
586  */
587
588 static char * strutol( start )
589     char *start;
590 {
591     char *q;
592     for (q=start; *q; q++)
593         if (isupper(*q))
594             *q=tolower(*q);
595     return(start);
596 }
597 \f
598 #ifdef GTOK_TEST             /* mainline test routine for fGetToken() */
599
600 #define MAXTOKEN 100
601
602 char *pgm = "gettoken";
603
604 main(argc,argv)
605     int argc;
606     char **argv;
607 {
608     char *p;
609     int type;
610     FILE *fp;
611
612     if (--argc) {
613         fp = fopen(*++argv,"ra");
614         if (fp == (FILE *)NULL) {
615             fprintf(stderr,"can\'t open \"%s\"\n",*argv);
616         }
617     } else
618         fp = stdin;
619
620     p = malloc(MAXTOKEN);
621     while (type = fGetToken(fp,p,MAXTOKEN)) {
622         switch(type) {
623         case GTOK_BAD_QSTRING:
624             printf("BAD QSTRING!\t");
625             break;
626         case GTOK_EOF:
627             printf("EOF!\t");
628             break;
629         case GTOK_QSTRING:
630             printf("QSTRING\t");
631             break;
632         case GTOK_STRING:
633             printf("STRING\t");
634             break;
635         case GTOK_NUMBER:
636             printf("NUMBER\t");
637             break;
638         case GTOK_PUNK:
639             printf("PUNK\t");
640             break;
641         case GTOK_WHITE:
642             printf("WHITE\t");
643             break;
644         default:
645             printf("HUH?\t");
646             break;
647         }
648         if (*p=='\n')
649             printf("\\n\n");
650         else
651             printf("%s\n",p);
652     }
653     exit(0);
654 }
655 #endif
656 \f
657 #ifdef KVTEST
658
659 main(argc,argv)
660     int argc;
661     char **argv;
662 {
663     int rc,ch;
664     FILE *fp;
665     char key[MAXKEY],valu[MAXVALUE];
666     char *filename;
667
668     if (argc != 2) {
669         fprintf(stderr,"usage: test <filename>\n");
670         exit(1);
671     }
672
673     if (!(fp=fopen(*++argv,"r"))) {
674         fprintf(stderr,"can\'t open input file \"%s\"\n",filename);
675         exit(1);
676     }
677     filename = *argv;
678
679     while ((rc=fGetKeywordValue(fp,key,MAXKEY,valu,MAXVALUE))!=KV_EOF){
680
681         switch (rc) {
682
683         case KV_EOL:
684             printf("%s, line %d: nada mas.\n",filename,sLineNbr-1);
685             break;
686
687         case KV_SYNTAX:
688             printf("%s, line %d: syntax error: %s\n",
689                    filename,sLineNbr,ErrorMsg);
690             while ( ((ch=fGetChar(fp))!=EOF) && (ch!='\n') );
691             break;
692
693         case KV_OKAY:
694             printf("%s, line %d: okay, %s=\"%s\"\n",
695                    filename,sLineNbr,key,valu);
696             break;
697
698         default:
699             printf("panic: bad return (%d) from fGetKeywordValue\n",rc);
700             break;
701         }
702     }
703     printf("EOF");
704     fclose(fp);
705     exit(0);
706 }
707 #endif
708 \f
709 #ifdef PSTEST
710
711 parmtable kparm[] = {
712     /*  keyword, default, found value */
713     { "user",       "",    (char *)NULL },
714     { "realm",   "Athena", (char *)NULL },
715     { "instance",   "",    (char *)NULL }
716 };
717
718 main(argc,argv)
719     int argc;
720     char **argv;
721 {
722     int rc,i,ch;
723     FILE *fp;
724     char *filename;
725
726     if (argc != 2) {
727         fprintf(stderr,"usage: test <filename>\n");
728         exit(1);
729     }
730
731     if (!(fp=fopen(*++argv,"r"))) {
732         fprintf(stderr,"can\'t open input file \"%s\"\n",filename);
733         exit(1);
734     }
735     filename = *argv;
736
737     while ((rc=fGetParameterSet(fp,kparm,PARMCOUNT(kparm))) != PS_EOF) {
738
739         switch (rc) {
740
741         case PS_BAD_KEYWORD:
742             printf("%s, line %d: %s\n",filename,sLineNbr,ErrorMsg);
743             while ( ((ch=fGetChar(fp))!=EOF) && (ch!='\n') );
744             break;
745
746         case PS_SYNTAX:
747             printf("%s, line %d: syntax error: %s\n",
748                    filename,sLineNbr,ErrorMsg);
749             while ( ((ch=fGetChar(fp))!=EOF) && (ch!='\n') );
750             break;
751
752         case PS_OKAY:
753             printf("%s, line %d: valid parameter set found:\n",
754                    filename,sLineNbr-1);
755             for (i=0; i<PARMCOUNT(kparm); i++) {
756                 printf("\t%s = \"%s\"\n",kparm[i].keyword,
757                        (kparm[i].value ? kparm[i].value
758                         : kparm[i].defvalue));
759             }
760             break;
761
762         default:
763             printf("panic: bad return (%d) from fGetParameterSet\n",rc);
764             break;
765         }
766         FreeParameterSet(kparm,PARMCOUNT(kparm));
767     }
768     printf("EOF");
769     fclose(fp);
770     exit(0);
771 }
772 #endif