acbc7a8d7ae247d04dd2edc34537d46dd66d0025
[debian/amanda] / common-src / match.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: match.c,v 1.23 2006/05/25 01:47:12 johnfranks Exp $
28  *
29  * functions for checking and matching regular expressions
30  */
31
32 #include "amanda.h"
33 #include "match.h"
34 #include <regex.h>
35
36 static int match_word(const char *glob, const char *word, const char separator);
37 static char *tar_to_regex(const char *glob);
38
39 char *
40 validate_regexp(
41     const char *        regex)
42 {
43     regex_t regc;
44     int result;
45     static char errmsg[STR_SIZE];
46
47     if ((result = regcomp(&regc, regex,
48                           REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
49       regerror(result, &regc, errmsg, SIZEOF(errmsg));
50       return errmsg;
51     }
52
53     regfree(&regc);
54
55     return NULL;
56 }
57
58 char *
59 clean_regex(
60     const char *        str,
61     gboolean            anchor)
62 {
63     char *result;
64     int j;
65     size_t i;
66     result = alloc(2*strlen(str)+3);
67
68     j = 0;
69     if (anchor)
70         result[j++] = '^';
71     for(i=0;i<strlen(str);i++) {
72         if(!isalnum((int)str[i]))
73             result[j++]='\\';
74         result[j++]=str[i];
75     }
76     if (anchor)
77         result[j++] = '$';
78     result[j] = '\0';
79     return result;
80 }
81
82 char *
83 make_exact_host_expression(
84     const char *        host)
85 {
86     char *result;
87     int j;
88     size_t i;
89     result = alloc(2*strlen(host)+3);
90
91     j = 0;
92     result[j++] = '^';
93     for(i=0;i<strlen(host);i++) {
94         /* quote host expression metcharacters *except* '.'.  Note that
95          * most of these are invalid in a DNS hostname anyway. */
96         switch (host[i]) {
97             case '\\':
98             case '/':
99             case '^':
100             case '$':
101             case '?':
102             case '*':
103             case '[':
104             case ']':
105             result[j++]='\\';
106             /* fall through */
107
108             default:
109             result[j++]=host[i];
110         }
111     }
112     result[j++] = '$';
113     result[j] = '\0';
114     return result;
115 }
116
117 char *
118 make_exact_disk_expression(
119     const char *        disk)
120 {
121     char *result;
122     int j;
123     size_t i;
124     result = alloc(2*strlen(disk)+3);
125
126     j = 0;
127     result[j++] = '^';
128     for(i=0;i<strlen(disk);i++) {
129         /* quote disk expression metcharacters *except* '/' */
130         switch (disk[i]) {
131             case '\\':
132             case '.':
133             case '^':
134             case '$':
135             case '?':
136             case '*':
137             case '[':
138             case ']':
139             result[j++]='\\';
140             /* fall through */
141
142             default:
143             result[j++]=disk[i];
144         }
145     }
146     result[j++] = '$';
147     result[j] = '\0';
148     return result;
149 }
150
151 int
152 match(
153     const char *        regex,
154     const char *        str)
155 {
156     regex_t regc;
157     int result;
158     char errmsg[STR_SIZE];
159
160     if((result = regcomp(&regc, regex,
161                          REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
162         regerror(result, &regc, errmsg, SIZEOF(errmsg));
163         error(_("regex \"%s\": %s"), regex, errmsg);
164         /*NOTREACHED*/
165     }
166
167     if((result = regexec(&regc, str, 0, 0, 0)) != 0
168        && result != REG_NOMATCH) {
169         regerror(result, &regc, errmsg, SIZEOF(errmsg));
170         error(_("regex \"%s\": %s"), regex, errmsg);
171         /*NOTREACHED*/
172     }
173
174     regfree(&regc);
175
176     return result == 0;
177 }
178
179 int
180 match_no_newline(
181     const char *        regex,
182     const char *        str)
183 {
184     regex_t regc;
185     int result;
186     char errmsg[STR_SIZE];
187
188     if((result = regcomp(&regc, regex,
189                          REG_EXTENDED|REG_NOSUB)) != 0) {
190         regerror(result, &regc, errmsg, SIZEOF(errmsg));
191         error(_("regex \"%s\": %s"), regex, errmsg);
192         /*NOTREACHED*/
193     }
194
195     if((result = regexec(&regc, str, 0, 0, 0)) != 0
196        && result != REG_NOMATCH) {
197         regerror(result, &regc, errmsg, SIZEOF(errmsg));
198         error(_("regex \"%s\": %s"), regex, errmsg);
199         /*NOTREACHED*/
200     }
201
202     regfree(&regc);
203
204     return result == 0;
205 }
206
207 char *
208 validate_glob(
209     const char *        glob)
210 {
211     char *regex;
212     regex_t regc;
213     int result;
214     static char errmsg[STR_SIZE];
215
216     regex = glob_to_regex(glob);
217     if ((result = regcomp(&regc, regex,
218                           REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
219       regerror(result, &regc, errmsg, SIZEOF(errmsg));
220       amfree(regex);
221       return errmsg;
222     }
223
224     regfree(&regc);
225     amfree(regex);
226
227     return NULL;
228 }
229
230 int
231 match_glob(
232     const char *        glob,
233     const char *        str)
234 {
235     char *regex;
236     regex_t regc;
237     int result;
238     char errmsg[STR_SIZE];
239
240     regex = glob_to_regex(glob);
241     if((result = regcomp(&regc, regex,
242                          REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
243         regerror(result, &regc, errmsg, SIZEOF(errmsg));
244         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
245         /*NOTREACHED*/
246     }
247
248     if((result = regexec(&regc, str, 0, 0, 0)) != 0
249        && result != REG_NOMATCH) {
250         regerror(result, &regc, errmsg, SIZEOF(errmsg));
251         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
252         /*NOTREACHED*/
253     }
254
255     regfree(&regc);
256     amfree(regex);
257
258     return result == 0;
259 }
260
261 char *
262 glob_to_regex(
263     const char *        glob)
264 {
265     char *regex;
266     char *r;
267     size_t len;
268     int ch;
269     int last_ch;
270
271     /*
272      * Allocate an area to convert into.  The worst case is a five to
273      * one expansion.
274      */
275     len = strlen(glob);
276     regex = alloc(1 + len * 5 + 1 + 1);
277
278     /*
279      * Do the conversion:
280      *
281      *  ?      -> [^/]
282      *  *      -> [^/]*
283      *  [!...] -> [^...]
284      *
285      * The following are given a leading backslash to protect them
286      * unless they already have a backslash:
287      *
288      *   ( ) { } + . ^ $ |
289      *
290      * Put a leading ^ and trailing $ around the result.  If the last
291      * non-escaped character is \ leave the $ off to cause a syntax
292      * error when the regex is compiled.
293      */
294
295     r = regex;
296     *r++ = '^';
297     last_ch = '\0';
298     for (ch = *glob++; ch != '\0'; last_ch = ch, ch = *glob++) {
299         if (last_ch == '\\') {
300             *r++ = (char)ch;
301             ch = '\0';                  /* so last_ch != '\\' next time */
302         } else if (last_ch == '[' && ch == '!') {
303             *r++ = '^';
304         } else if (ch == '\\') {
305             *r++ = (char)ch;
306         } else if (ch == '*' || ch == '?') {
307             *r++ = '[';
308             *r++ = '^';
309             *r++ = '/';
310             *r++ = ']';
311             if (ch == '*') {
312                 *r++ = '*';
313             }
314         } else if (ch == '('
315                    || ch == ')'
316                    || ch == '{'
317                    || ch == '}'
318                    || ch == '+'
319                    || ch == '.'
320                    || ch == '^'
321                    || ch == '$'
322                    || ch == '|') {
323             *r++ = '\\';
324             *r++ = (char)ch;
325         } else {
326             *r++ = (char)ch;
327         }
328     }
329     if (last_ch != '\\') {
330         *r++ = '$';
331     }
332     *r = '\0';
333
334     return regex;
335 }
336
337
338 int
339 match_tar(
340     const char *        glob,
341     const char *        str)
342 {
343     char *regex;
344     regex_t regc;
345     int result;
346     char errmsg[STR_SIZE];
347
348     regex = tar_to_regex(glob);
349     if((result = regcomp(&regc, regex,
350                          REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
351         regerror(result, &regc, errmsg, SIZEOF(errmsg));
352         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
353         /*NOTREACHED*/
354     }
355
356     if((result = regexec(&regc, str, 0, 0, 0)) != 0
357        && result != REG_NOMATCH) {
358         regerror(result, &regc, errmsg, SIZEOF(errmsg));
359         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
360         /*NOTREACHED*/
361     }
362
363     regfree(&regc);
364     amfree(regex);
365
366     return result == 0;
367 }
368
369 static char *
370 tar_to_regex(
371     const char *        glob)
372 {
373     char *regex;
374     char *r;
375     size_t len;
376     int ch;
377     int last_ch;
378
379     /*
380      * Allocate an area to convert into.  The worst case is a five to
381      * one expansion.
382      */
383     len = strlen(glob);
384     regex = alloc(1 + len * 5 + 1 + 1);
385
386     /*
387      * Do the conversion:
388      *
389      *  ?      -> [^/]
390      *  *      -> .*
391      *  [!...] -> [^...]
392      *
393      * The following are given a leading backslash to protect them
394      * unless they already have a backslash:
395      *
396      *   ( ) { } + . ^ $ |
397      *
398      * Put a leading ^ and trailing $ around the result.  If the last
399      * non-escaped character is \ leave the $ off to cause a syntax
400      * error when the regex is compiled.
401      */
402
403     r = regex;
404     *r++ = '^';
405     last_ch = '\0';
406     for (ch = *glob++; ch != '\0'; last_ch = ch, ch = *glob++) {
407         if (last_ch == '\\') {
408             *r++ = (char)ch;
409             ch = '\0';                  /* so last_ch != '\\' next time */
410         } else if (last_ch == '[' && ch == '!') {
411             *r++ = '^';
412         } else if (ch == '\\') {
413             *r++ = (char)ch;
414         } else if (ch == '*') {
415             *r++ = '.';
416             *r++ = '*';
417         } else if (ch == '?') {
418             *r++ = '[';
419             *r++ = '^';
420             *r++ = '/';
421             *r++ = ']';
422         } else if (ch == '('
423                    || ch == ')'
424                    || ch == '{'
425                    || ch == '}'
426                    || ch == '+'
427                    || ch == '.'
428                    || ch == '^'
429                    || ch == '$'
430                    || ch == '|') {
431             *r++ = '\\';
432             *r++ = (char)ch;
433         } else {
434             *r++ = (char)ch;
435         }
436     }
437     if (last_ch != '\\') {
438         *r++ = '$';
439     }
440     *r = '\0';
441
442     return regex;
443 }
444
445
446 static int
447 match_word(
448     const char *        glob,
449     const char *        word,
450     const char          separator)
451 {
452     char *regex;
453     char *r;
454     size_t  len;
455     int  ch;
456     int  last_ch;
457     int  next_ch;
458     size_t  lenword;
459     char  *mword, *nword;
460     char  *mglob, *nglob;
461     char *g; 
462     const char *w;
463     int  i;
464
465     lenword = strlen(word);
466     nword = (char *)alloc(lenword + 3);
467
468     if (separator == '/' && lenword > 2 && word[0] == '\\' && word[1] == '\\' && !strchr(word, '/')) {
469         /* Convert all "\" to '/' */
470         mword = (char *)alloc(lenword + 1);
471         r = mword;
472         w = word;
473         while (*w != '\0') {
474             if (*w == '\\') {
475                 *r++ = '/';
476                 w += 1;
477             } else {
478                 *r++ = *w++;
479             }
480         }
481         *r++ = '\0';
482         lenword = strlen(word);
483
484         /* Convert all "\\" to '/' */
485         mglob = (char *)alloc(strlen(glob) + 1);
486         r = mglob;
487         w = glob;
488         while (*w != '\0') {
489             if (*w == '\\' && *(w+1) == '\\') {
490                 *r++ = '/';
491                 w += 2;
492             } else {
493                 *r++ = *w++;
494             }
495         }
496         *r++ = '\0';
497     } else {
498         mword = stralloc(word);
499         mglob = stralloc(glob);
500     }
501
502     r = nword;
503     w = mword;
504     if(lenword == 1 && *w == separator) {
505         *r++ = separator;
506         *r++ = separator;
507     }
508     else {
509         if(*w != separator)
510             *r++ = separator;
511         while(*w != '\0')
512             *r++ = *w++;
513         if(*(r-1) != separator)
514             *r++ = separator;    
515     }
516     *r = '\0';
517
518     /*
519      * Allocate an area to convert into.  The worst case is a six to
520      * one expansion.
521      */
522     len = strlen(mglob);
523     regex = (char *)alloc(1 + len * 6 + 1 + 1 + 2 + 2);
524     r = regex;
525     nglob = stralloc(mglob);
526     g = nglob;
527
528     if((len == 1 && nglob[0] == separator) ||
529        (len == 2 && nglob[0] == '^' && nglob[1] == separator) ||
530        (len == 2 && nglob[0] == separator && nglob[1] == '$') ||
531        (len == 3 && nglob[0] == '^' && nglob[1] == separator &&
532         nglob[2] == '$')) {
533         *r++ = '^';
534         *r++ = '\\';
535         *r++ = separator;
536         *r++ = '\\';
537         *r++ = separator;
538         *r++ = '$';
539     }
540     else {
541         /*
542          * Do the conversion:
543          *
544          *  ?      -> [^\separator]
545          *  *      -> [^\separator]*
546          *  [!...] -> [^...]
547          *  **     -> .*
548          *
549          * The following are given a leading backslash to protect them
550          * unless they already have a backslash:
551          *
552          *   ( ) { } + . ^ $ |
553          *
554          * If the last
555          * non-escaped character is \ leave it to cause a syntax
556          * error when the regex is compiled.
557          */
558
559         if(*g == '^') {
560             *r++ = '^';
561             *r++ = '\\';        /* escape the separator */
562             *r++ = separator;
563             g++;
564             if(*g == separator) g++;
565         }
566         else if(*g != separator) {
567             *r++ = '\\';        /* add a leading \separator */
568             *r++ = separator;
569         }
570         last_ch = '\0';
571         for (ch = *g++; ch != '\0'; last_ch = ch, ch = *g++) {
572             next_ch = *g;
573             if (last_ch == '\\') {
574                 *r++ = (char)ch;
575                 ch = '\0';              /* so last_ch != '\\' next time */
576             } else if (last_ch == '[' && ch == '!') {
577                 *r++ = '^';
578             } else if (ch == '\\') {
579                 *r++ = (char)ch;
580             } else if (ch == '*' || ch == '?') {
581                 if(ch == '*' && next_ch == '*') {
582                     *r++ = '.';
583                     g++;
584                 }
585                 else {
586                     *r++ = '[';
587                     *r++ = '^';
588                     *r++ = '\\';
589                     *r++ = separator;
590                     *r++ = ']';
591                 }
592                 if (ch == '*') {
593                     *r++ = '*';
594                 }
595             } else if (ch == '$' && next_ch == '\0') {
596                 if(last_ch != separator) {
597                     *r++ = '\\';
598                     *r++ = separator;
599                 }
600                 *r++ = (char)ch;
601             } else if (   ch == '('
602                        || ch == ')'
603                        || ch == '{'
604                        || ch == '}'
605                        || ch == '+'
606                        || ch == '.'
607                        || ch == '^'
608                        || ch == '$'
609                        || ch == '|') {
610                 *r++ = '\\';
611                 *r++ = (char)ch;
612             } else {
613                 *r++ = (char)ch;
614             }
615         }
616         if(last_ch != '\\') {
617             if(last_ch != separator && last_ch != '$') {
618                 *r++ = '\\';
619                 *r++ = separator;               /* add a trailing \separator */
620             }
621         }
622     }
623     *r = '\0';
624
625     i = match(regex,nword);
626
627     amfree(mword);
628     amfree(mglob);
629     amfree(nword);
630     amfree(nglob);
631     amfree(regex);
632     return i;
633 }
634
635
636 int
637 match_host(
638     const char *        glob,
639     const char *        host)
640 {
641     char *lglob, *lhost;
642     char *c;
643     const char *d;
644     int i;
645
646     
647     lglob = (char *)alloc(strlen(glob)+1);
648     c = lglob, d=glob;
649     while( *d != '\0')
650         *c++ = (char)tolower(*d++);
651     *c = *d;
652
653     lhost = (char *)alloc(strlen(host)+1);
654     c = lhost, d=host;
655     while( *d != '\0')
656         *c++ = (char)tolower(*d++);
657     *c = *d;
658
659     i = match_word(lglob, lhost, (int)'.');
660     amfree(lglob);
661     amfree(lhost);
662     return i;
663 }
664
665
666 int
667 match_disk(
668     const char *        glob,
669     const char *        disk)
670 {
671     return match_word(glob, disk, '/');
672 }
673
674 static int
675 alldigits(
676     const char *str)
677 {
678     while (*str) {
679         if (!isdigit((int)*(str++)))
680             return 0;
681     }
682     return 1;
683 }
684
685 int
686 match_datestamp(
687     const char *        dateexp,
688     const char *        datestamp)
689 {
690     char *dash;
691     size_t len, len_suffix;
692     size_t len_prefix;
693     char firstdate[100], lastdate[100];
694     char mydateexp[100];
695     int match_exact;
696
697     if(strlen(dateexp) >= 100 || strlen(dateexp) < 1) {
698         goto illegal;
699     }
700    
701     /* strip and ignore an initial "^" */
702     if(dateexp[0] == '^') {
703         strncpy(mydateexp, dateexp+1, sizeof(mydateexp)-1);
704         mydateexp[sizeof(mydateexp)-1] = '\0';
705     }
706     else {
707         strncpy(mydateexp, dateexp, sizeof(mydateexp)-1);
708         mydateexp[sizeof(mydateexp)-1] = '\0';
709     }
710
711     if(mydateexp[strlen(mydateexp)-1] == '$') {
712         match_exact = 1;
713         mydateexp[strlen(mydateexp)-1] = '\0';  /* strip the trailing $ */
714     }
715     else
716         match_exact = 0;
717
718     /* a single dash represents a date range */
719     if((dash = strchr(mydateexp,'-'))) {
720         if(match_exact == 1 || strchr(dash+1, '-')) {
721             goto illegal;
722         }
723
724         /* format: XXXYYYY-ZZZZ, indicating dates XXXYYYY to XXXZZZZ */
725
726         len = (size_t)(dash - mydateexp);   /* length of XXXYYYY */
727         len_suffix = strlen(dash) - 1;  /* length of ZZZZ */
728         if (len_suffix > len) goto illegal;
729         len_prefix = len - len_suffix; /* length of XXX */
730
731         dash++;
732
733         strncpy(firstdate, mydateexp, len);
734         firstdate[len] = '\0';
735         strncpy(lastdate, mydateexp, len_prefix);
736         strncpy(&(lastdate[len_prefix]), dash, len_suffix);
737         lastdate[len] = '\0';
738         if (!alldigits(firstdate) || !alldigits(lastdate))
739             goto illegal;
740         if (strncmp(firstdate, lastdate, strlen(firstdate)) > 0)
741             goto illegal;
742         return ((strncmp(datestamp, firstdate, strlen(firstdate)) >= 0) &&
743                 (strncmp(datestamp, lastdate , strlen(lastdate))  <= 0));
744     }
745     else {
746         if (!alldigits(mydateexp))
747             goto illegal;
748         if(match_exact == 1) {
749             return (strcmp(datestamp, mydateexp) == 0);
750         }
751         else {
752             return (strncmp(datestamp, mydateexp, strlen(mydateexp)) == 0);
753         }
754     }
755 illegal:
756         error(_("Illegal datestamp expression %s"),dateexp);
757         /*NOTREACHED*/
758 }
759
760
761 int
762 match_level(
763     const char *        levelexp,
764     const char *        level)
765 {
766     char *dash;
767     long int low, hi, level_i;
768     char mylevelexp[100];
769     int match_exact;
770
771     if(strlen(levelexp) >= 100 || strlen(levelexp) < 1) {
772         error(_("Illegal level expression %s"),levelexp);
773         /*NOTREACHED*/
774     }
775    
776     if(levelexp[0] == '^') {
777         strncpy(mylevelexp, levelexp+1, strlen(levelexp)-1); 
778         mylevelexp[strlen(levelexp)-1] = '\0';
779     }
780     else {
781         strncpy(mylevelexp, levelexp, strlen(levelexp));
782         mylevelexp[strlen(levelexp)] = '\0';
783     }
784
785     if(mylevelexp[strlen(mylevelexp)] == '$') {
786         match_exact = 1;
787         mylevelexp[strlen(mylevelexp)] = '\0';
788     }
789     else
790         match_exact = 0;
791
792     if((dash = strchr(mylevelexp,'-'))) {
793         if(match_exact == 1) {
794             goto illegal;
795         }
796
797         *dash = '\0';
798         if (!alldigits(mylevelexp) || !alldigits(dash+1)) goto illegal;
799
800         errno = 0;
801         low = strtol(mylevelexp, (char **) NULL, 10);
802         if (errno) goto illegal;
803         hi = strtol(dash+1, (char **) NULL, 10);
804         if (errno) goto illegal;
805         level_i = strtol(level, (char **) NULL, 10);
806         if (errno) goto illegal;
807
808         return ((level_i >= low) && (level_i <= hi));
809     }
810     else {
811         if (!alldigits(mylevelexp)) goto illegal;
812         if(match_exact == 1) {
813             return (strcmp(level, mylevelexp) == 0);
814         }
815         else {
816             return (strncmp(level, mylevelexp, strlen(mylevelexp)) == 0);
817         }
818     }
819 illegal:
820     error(_("Illegal level expression %s"),levelexp);
821     /*NOTREACHED*/
822 }