Imported Upstream version 3.2.0
[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      *
286      * The following are given a leading backslash to protect them
287      * unless they already have a backslash:
288      *
289      *   ( ) { } + . ^ $ |
290      *
291      * Put a leading ^ and trailing $ around the result.  If the last
292      * non-escaped character is \ leave the $ off to cause a syntax
293      * error when the regex is compiled.
294      */
295
296     r = regex;
297     *r++ = '^';
298     last_ch = '\0';
299     for (ch = *glob++; ch != '\0'; last_ch = ch, ch = *glob++) {
300         if (last_ch == '\\') {
301             *r++ = (char)ch;
302             ch = '\0';                  /* so last_ch != '\\' next time */
303         } else if (last_ch == '[' && ch == '!') {
304             *r++ = '^';
305         } else if (ch == '\\') {
306             *r++ = (char)ch;
307         } else if (ch == '*' || ch == '?') {
308             *r++ = '[';
309             *r++ = '^';
310             *r++ = '/';
311             *r++ = ']';
312             if (ch == '*') {
313                 *r++ = '*';
314             }
315         } else if (ch == '('
316                    || ch == ')'
317                    || ch == '{'
318                    || ch == '}'
319                    || ch == '+'
320                    || ch == '.'
321                    || ch == '^'
322                    || ch == '$'
323                    || ch == '|') {
324             *r++ = '\\';
325             *r++ = (char)ch;
326         } else {
327             *r++ = (char)ch;
328         }
329     }
330     if (last_ch != '\\') {
331         *r++ = '$';
332     }
333     *r = '\0';
334
335     return regex;
336 }
337
338
339 int
340 match_tar(
341     const char *        glob,
342     const char *        str)
343 {
344     char *regex;
345     regex_t regc;
346     int result;
347     char errmsg[STR_SIZE];
348
349     regex = tar_to_regex(glob);
350     if((result = regcomp(&regc, regex,
351                          REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
352         regerror(result, &regc, errmsg, SIZEOF(errmsg));
353         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
354         /*NOTREACHED*/
355     }
356
357     if((result = regexec(&regc, str, 0, 0, 0)) != 0
358        && result != REG_NOMATCH) {
359         regerror(result, &regc, errmsg, SIZEOF(errmsg));
360         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
361         /*NOTREACHED*/
362     }
363
364     regfree(&regc);
365     amfree(regex);
366
367     return result == 0;
368 }
369
370 static char *
371 tar_to_regex(
372     const char *        glob)
373 {
374     char *regex;
375     char *r;
376     size_t len;
377     int ch;
378     int last_ch;
379
380     /*
381      * Allocate an area to convert into.  The worst case is a five to
382      * one expansion.
383      */
384     len = strlen(glob);
385     regex = alloc(1 + len * 5 + 5 + 5);
386
387     /*
388      * Do the conversion:
389      *
390      *  ?      -> [^/]
391      *  *      -> .*
392      *  [...]  -> [...]
393      *  [!...] -> [^...]
394      *
395      * The following are given a leading backslash to protect them
396      * unless they already have a backslash:
397      *
398      *   ( ) { } + . ^ $ |
399      *
400      * The expression must begin and end either at the beginning/end of the string or
401      * at at a pathname separator.
402      *
403      * If the last non-escaped character is \ leave the $ off to cause a syntax
404      * error when the regex is compiled.
405      */
406
407     r = regex;
408     *r++ = '(';
409     *r++ = '^';
410     *r++ = '|';
411     *r++ = '/';
412     *r++ = ')';
413     last_ch = '\0';
414     for (ch = *glob++; ch != '\0'; last_ch = ch, ch = *glob++) {
415         if (last_ch == '\\') {
416             *r++ = (char)ch;
417             ch = '\0';                  /* so last_ch != '\\' next time */
418         } else if (last_ch == '[' && ch == '!') {
419             *r++ = '^';
420         } else if (ch == '\\') {
421             *r++ = (char)ch;
422         } else if (ch == '*') {
423             *r++ = '.';
424             *r++ = '*';
425         } else if (ch == '?') {
426             *r++ = '[';
427             *r++ = '^';
428             *r++ = '/';
429             *r++ = ']';
430         } else if (ch == '('
431                    || ch == ')'
432                    || ch == '{'
433                    || ch == '}'
434                    || ch == '+'
435                    || ch == '.'
436                    || ch == '^'
437                    || ch == '$'
438                    || ch == '|') {
439             *r++ = '\\';
440             *r++ = (char)ch;
441         } else {
442             *r++ = (char)ch;
443         }
444     }
445     if (last_ch != '\\') {
446         *r++ = '(';
447         *r++ = '$';
448         *r++ = '|';
449         *r++ = '/';
450         *r++ = ')';
451     }
452     *r = '\0';
453
454     return regex;
455 }
456
457
458 static int
459 match_word(
460     const char *        glob,
461     const char *        word,
462     const char          separator)
463 {
464     char *regex;
465     char *r;
466     size_t  len;
467     int  ch;
468     int  last_ch;
469     int  next_ch;
470     size_t  lenword;
471     char  *mword, *nword;
472     char  *mglob, *nglob;
473     char *g; 
474     const char *w;
475     int  i;
476
477     lenword = strlen(word);
478     nword = (char *)alloc(lenword + 3);
479
480     if (separator == '/' && lenword > 2 && word[0] == '\\' && word[1] == '\\' && !strchr(word, '/')) {
481         /* Convert all "\" to '/' */
482         mword = (char *)alloc(lenword + 1);
483         r = mword;
484         w = word;
485         while (*w != '\0') {
486             if (*w == '\\') {
487                 *r++ = '/';
488                 w += 1;
489             } else {
490                 *r++ = *w++;
491             }
492         }
493         *r++ = '\0';
494         lenword = strlen(word);
495
496         /* Convert all "\\" to '/' */
497         mglob = (char *)alloc(strlen(glob) + 1);
498         r = mglob;
499         w = glob;
500         while (*w != '\0') {
501             if (*w == '\\' && *(w+1) == '\\') {
502                 *r++ = '/';
503                 w += 2;
504             } else {
505                 *r++ = *w++;
506             }
507         }
508         *r++ = '\0';
509     } else {
510         mword = stralloc(word);
511         mglob = stralloc(glob);
512     }
513
514     r = nword;
515     w = mword;
516     if(lenword == 1 && *w == separator) {
517         *r++ = separator;
518         *r++ = separator;
519     }
520     else {
521         if(*w != separator)
522             *r++ = separator;
523         while(*w != '\0')
524             *r++ = *w++;
525         if(*(r-1) != separator)
526             *r++ = separator;    
527     }
528     *r = '\0';
529
530     /*
531      * Allocate an area to convert into.  The worst case is a six to
532      * one expansion.
533      */
534     len = strlen(mglob);
535     regex = (char *)alloc(1 + len * 6 + 1 + 1 + 2 + 2);
536     r = regex;
537     nglob = stralloc(mglob);
538     g = nglob;
539
540     if((len == 1 && nglob[0] == separator) ||
541        (len == 2 && nglob[0] == '^' && nglob[1] == separator) ||
542        (len == 2 && nglob[0] == separator && nglob[1] == '$') ||
543        (len == 3 && nglob[0] == '^' && nglob[1] == separator &&
544         nglob[2] == '$')) {
545         *r++ = '^';
546         *r++ = '\\';
547         *r++ = separator;
548         *r++ = '\\';
549         *r++ = separator;
550         *r++ = '$';
551     }
552     else {
553         /*
554          * Do the conversion:
555          *
556          *  ?      -> [^\separator]
557          *  *      -> [^\separator]*
558          *  [!...] -> [^...]
559          *  **     -> .*
560          *
561          * The following are given a leading backslash to protect them
562          * unless they already have a backslash:
563          *
564          *   ( ) { } + . ^ $ |
565          *
566          * If the last
567          * non-escaped character is \ leave it to cause a syntax
568          * error when the regex is compiled.
569          */
570
571         if(*g == '^') {
572             *r++ = '^';
573             *r++ = '\\';        /* escape the separator */
574             *r++ = separator;
575             g++;
576             if(*g == separator) g++;
577         }
578         else if(*g != separator) {
579             *r++ = '\\';        /* add a leading \separator */
580             *r++ = separator;
581         }
582         last_ch = '\0';
583         for (ch = *g++; ch != '\0'; last_ch = ch, ch = *g++) {
584             next_ch = *g;
585             if (last_ch == '\\') {
586                 *r++ = (char)ch;
587                 ch = '\0';              /* so last_ch != '\\' next time */
588             } else if (last_ch == '[' && ch == '!') {
589                 *r++ = '^';
590             } else if (ch == '\\') {
591                 *r++ = (char)ch;
592             } else if (ch == '*' || ch == '?') {
593                 if(ch == '*' && next_ch == '*') {
594                     *r++ = '.';
595                     g++;
596                 }
597                 else {
598                     *r++ = '[';
599                     *r++ = '^';
600                     *r++ = '\\';
601                     *r++ = separator;
602                     *r++ = ']';
603                 }
604                 if (ch == '*') {
605                     *r++ = '*';
606                 }
607             } else if (ch == '$' && next_ch == '\0') {
608                 if(last_ch != separator) {
609                     *r++ = '\\';
610                     *r++ = separator;
611                 }
612                 *r++ = (char)ch;
613             } else if (   ch == '('
614                        || ch == ')'
615                        || ch == '{'
616                        || ch == '}'
617                        || ch == '+'
618                        || ch == '.'
619                        || ch == '^'
620                        || ch == '$'
621                        || ch == '|') {
622                 *r++ = '\\';
623                 *r++ = (char)ch;
624             } else {
625                 *r++ = (char)ch;
626             }
627         }
628         if(last_ch != '\\') {
629             if(last_ch != separator && last_ch != '$') {
630                 *r++ = '\\';
631                 *r++ = separator;               /* add a trailing \separator */
632             }
633         }
634     }
635     *r = '\0';
636
637     i = match(regex,nword);
638
639     amfree(mword);
640     amfree(mglob);
641     amfree(nword);
642     amfree(nglob);
643     amfree(regex);
644     return i;
645 }
646
647
648 int
649 match_host(
650     const char *        glob,
651     const char *        host)
652 {
653     char *lglob, *lhost;
654     char *c;
655     const char *d;
656     int i;
657
658     
659     lglob = (char *)alloc(strlen(glob)+1);
660     c = lglob, d=glob;
661     while( *d != '\0')
662         *c++ = (char)tolower(*d++);
663     *c = *d;
664
665     lhost = (char *)alloc(strlen(host)+1);
666     c = lhost, d=host;
667     while( *d != '\0')
668         *c++ = (char)tolower(*d++);
669     *c = *d;
670
671     i = match_word(lglob, lhost, (int)'.');
672     amfree(lglob);
673     amfree(lhost);
674     return i;
675 }
676
677
678 int
679 match_disk(
680     const char *        glob,
681     const char *        disk)
682 {
683     return match_word(glob, disk, '/');
684 }
685
686 static int
687 alldigits(
688     const char *str)
689 {
690     while (*str) {
691         if (!isdigit((int)*(str++)))
692             return 0;
693     }
694     return 1;
695 }
696
697 int
698 match_datestamp(
699     const char *        dateexp,
700     const char *        datestamp)
701 {
702     char *dash;
703     size_t len, len_suffix;
704     size_t len_prefix;
705     char firstdate[100], lastdate[100];
706     char mydateexp[100];
707     int match_exact;
708
709     if(strlen(dateexp) >= 100 || strlen(dateexp) < 1) {
710         goto illegal;
711     }
712    
713     /* strip and ignore an initial "^" */
714     if(dateexp[0] == '^') {
715         strncpy(mydateexp, dateexp+1, sizeof(mydateexp)-1);
716         mydateexp[sizeof(mydateexp)-1] = '\0';
717     }
718     else {
719         strncpy(mydateexp, dateexp, sizeof(mydateexp)-1);
720         mydateexp[sizeof(mydateexp)-1] = '\0';
721     }
722
723     if(mydateexp[strlen(mydateexp)-1] == '$') {
724         match_exact = 1;
725         mydateexp[strlen(mydateexp)-1] = '\0';  /* strip the trailing $ */
726     }
727     else
728         match_exact = 0;
729
730     /* a single dash represents a date range */
731     if((dash = strchr(mydateexp,'-'))) {
732         if(match_exact == 1 || strchr(dash+1, '-')) {
733             goto illegal;
734         }
735
736         /* format: XXXYYYY-ZZZZ, indicating dates XXXYYYY to XXXZZZZ */
737
738         len = (size_t)(dash - mydateexp);   /* length of XXXYYYY */
739         len_suffix = strlen(dash) - 1;  /* length of ZZZZ */
740         if (len_suffix > len) goto illegal;
741         len_prefix = len - len_suffix; /* length of XXX */
742
743         dash++;
744
745         strncpy(firstdate, mydateexp, len);
746         firstdate[len] = '\0';
747         strncpy(lastdate, mydateexp, len_prefix);
748         strncpy(&(lastdate[len_prefix]), dash, len_suffix);
749         lastdate[len] = '\0';
750         if (!alldigits(firstdate) || !alldigits(lastdate))
751             goto illegal;
752         if (strncmp(firstdate, lastdate, strlen(firstdate)) > 0)
753             goto illegal;
754         return ((strncmp(datestamp, firstdate, strlen(firstdate)) >= 0) &&
755                 (strncmp(datestamp, lastdate , strlen(lastdate))  <= 0));
756     }
757     else {
758         if (!alldigits(mydateexp))
759             goto illegal;
760         if(match_exact == 1) {
761             return (strcmp(datestamp, mydateexp) == 0);
762         }
763         else {
764             return (strncmp(datestamp, mydateexp, strlen(mydateexp)) == 0);
765         }
766     }
767 illegal:
768         error(_("Illegal datestamp expression %s"),dateexp);
769         /*NOTREACHED*/
770 }
771
772
773 int
774 match_level(
775     const char *        levelexp,
776     const char *        level)
777 {
778     char *dash;
779     long int low, hi, level_i;
780     char mylevelexp[100];
781     int match_exact;
782
783     if(strlen(levelexp) >= 100 || strlen(levelexp) < 1) {
784         error(_("Illegal level expression %s"),levelexp);
785         /*NOTREACHED*/
786     }
787    
788     if(levelexp[0] == '^') {
789         strncpy(mylevelexp, levelexp+1, strlen(levelexp)-1); 
790         mylevelexp[strlen(levelexp)-1] = '\0';
791     }
792     else {
793         strncpy(mylevelexp, levelexp, strlen(levelexp));
794         mylevelexp[strlen(levelexp)] = '\0';
795     }
796
797     if(mylevelexp[strlen(mylevelexp)-1] == '$') {
798         match_exact = 1;
799         mylevelexp[strlen(mylevelexp)-1] = '\0';
800     }
801     else
802         match_exact = 0;
803
804     if((dash = strchr(mylevelexp,'-'))) {
805         if(match_exact == 1) {
806             goto illegal;
807         }
808
809         *dash = '\0';
810         if (!alldigits(mylevelexp) || !alldigits(dash+1)) goto illegal;
811
812         errno = 0;
813         low = strtol(mylevelexp, (char **) NULL, 10);
814         if (errno) goto illegal;
815         hi = strtol(dash+1, (char **) NULL, 10);
816         if (errno) goto illegal;
817         level_i = strtol(level, (char **) NULL, 10);
818         if (errno) goto illegal;
819
820         return ((level_i >= low) && (level_i <= hi));
821     }
822     else {
823         if (!alldigits(mylevelexp)) goto illegal;
824         if(match_exact == 1) {
825             return (strcmp(level, mylevelexp) == 0);
826         }
827         else {
828             return (strncmp(level, mylevelexp, strlen(mylevelexp)) == 0);
829         }
830     }
831 illegal:
832     error(_("Illegal level expression %s"),levelexp);
833     /*NOTREACHED*/
834 }