Imported Upstream version 3.3.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 /*
40  * REGEX MATCHING FUNCTIONS
41  */
42
43 /*
44  * Define a specific type to hold error messages in case regex compile/matching
45  * fails
46  */
47
48 typedef char regex_errbuf[STR_SIZE];
49
50 /*
51  * Validate one regular expression. If the regex is invalid, copy the error
52  * message into the supplied regex_errbuf pointer. Also, we want to know whether
53  * flags should include REG_NEWLINE (See regcomp(3) for details). Since this is
54  * the more frequent case, add REG_NEWLINE to the default flags, and remove it
55  * only if match_newline is set to FALSE.
56  */
57
58 static gboolean do_validate_regex(const char *str, regex_t *regex,
59         regex_errbuf *errbuf, gboolean match_newline)
60 {
61         int flags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
62         int result;
63
64         if (!match_newline)
65                 CLR(flags, REG_NEWLINE);
66
67         result = regcomp(regex, str, flags);
68
69         if (!result)
70                 return TRUE;
71
72         regerror(result, regex, *errbuf, SIZEOF(*errbuf));
73         return FALSE;
74 }
75
76 /*
77  * See if a string matches a regular expression. Return one of MATCH_* defined
78  * below. If, for some reason, regexec() returns something other than not 0 or
79  * REG_NOMATCH, return MATCH_ERROR and print the error message in the supplied
80  * regex_errbuf.
81  */
82
83 #define MATCH_OK (1)
84 #define MATCH_NONE (0)
85 #define MATCH_ERROR (-1)
86
87 static int try_match(regex_t *regex, const char *str,
88     regex_errbuf *errbuf)
89 {
90     int result = regexec(regex, str, 0, 0, 0);
91
92     switch(result) {
93         case 0:
94             return MATCH_OK;
95         case REG_NOMATCH:
96             return MATCH_NONE;
97         /* Fall through: something went really wrong */
98     }
99
100     regerror(result, regex, *errbuf, SIZEOF(*errbuf));
101     return MATCH_ERROR;
102 }
103
104 char *
105 validate_regexp(
106     const char *        regex)
107 {
108     regex_t regc;
109     static regex_errbuf errmsg;
110     gboolean valid;
111
112     valid = do_validate_regex(regex, &regc, &errmsg, TRUE);
113
114     regfree(&regc);
115     return (valid) ? NULL : errmsg;
116 }
117
118 char *
119 clean_regex(
120     const char *        str,
121     gboolean            anchor)
122 {
123     char *result;
124     int j;
125     size_t i;
126     result = alloc(2*strlen(str)+3);
127
128     j = 0;
129     if (anchor)
130         result[j++] = '^';
131     for(i=0;i<strlen(str);i++) {
132         if(!isalnum((int)str[i]))
133             result[j++]='\\';
134         result[j++]=str[i];
135     }
136     if (anchor)
137         result[j++] = '$';
138     result[j] = '\0';
139     return result;
140 }
141
142 /*
143  * Check whether a given character should be escaped (that is, prepended with a
144  * backslash), EXCEPT for one character.
145  */
146
147 static gboolean should_be_escaped_except(char c, char not_this_one)
148 {
149     if (c == not_this_one)
150         return FALSE;
151
152     switch (c) {
153         case '\\':
154         case '^':
155         case '$':
156         case '?':
157         case '*':
158         case '[':
159         case ']':
160         case '.':
161         case '/':
162             return TRUE;
163     }
164
165     return FALSE;
166 }
167
168 /*
169  * Take a disk/host expression and turn it into a full-blown amglob (with
170  * start and end anchors) following rules in amanda-match(7). The not_this_one
171  * argument represents a character which is NOT meant to be special in this
172  * case: '/' for disks and '.' for hosts.
173  */
174
175 static char *full_amglob_from_expression(const char *str, char not_this_one)
176 {
177     const char *src;
178     char *result, *dst;
179
180     result = alloc(2 * strlen(str) + 3);
181     dst = result;
182
183     *(dst++) = '^';
184
185     for (src = str; *src; src++) {
186         if (should_be_escaped_except(*src, not_this_one))
187             *(dst++) = '\\';
188         *(dst++) = *src;
189     }
190
191     *(dst++) = '$';
192     *dst = '\0';
193     return result;
194 }
195
196 char *
197 make_exact_host_expression(
198     const char *        host)
199 {
200     return full_amglob_from_expression(host, '.');
201 }
202
203 char *
204 make_exact_disk_expression(
205     const char *        disk)
206 {
207     return full_amglob_from_expression(disk, '/');
208 }
209
210 int do_match(const char *regex, const char *str, gboolean match_newline)
211 {
212     regex_t regc;
213     int result;
214     regex_errbuf errmsg;
215     gboolean ok;
216
217     ok = do_validate_regex(regex, &regc, &errmsg, match_newline);
218
219     if (!ok)
220         error(_("regex \"%s\": %s"), regex, errmsg);
221         /*NOTREACHED*/
222
223     result = try_match(&regc, str, &errmsg);
224
225     if (result == MATCH_ERROR)
226         error(_("regex \"%s\": %s"), regex, errmsg);
227         /*NOTREACHED*/
228
229     regfree(&regc);
230
231     return result;
232 }
233
234 char *
235 validate_glob(
236     const char *        glob)
237 {
238     char *regex, *ret = NULL;
239     regex_t regc;
240     static regex_errbuf errmsg;
241
242     regex = glob_to_regex(glob);
243
244     if (!do_validate_regex(regex, &regc, &errmsg, TRUE))
245         ret = errmsg;
246
247     regfree(&regc);
248     amfree(regex);
249     return ret;
250 }
251
252 int
253 match_glob(
254     const char *        glob,
255     const char *        str)
256 {
257     char *regex;
258     regex_t regc;
259     int result;
260     regex_errbuf errmsg;
261     gboolean ok;
262
263     regex = glob_to_regex(glob);
264     ok = do_validate_regex(regex, &regc, &errmsg, TRUE);
265
266     if (!ok)
267         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
268         /*NOTREACHED*/
269
270     result = try_match(&regc, str, &errmsg);
271
272     if (result == MATCH_ERROR)
273         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
274         /*NOTREACHED*/
275
276     regfree(&regc);
277     amfree(regex);
278
279     return result;
280 }
281
282 /*
283  * Macro to tell whether a character is a regex metacharacter. Note that '*'
284  * and '?' are NOT included: they are themselves special in globs.
285  */
286
287 #define IS_REGEX_META(c) ( \
288     (c) == '.' || (c) == '(' || (c) == ')' || (c) == '{' || (c) == '}' || \
289     (c) == '+' || (c) == '^' || (c) == '$' || (c) == '|' \
290 )
291
292 /*
293  * EXPANDING A MATCH TO A REGEX (as per amanda-match(7))
294  *
295  * The function at the code of this operation is amglob_to_regex(). It
296  * takes three arguments: the string to convert, a substitution table and a
297  * worst-case expansion.
298  *
299  * The substitution table, defined right below, is used to replace particular
300  * string positions and/or characters. Its fields are:
301  * - begin: what the beginnin of the string should be replaced with;
302  * - end: what the end of the string should be replaced with;
303  * - question_mark: what the question mark ('?') should be replaced with;
304  * - star: what the star ('*') should be replaced with;
305  * - double_star: what two consecutive stars should be replaced with.
306  *
307  * Note that apart from double_star, ALL OTHER FIELDS MUST NOT BE NULL
308  */
309
310 struct subst_table {
311     const char *begin;
312     const char *end;
313     const char *question_mark;
314     const char *star;
315     const char *double_star;
316 };
317
318 static char *amglob_to_regex(const char *str, struct subst_table *table,
319     size_t worst_case)
320 {
321     const char *src;
322     char *result, *dst;
323     char c;
324
325     /*
326      * There are two particular cases when building a regex out of a glob:
327      * character classes (anything inside [...] or [!...] and quotes (anything
328      * preceded by a backslash). We start with none being true.
329      */
330
331     gboolean in_character_class = FALSE, in_quote = FALSE;
332
333     /*
334      * Allocate enough space for our string. At worst, the allocated space is
335      * the length of the following:
336      * - beginning of regex;
337      * - size of original string multiplied by worst-case expansion;
338      * - end of regex;
339      * - final 0.
340      */
341
342     result = alloc(strlen(table->begin) + strlen(str) * worst_case
343         + strlen(table->end) + 1);
344
345     /*
346      * Start by copying the beginning of the regex...
347      */
348
349     dst = g_stpcpy(result, table->begin);
350
351     /*
352      * ... Now to the meat of it.
353      */
354
355     for (src = str; *src; src++) {
356         c = *src;
357
358         /*
359          * First, check that we're in a character class: each and every
360          * character can be copied as is. We only need to be careful is the
361          * character is a closing bracket: it will end the character class IF
362          * AND ONLY IF it is not preceded by a backslash.
363          */
364
365         if (in_character_class) {
366             in_character_class = ((c != ']') || (*(src - 1) == '\\'));
367             goto straight_copy;
368         }
369
370         /*
371          * Are we in a quote? If yes, it is really simple: copy the current
372          * character, close the quote, the end.
373          */
374
375         if (in_quote) {
376             in_quote = FALSE;
377             goto straight_copy;
378         }
379
380         /*
381          * The only thing left to handle now is the "normal" case: we are not in
382          * a character class nor in a quote.
383          */
384
385         if (c == '\\') {
386             /*
387              * Backslash: append it, and open a new quote.
388              */
389             in_quote = TRUE;
390             goto straight_copy;
391         } else if (c == '[') {
392             /*
393              * Opening bracket: the beginning of a character class.
394              *
395              * Look ahead the next character: if it's an exclamation mark, then
396              * this is a complemented character class; append a caret to make
397              * the result string regex-friendly, and forward one character in
398              * advance.
399              */
400             *dst++ = c;
401             in_character_class = TRUE;
402             if (*(src + 1) == '!') {
403                 *dst++ = '^';
404                 src++;
405             }
406         } else if (IS_REGEX_META(c)) {
407             /*
408              * Regex metacharacter (except for ? and *, see below): append a
409              * backslash, and then the character itself.
410              */
411             *dst++ = '\\';
412             goto straight_copy;
413         } else if (c == '?')
414             /*
415              * Question mark: take the subsitution string out of our subst_table
416              * and append it to the string.
417              */
418             dst = g_stpcpy(dst, table->question_mark);
419         else if (c == '*') {
420             /*
421              * Star: append the subsitution string found in our subst_table.
422              * However, look forward the next character: if it's yet another
423              * star, then see if there is a substitution string for the double
424              * star and append this one instead.
425              *
426              * FIXME: this means that two consecutive stars in a glob string
427              * where there is no substition for double_star can lead to
428              * exponential regex execution time: consider [^/]*[^/]*.
429              */
430             const char *p = table->star;
431             if (*(src + 1) == '*' && table->double_star) {
432                 src++;
433                 p = table->double_star;
434             }
435             dst = g_stpcpy(dst, p);
436         } else {
437             /*
438              * Any other character: append each time.
439              */
440 straight_copy:
441             *dst++ = c;
442         }
443     }
444
445     /*
446      * Done, now append the end, ONLY if we are not in a quote - a lone
447      * backslash at the end of a glob is illegal, just leave it as it, it will
448      * make the regex compile fail.
449      */
450
451     if (!in_quote)
452         dst = g_stpcpy(dst, table->end);
453     /*
454      * Finalize, return.
455      */
456
457     *dst = '\0';
458     return result;
459 }
460
461 static struct subst_table glob_subst_stable = {
462     "^", /* begin */
463     "$", /* end */
464     "[^/]", /* question_mark */
465     "[^/]*", /* star */
466     NULL /* double_star */
467 };
468
469 static size_t glob_worst_case = 5; /* star */
470
471 char *
472 glob_to_regex(
473     const char *        glob)
474 {
475     return amglob_to_regex(glob, &glob_subst_stable, glob_worst_case);
476 }
477
478 int
479 match_tar(
480     const char *        glob,
481     const char *        str)
482 {
483     char *regex;
484     regex_t regc;
485     int result;
486     regex_errbuf errmsg;
487     gboolean ok;
488
489     regex = tar_to_regex(glob);
490     ok = do_validate_regex(regex, &regc, &errmsg, TRUE);
491
492     if (!ok)
493         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
494         /*NOTREACHED*/
495
496     result = try_match(&regc, str, &errmsg);
497
498     if (result == MATCH_ERROR)
499         error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
500         /*NOTREACHED*/
501
502     regfree(&regc);
503     amfree(regex);
504
505     return result;
506 }
507
508 static struct subst_table tar_subst_stable = {
509     "(^|/)", /* begin */
510     "($|/)", /* end */
511     "[^/]", /* question_mark */
512     ".*", /* star */
513     NULL /* double_star */
514 };
515
516 static size_t tar_worst_case = 5; /* begin or end */
517
518 static char *
519 tar_to_regex(
520     const char *        glob)
521 {
522     return amglob_to_regex(glob, &tar_subst_stable, tar_worst_case);
523 }
524
525 /*
526  * Two utility functions used by match_disk() below: they are used to convert a
527  * disk and glob from Windows expressed paths (backslashes) into Unix paths
528  * (slashes).
529  *
530  * Note: the resulting string is dynamically allocated, it is up to the caller
531  * to free it.
532  *
533  * Note 2: UNC in convert_unc_to_unix stands for Uniform Naming Convention.
534  */
535
536 static char *convert_unc_to_unix(const char *unc)
537 {
538     const char *src;
539     char *result, *dst;
540     result = alloc(strlen(unc) + 1);
541     dst = result;
542
543     for (src = unc; *src; src++)
544         *(dst++) = (*src == '\\') ? '/' : *src;
545
546     *dst = '\0';
547     return result;
548 }
549
550 static char *convert_winglob_to_unix(const char *glob)
551 {
552     const char *src;
553     char *result, *dst;
554     result = alloc(strlen(glob) + 1);
555     dst = result;
556
557     for (src = glob; *src; src++) {
558         if (*src == '\\' && *(src + 1) == '\\') {
559             *(dst++) = '/';
560             src++;
561             continue;
562         }
563         *(dst++) = *src;
564     }
565     *dst = '\0';
566     return result;
567 }
568
569 /*
570  * Check whether a glob passed as an argument to match_word() only looks for the
571  * separator
572  */
573
574 static gboolean glob_is_separator_only(const char *glob, char sep) {
575     size_t len = strlen(glob);
576     const char len2_1[] = { '^', sep , 0 }, len2_2[] = { sep, '$', 0 },
577         len3[] = { '^', sep, '$', 0 };
578
579     switch (len) {
580         case 1:
581             return (*glob == sep);
582         case 2:
583             return !(strcmp(glob, len2_1) && strcmp(glob, len2_2));
584         case 3:
585             return !strcmp(glob, len3);
586         default:
587             return FALSE;
588     }
589 }
590
591 static int
592 match_word(
593     const char *        glob,
594     const char *        word,
595     const char          separator)
596 {
597     char *regex;
598     char *dst;
599     size_t  len;
600     size_t  lenword;
601     char *nword;
602     char *nglob;
603     const char *src;
604     int ret;
605
606     lenword = strlen(word);
607     nword = (char *)alloc(lenword + 3);
608
609     dst = nword;
610     src = word;
611     if(lenword == 1 && *src == separator) {
612         *dst++ = separator;
613         *dst++ = separator;
614     }
615     else {
616         if(*src != separator)
617             *dst++ = separator;
618         while(*src != '\0')
619             *dst++ = *src++;
620         if(*(dst-1) != separator)
621             *dst++ = separator;
622     }
623     *dst = '\0';
624
625     len = strlen(glob);
626     nglob = stralloc(glob);
627
628     if(glob_is_separator_only(nglob, separator)) {
629         regex = alloc(7); /* Length of what is written below plus '\0' */
630         dst = regex;
631         *dst++ = '^';
632         *dst++ = '\\';
633         *dst++ = separator;
634         *dst++ = '\\';
635         *dst++ = separator;
636         *dst++ = '$';
637         *dst = '\0';
638     } else {
639         /*
640          * Unlike what happens for tar and disk expressions, here the
641          * substitution table needs to be dynamically allocated. When we enter
642          * here, we know what the expansions will be for the question mark, the
643          * star and the double star, and also the worst case expansion. We
644          * calculate the begin and end expansions below.
645          */
646
647 #define MATCHWORD_STAR_EXPANSION(c) (const char []) { \
648     '[', '^', (c), ']', '*', 0 \
649 }
650 #define MATCHWORD_QUESTIONMARK_EXPANSION(c) (const char []) { \
651     '[', '^', (c), ']', 0 \
652 }
653 #define MATCHWORD_DOUBLESTAR_EXPANSION ".*"
654
655         struct subst_table table;
656         size_t worst_case = 5;
657         const char *begin, *end;
658         char *p, *g = nglob;
659
660         /*
661          * Calculate the beginning of the regex:
662          * - by default, it is an unanchored separator;
663          * - if the glob begins with a caret, make that an anchored separator,
664          *   and increment g appropriately;
665          * - if it begins with a separator, make it the empty string.
666          */
667
668         p = nglob;
669
670 #define REGEX_BEGIN_FULL(c) (const char[]) { '^', '\\', (c), 0 }
671 #define REGEX_BEGIN_NOANCHOR(c) (const char[]) { '\\', (c), 0 }
672 #define REGEX_BEGIN_ANCHORONLY "^" /* Unused, but defined for consistency */
673 #define REGEX_BEGIN_EMPTY ""
674
675         begin = REGEX_BEGIN_NOANCHOR(separator);
676
677         if (*p == '^') {
678             begin = REGEX_BEGIN_FULL(separator);
679             p++, g++;
680             if (*p == separator)
681                 g++;
682         } else if (*p == separator)
683             begin = REGEX_BEGIN_EMPTY;
684
685         /*
686          * Calculate the end of the regex:
687          * - an unanchored separator by default;
688          * - if the last character is a backslash or the separator itself, it
689          *   should be the empty string;
690          * - if it is a dollar sign, overwrite it with 0 and look at the
691          *   character before it: if it is the separator, only anchor at the
692          *   end, otherwise, add a separator before the anchor.
693          */
694
695         p = &(nglob[strlen(nglob) - 1]);
696
697 #define REGEX_END_FULL(c) (const char[]) { '\\', (c), '$', 0 }
698 #define REGEX_END_NOANCHOR(c) REGEX_BEGIN_NOANCHOR(c)
699 #define REGEX_END_ANCHORONLY "$"
700 #define REGEX_END_EMPTY REGEX_BEGIN_EMPTY
701
702         end = REGEX_END_NOANCHOR(separator);
703
704         if (*p == '\\' || *p == separator)
705             end = REGEX_END_EMPTY;
706         else if (*p == '$') {
707             char prev = *(p - 1);
708             *p = '\0';
709             if (prev == separator)
710                 end = REGEX_END_ANCHORONLY;
711             else
712                 end = REGEX_END_FULL(separator);
713         }
714
715         /*
716          * Fill in our substitution table and generate the regex
717          */
718
719         table.begin = begin;
720         table.end = end;
721         table.question_mark = MATCHWORD_QUESTIONMARK_EXPANSION(separator);
722         table.star = MATCHWORD_STAR_EXPANSION(separator);
723         table.double_star = MATCHWORD_DOUBLESTAR_EXPANSION;
724
725         regex = amglob_to_regex(g, &table, worst_case);
726     }
727
728     ret = do_match(regex, nword, TRUE);
729
730     amfree(nword);
731     amfree(nglob);
732     amfree(regex);
733
734     return ret;
735 }
736
737
738 int
739 match_host(
740     const char *        glob,
741     const char *        host)
742 {
743     char *lglob, *lhost;
744     int ret;
745
746     
747     lglob = g_ascii_strdown(glob, -1);
748     lhost = g_ascii_strdown(host, -1);
749
750     ret = match_word(lglob, lhost, '.');
751
752     amfree(lglob);
753     amfree(lhost);
754     return ret;
755 }
756
757
758 int
759 match_disk(
760     const char *        glob,
761     const char *        disk)
762 {
763     char *glob2 = NULL, *disk2 = NULL;
764     const char *g = glob, *d = disk;
765     int result;
766
767     /*
768      * Check whether our disk potentially refers to a Windows share (the first
769      * two characters are '\' and there is no / in the word at all): if yes,
770      * convert all double backslashes to slashes in the glob, and simple
771      * backslashes into slashes in the disk, and pass these new strings as
772      * arguments instead of the originals.
773      */
774     gboolean windows_share = !(strncmp(disk, "\\\\", 2) || strchr(disk, '/'));
775
776     if (windows_share) {
777         glob2 = convert_winglob_to_unix(glob);
778         disk2 = convert_unc_to_unix(disk);
779         g = (const char *) glob2;
780         d = (const char *) disk2;
781     }
782
783     result = match_word(g, d, '/');
784
785     /*
786      * We can amfree(NULL), so this is "safe"
787      */
788     amfree(glob2);
789     amfree(disk2);
790
791     return result;
792 }
793
794 static int
795 alldigits(
796     const char *str)
797 {
798     while (*str) {
799         if (!isdigit((int)*(str++)))
800             return 0;
801     }
802     return 1;
803 }
804
805 int
806 match_datestamp(
807     const char *        dateexp,
808     const char *        datestamp)
809 {
810     char *dash;
811     size_t len, len_suffix;
812     size_t len_prefix;
813     char firstdate[100], lastdate[100];
814     char mydateexp[100];
815     int match_exact;
816
817     if(strlen(dateexp) >= 100 || strlen(dateexp) < 1) {
818         goto illegal;
819     }
820    
821     /* strip and ignore an initial "^" */
822     if(dateexp[0] == '^') {
823         strncpy(mydateexp, dateexp+1, sizeof(mydateexp)-1);
824         mydateexp[sizeof(mydateexp)-1] = '\0';
825     }
826     else {
827         strncpy(mydateexp, dateexp, sizeof(mydateexp)-1);
828         mydateexp[sizeof(mydateexp)-1] = '\0';
829     }
830
831     if(mydateexp[strlen(mydateexp)-1] == '$') {
832         match_exact = 1;
833         mydateexp[strlen(mydateexp)-1] = '\0';  /* strip the trailing $ */
834     }
835     else
836         match_exact = 0;
837
838     /* a single dash represents a date range */
839     if((dash = strchr(mydateexp,'-'))) {
840         if(match_exact == 1 || strchr(dash+1, '-')) {
841             goto illegal;
842         }
843
844         /* format: XXXYYYY-ZZZZ, indicating dates XXXYYYY to XXXZZZZ */
845
846         len = (size_t)(dash - mydateexp);   /* length of XXXYYYY */
847         len_suffix = strlen(dash) - 1;  /* length of ZZZZ */
848         if (len_suffix > len) goto illegal;
849         len_prefix = len - len_suffix; /* length of XXX */
850
851         dash++;
852
853         strncpy(firstdate, mydateexp, len);
854         firstdate[len] = '\0';
855         strncpy(lastdate, mydateexp, len_prefix);
856         strncpy(&(lastdate[len_prefix]), dash, len_suffix);
857         lastdate[len] = '\0';
858         if (!alldigits(firstdate) || !alldigits(lastdate))
859             goto illegal;
860         if (strncmp(firstdate, lastdate, strlen(firstdate)) > 0)
861             goto illegal;
862         return ((strncmp(datestamp, firstdate, strlen(firstdate)) >= 0) &&
863                 (strncmp(datestamp, lastdate , strlen(lastdate))  <= 0));
864     }
865     else {
866         if (!alldigits(mydateexp))
867             goto illegal;
868         if(match_exact == 1) {
869             return (strcmp(datestamp, mydateexp) == 0);
870         }
871         else {
872             return (strncmp(datestamp, mydateexp, strlen(mydateexp)) == 0);
873         }
874     }
875 illegal:
876         error(_("Illegal datestamp expression %s"),dateexp);
877         /*NOTREACHED*/
878 }
879
880
881 int
882 match_level(
883     const char *        levelexp,
884     const char *        level)
885 {
886     char *dash;
887     long int low, hi, level_i;
888     char mylevelexp[100];
889     int match_exact;
890
891     if(strlen(levelexp) >= 100 || strlen(levelexp) < 1) {
892         error(_("Illegal level expression %s"),levelexp);
893         /*NOTREACHED*/
894     }
895    
896     if(levelexp[0] == '^') {
897         strncpy(mylevelexp, levelexp+1, strlen(levelexp)-1); 
898         mylevelexp[strlen(levelexp)-1] = '\0';
899     }
900     else {
901         strncpy(mylevelexp, levelexp, strlen(levelexp));
902         mylevelexp[strlen(levelexp)] = '\0';
903     }
904
905     if(mylevelexp[strlen(mylevelexp)-1] == '$') {
906         match_exact = 1;
907         mylevelexp[strlen(mylevelexp)-1] = '\0';
908     }
909     else
910         match_exact = 0;
911
912     if((dash = strchr(mylevelexp,'-'))) {
913         if(match_exact == 1) {
914             goto illegal;
915         }
916
917         *dash = '\0';
918         if (!alldigits(mylevelexp) || !alldigits(dash+1)) goto illegal;
919
920         errno = 0;
921         low = strtol(mylevelexp, (char **) NULL, 10);
922         if (errno) goto illegal;
923         hi = strtol(dash+1, (char **) NULL, 10);
924         if (errno) goto illegal;
925         level_i = strtol(level, (char **) NULL, 10);
926         if (errno) goto illegal;
927
928         return ((level_i >= low) && (level_i <= hi));
929     }
930     else {
931         if (!alldigits(mylevelexp)) goto illegal;
932         if(match_exact == 1) {
933             return (strcmp(level, mylevelexp) == 0);
934         }
935         else {
936             return (strncmp(level, mylevelexp, strlen(mylevelexp)) == 0);
937         }
938     }
939 illegal:
940     error(_("Illegal level expression %s"),levelexp);
941     /*NOTREACHED*/
942 }