Exemple #1
0
/*
 * Parse a line 'src' from an /etc/krb.equiv file.
 * Sets the buffer pointed to by 'principal' to be the kerberos
 * identity and sets the buffer pointed to by 'localuser' to
 * be the local user.  Both buffers must be of size one larger than
 * MAX_K_NAME_SZ.  Returns 1 on success, 0 on failure.
 */
static int parse_krbequiv_line(const char *src,
                               char *principal,
                               char *localuser)
{
    int i;

    while (Uisspace(*src)) src++;
    if (!*src) return 0;

    for (i = 0; *src && !Uisspace(*src); i++) {
        if (i >= MAX_K_NAME_SZ) return 0;
        *principal++ = *src++;
    }
    *principal = 0;

    if (!Uisspace(*src)) return 0; /* Need at least one separator */
    while (Uisspace(*src)) src++;
    if (!*src) return 0;

    for (i = 0; *src && !Uisspace(*src); i++) {
        if (i >= MAX_K_NAME_SZ) return 0;
        *localuser++ = *src++;
    }
    *localuser = 0;
    return 1;
}
Exemple #2
0
/*
 * Given a line buffer @buf, find the IMAP response code named by @code,
 * isolate it and return the start of it, or NULL if not found.
 */
static char *find_response_code(char *buf, const char *code)
{
    char *start;
    char *end;
    int codelen = strlen(code);

    /* Try to find the first response code */
    start = strchr(buf, '[');
    if (!start)
	return NULL;	/* no response codes */

    start++;
    for (;;) {
	while (*start && Uisspace(*start))
	    start++;
	if (!*start)
	    break;	/* nothing to see here */
	/* response codes are delineated by [] */
	if (!(end = strchr(start, ']')))
	    break;	/* unbalanced [response code] */
	if (!strncasecmp(start, code, codelen) && Uisspace(start[codelen])) {
	    *end = '\0';
	    start += codelen+1;
	    return start;
	} else {
	    start = end+1;
	}
    }

    return NULL;
}
Exemple #3
0
int is_local_realm(const char *realm)
{
    const char *val = localrealms;

    if(!val || !realm) return 0;

    while (*val) {
        char buf[1024];
        size_t len;
        char *p;

        for (p = (char *) val; *p && !Uisspace(*p); p++);
        len = p-val;
        if(len >= sizeof(buf))
            len = sizeof(buf) - 1;
        memcpy(buf, val, len);
        buf[len] = '\0';

        if (!strcasecmp(realm,buf)) {
            return 1;
        }
        val = p;
        while (*val && Uisspace(*val)) val++;
    }

    return 0;
}
Exemple #4
0
static void split_args(struct entry *e, char *buf)
{
    char *p = buf, *q;
    char *key, *value;

    for (;;) {
	/* skip whitespace before arg */
	while (Uisspace(*p))
	    p++;
	if (!*p)
	    return;
	key = p;

	/* parse the key */
	for (q = p ; Uisalnum(*q) ; q++)
	    ;
	if (*q != '=')
	    fatalf(EX_CONFIG, "configuration file %s: "
			      "bad character '%c' in argument on line %d",
			      MASTER_CONFIG_FILENAME, *q, e->lineno);
	*q++ = '\0';

	/* parse the value */
	if (*q == '"') {
	    /* quoted string */
	    value = ++q;
	    q = strchr(q, '"');
	    if (!q)
		fatalf(EX_CONFIG, "configuration file %s: missing \" on line %d",
			MASTER_CONFIG_FILENAME, e->lineno);
	    *q++ = '\0';
	}
	else {
	    /* simple word */
	    value = q;
	    while (*q && !Uisspace(*q))
		q++;
	    if (*q)
		*q++ = '\0';
	}

	if (e->nargs == MAXARGS)
		fatalf(EX_CONFIG, "configuration file %s: too many arguments on line %d",
			MASTER_CONFIG_FILENAME, e->lineno);
	e->args[e->nargs].key = key;
	e->args[e->nargs].value = value;
	e->nargs++;
	p = q;
    }
}
Exemple #5
0
static void process_section(FILE *f, int *lnptr, 
			    masterconf_process *func, void *rock)
{
    struct entry e;
    char buf[4096];
    int lineno = *lnptr;

    while (fgets(buf, sizeof(buf), f)) {
	char *p, *q;

	lineno++;

	/* remove EOL character */
	if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
	/* remove starting whitespace */
	for (p = buf; *p && Uisspace(*p); p++);

	/* remove comments */
	q = strchr(p, '#');
	if (q) *q = '\0';

	/* skip empty lines or all comment lines */
	if (!*p) continue;
	if (*p == '}') break;

	for (q = p; Uisalnum(*q); q++) ;
	if (*q) {
	    if (q > p && !Uisspace(*q))
		fatalf(EX_CONFIG, "configuration file %s: "
				  "bad character '%c' in name on line %d",
				  MASTER_CONFIG_FILENAME, *q, lineno);
	    *q++ = '\0';
	}

	if (q - p > 0) {
	    /* there's a value on this line */
	    memset(&e, 0, sizeof(e));
	    e.lineno = lineno;
	    split_args(&e, q);
	    func(p, &e, rock);
	}

	/* end of section? */
	if (strchr(q, '}')) break;
    }

    *lnptr = lineno;
}
Exemple #6
0
/*
 * Parse a source route (at-domain-list)
 */
static int parseaddr_route(char **inp, char **routep)
{
    int c;
    char *src = *inp;
    char *dst;

    SKIPWHITESPACE(src);

    *routep = dst = src;

    for (;;) {
        c = *src++;
	if (Uisalnum(c) || c == '-' || c == '[' || c == ']' ||
	    c == ',' || c == '@') {
	    *dst++ = c;
	}
	else if (c == '.') {
	    if (dst > *routep && dst[-1] != '.') *dst++ = c;
	}
	else if (Uisspace(c) || c == '(') {
	    src--;
	    SKIPWHITESPACE(src);
	}
	else {
	    while (dst > *routep &&
		   (dst[-1] == '.' || dst[-1] == ',' || dst[-1] == '@')) dst--;
	    *dst = '\0';
	    *inp = src;
	    return c;
	}
    }
}
Exemple #7
0
/*
 * Parse an RFC 822 "phrase", stopping at 'specials'
 */
static int parseaddr_phrase(char **inp, char **phrasep, const char *specials)
{
    int c;
    char *src = *inp;
    char *dst;

    SKIPWHITESPACE(src);

    *phrasep = dst = src;

    for (;;) {
        c = *src++;
	if (c == '"') {
	    while ((c = *src)) {
		src++;
		if (c == '\r' && *src == '\n') {
		    /* CR+LF combination */
		    src++;
		    if (*src == ' ' || *src == '\t') {
			/* CR+LF+WSP - folded header field,
			 * unfold it by skipping ONLY the CR+LF */
			continue;
		    }
		    /* otherwise we have CR+LF at the end of a header
		     * field, which means we have an unbalanced " */
		    goto fail;
		}
		if (c == '\r' || c == '\n') goto fail;	/* invalid chars */
		if (c == '"') break;	    /* end of quoted string */
		if (c == '\\') {
		    if (!(c = *src)) goto fail;
		    src++;
		}
		*dst++ = c;
	    }
	    if (c != '"') goto fail;	    /* unbalanced " */
	}
	else if (Uisspace(c) || c == '(') {
	    src--;
	    SKIPWHITESPACE(src);
	    *dst++ = ' ';
	}
	else if (!c || strchr(specials, c)) {
	    if (dst > *phrasep && dst[-1] == ' ') dst--;
	    *dst = '\0';
	    *inp = src;
	    return c;
	}
	else {
	    *dst++ = c;
	}
    }

fail:
    /* simulate end-of-string */
    *phrasep = "";
    return 0;
}
Exemple #8
0
/*
 * Parse a domain.  If 'commentp' is non-nil, parses any trailing comment.
 * If the domain is invalid, set invalid to non-zero.
 */
static int parseaddr_domain(char **inp, char **domainp, char **commentp, int *invalid)
{
    int c;
    char *src = *inp;
    char *dst;
    char *cdst;
    int comment;

    if (commentp) *commentp = 0;
    SKIPWHITESPACE(src);

    *domainp = dst = src;

    for (;;) {
        c = *src++;
        if (Uisalnum(c) || c == '-' || c == '[' || c == ']' || c == ':') {
            *dst++ = c;
            if (commentp) *commentp = 0;
        }
        else if (c == '.') {
            if (dst > *domainp && dst[-1] != '.') *dst++ = c;
            if (commentp) *commentp = 0;
        }
        else if (c == '(') {
            if (commentp) {
                *commentp = cdst = src;
                comment = 1;
                while (comment && (c = *src)) {
                    src++;
                    if (c == '(') comment++;
                    else if (c == ')') comment--;
                    else if (c == '\\' && (c = *src)) src++;

                    if (comment) *cdst++ = c;
                }
                *cdst = '\0';
            }
            else {
                src--;
                SKIPWHITESPACE(src);
            }
        }
        else if (c == '@') {
            /* This domain name is garbage. Continue eating up the characters
             * until we get to a sane state. */
            *invalid = 1;
            *dst++ = c;
            if (commentp) *commentp = 0;
        }
        else if (!Uisspace(c)) {
            if (dst > *domainp && dst[-1] == '.') dst--;
            *dst = '\0';
            *inp = src;
            return c;
        }
    }
}
Exemple #9
0
/*
 * Parse a word from the string starting at the pointer pointed to by 's'.
 * Places a pointer to the parsed word in the pointer at 'retval',
 * returns the character following the word, and modifies the pointer at
 * 's' to point after the returned character.  Modifies the input buffer.
 */
EXPORTED int imparse_word(char **s, char **retval)
{
    int c;

    *retval = *s;
    for (;;) {
        c = *(*s)++;
        if (!c || Uisspace(c) || c == '(' || c == ')' || c == '\"') {
            (*s)[-1] = '\0';
            return c;
        }
    }
}
Exemple #10
0
/*
 * Parse a domain.  If 'commentp' is non-nil, parses any trailing comment
 */
static int parseaddr_domain(char **inp, char **domainp, char **commentp)
{
    int c;
    char *src = *inp;
    char *dst;
    char *cdst;
    int comment;

    if (commentp) *commentp = 0;
    SKIPWHITESPACE(src);

    *domainp = dst = src;

    for (;;) {
        c = *src++;
	if (Uisalnum(c) || c == '-' || c == '[' || c == ']' || c == ':') {
	    *dst++ = c;
	    if (commentp) *commentp = 0;
	}
	else if (c == '.') {
	    if (dst > *domainp && dst[-1] != '.') *dst++ = c;
	    if (commentp) *commentp = 0;
	}
	else if (c == '(') {
	    if (commentp) {
		*commentp = cdst = src;
		comment = 1;
		while (comment && (c = *src)) {
		    src++;
		    if (c == '(') comment++;
		    else if (c == ')') comment--;
		    else if (c == '\\' && (c = *src)) src++;

		    if (comment) *cdst++ = c;
		}
		*cdst = '\0';
	    }
	    else {
		src--;
		SKIPWHITESPACE(src);
	    }
	}
	else if (!Uisspace(c)) {
	    if (dst > *domainp && dst[-1] == '.') dst--;
	    *dst = '\0';
	    *inp = src;
	    return c;
	}
    }
}
Exemple #11
0
/*
 * Skip RFC822 FWS = Folding White Space.  This is the white
 * space that can be inserted harmlessly into structured
 * RFC822 headers, including splitting them over multiple lines.
 *
 * Note that RFC822 isn't entirely clear about whether such
 * space may be present in date-times, but it's successor
 * RFC2822 is quite clear and explicit.  Note also that
 * neither RFC allows for (comments) inside a date-time,
 * so we don't attempt to handle that here.
 */
static const char *skip_fws(const char *p)
{
    if (!p)
        return NULL;
    while (*p && Uisspace(*p)) {
        /* check for end of an RFC822 header line */
        if (*p == '\n') {
            p++;
            if (*p != ' ' && *p != '\t')
                return NULL;
        }
        else
            p++;
    }
    return (*p ? p : NULL);
}
Exemple #12
0
static void parse_data(const char *data, int datalen, struct seendata *sd)
{
    /* remember that 'data' may not be null terminated ! */
    const char *dend = data + datalen;
    char *p;
    int uidlen;
    int version;

    memset(sd, 0, sizeof(struct seendata));

    version = strtol(data, &p, 10); data = p;
    assert(version == SEEN_VERSION);

    sd->lastread = strtol(data, &p, 10); data = p;
    sd->lastuid = strtoll(data, &p, 10); data = p;
    sd->lastchange = strtol(data, &p, 10); data = p;
    while (p < dend && Uisspace(*p)) p++; data = p;
    uidlen = dend - data;
    sd->seenuids = xmalloc(uidlen + 1);
    memcpy(sd->seenuids, data, uidlen);
    sd->seenuids[uidlen] = '\0';
}
Exemple #13
0
char *find_free_server(void)
{
    const char *servers = config_getstring(IMAPOPT_SERVERLIST);
    unsigned long max_avail = 0;
    char *server = NULL;

    if (servers) {
	char *tmpbuf, *cur_server, *next_server;
	char mytag[128];
	struct backend *be;

	/* make a working copy of the list */
	cur_server = tmpbuf = xstrdup(servers);

	while (cur_server) {
	    /* eat any leading whitespace */
	    while (Uisspace(*cur_server)) cur_server++;

	    if (!*cur_server) break;

	    /* find end of server */
	    if ((next_server = strchr(cur_server, ' ')) ||
		(next_server = strchr(cur_server, '\t')))
		*next_server++ = '\0';

	    syslog(LOG_DEBUG, "checking free space on server '%s'", cur_server);

	    /* connect to server */
	    be = proxy_findserver(cur_server, &imap_protocol,
				  proxy_userid, &backend_cached,
				  &backend_current, &backend_inbox, imapd_in);
	    if (be) {
		unsigned avail = 0;
		int c;

		/* fetch annotation from remote */
		proxy_gentag(mytag, sizeof(mytag));
		prot_printf(be->out,
			    "%s GETANNOTATION \"\" "
			    "\"/vendor/cmu/cyrus-imapd/freespace\" "
			    "\"value.shared\"\r\n", mytag);
		prot_flush(be->out);

		for (/* each annotation response */;;) {
		    /* read a line */
		    c = prot_getc(be->in);
		    if (c != '*') break;
		    c = prot_getc(be->in);
		    if (c != ' ') { /* protocol error */ c = EOF; break; }

		    c = chomp(be->in,
			      "ANNOTATION \"\" "
			      "\"/vendor/cmu/cyrus-imapd/freespace\" "
			      "(\"value.shared\" \"");
		    if (c == EOF) {
			/* we don't care about this response */
			eatline(be->in, c);
			continue;
		    }

		    /* read uidvalidity */
		    c = getuint32(be->in, &avail);
		    if (c != '\"') { c = EOF; break; }
		    eatline(be->in, c); /* we don't care about the rest of the line */
		}
		if (c != EOF) {
		    prot_ungetc(c, be->in);

		    /* we should be looking at the tag now */
		    eatline(be->in, c);
		}
		if (c == EOF) {
		    /* uh oh, we're not happy */
		    fatal("Lost connection to backend", EC_UNAVAILABLE);
		}
		if (avail > max_avail) {
		    server = cur_server;
		    max_avail = avail;
		}
	    }

	    /* move to next server */
	    cur_server = next_server;
	}

	if (server) server = xstrdup(server);

	free(tmpbuf);
    }

    return server;
}
Exemple #14
0
/* Determine if we should respond to a vacation message */
static int shouldRespond(void * m, sieve_interp_t *interp,
			 int numaddresses, bytecode_input_t* bc,
			 int i, char **from, char **to)
{
    const char **body;
    char buf[128];
    char *myaddr = NULL;
    int l = SIEVE_OK, j;
    int curra, x;
    char *found = NULL;
    char *reply_to = NULL;
  
    /* Implementations SHOULD NOT respond to any message that contains a
       "List-Id" [RFC2919], "List-Help", "List-Subscribe", "List-
       Unsubscribe", "List-Post", "List-Owner" or "List-Archive" [RFC2369]
       header field. */
    for (j = 0; list_fields[j]; j++) {
	strcpy(buf, list_fields[j]);
	if (interp->getheader(m, buf, &body) == SIEVE_OK) {
	    l = SIEVE_DONE;
	    break;
	}
    }

    /* Implementations SHOULD NOT respond to any message that has an
       "Auto-submitted" header field with a value other than "no".
       This header field is described in [RFC3834]. */
    strcpy(buf, "auto-submitted");
    if (interp->getheader(m, buf, &body) == SIEVE_OK) {
	/* we don't deal with comments, etc. here */
	/* skip leading white-space */
	while (*body[0] && Uisspace(*body[0])) body[0]++;
	if (strcasecmp(body[0], "no")) l = SIEVE_DONE;
    }

    /* is there a Precedence keyword of "junk | bulk | list"? */
    /* XXX  non-standard header, but worth checking */
    strcpy(buf, "precedence");
    if (interp->getheader(m, buf, &body) == SIEVE_OK) {
	/* we don't deal with comments, etc. here */
	/* skip leading white-space */
	while (*body[0] && Uisspace(*body[0])) body[0]++;
	if (!strcasecmp(body[0], "junk") ||
	    !strcasecmp(body[0], "bulk") ||
	    !strcasecmp(body[0], "list"))
	    l = SIEVE_DONE;
    }

    /* Note: the domain-part of all addresses are canonicalized */
    /* grab my address from the envelope */
    if (l == SIEVE_OK) {
	strcpy(buf, "to");
	l = interp->getenvelope(m, buf, &body);
	
	if (body[0])
	    myaddr = address_canonicalise(body[0]);
    }  
  
    if (l == SIEVE_OK) {
	strcpy(buf, "from");
	l = interp->getenvelope(m, buf, &body);
    }
    if (l == SIEVE_OK && body[0]) {
	/* we have to parse this address & decide whether we
	   want to respond to it */
	reply_to = address_canonicalise(body[0]);

	/* first, is there a reply-to address? */
	if (reply_to == NULL) {
	    l = SIEVE_DONE;
	}
    
	/* first, is it from me? */
	if (l == SIEVE_OK && myaddr && !strcmp(myaddr, reply_to)) {
	    l = SIEVE_DONE;
	}
   
	/* ok, is it any of the other addresses i've
	   specified? */
	if (l == SIEVE_OK)
	{
	    curra=i;
	    for(x=0; x<numaddresses; x++) {
		const char *address;

		curra = unwrap_string(bc, curra, &address, NULL);
		
		if (!strcmp(address, reply_to))
		    l = SIEVE_DONE;
	    }
	}
   
	/* ok, is it a system address? */
	if (l == SIEVE_OK && sysaddr(reply_to)) {
	    l = SIEVE_DONE;
	}
    }
    if (l == SIEVE_OK) {
	/* ok, we're willing to respond to the sender.
	   but is this message to me?  that is, is my address
	   in the [Resent]-To, [Resent]-Cc or [Resent]-Bcc fields? */
	if (strcpy(buf, "to"), 
	    interp->getheader(m, buf, &body) == SIEVE_OK)
	    found = look_for_me(myaddr, numaddresses ,bc, i, body);
	if (!found && (strcpy(buf, "cc"),
		       (interp->getheader(m, buf, &body) == SIEVE_OK)))
	    found = look_for_me(myaddr, numaddresses, bc, i, body);
	if (!found && (strcpy(buf, "bcc"),
		       (interp->getheader(m, buf, &body) == SIEVE_OK)))
	    found = look_for_me(myaddr, numaddresses, bc, i, body);
	if (!found && (strcpy(buf, "resent-to"), 
		       (interp->getheader(m, buf, &body) == SIEVE_OK)))
	    found = look_for_me(myaddr, numaddresses ,bc, i, body);
	if (!found && (strcpy(buf, "resent-cc"),
		       (interp->getheader(m, buf, &body) == SIEVE_OK)))
	    found = look_for_me(myaddr, numaddresses, bc, i, body);
	if (!found && (strcpy(buf, "resent-bcc"),
		       (interp->getheader(m, buf, &body) == SIEVE_OK)))
	    found = look_for_me(myaddr, numaddresses, bc, i, body);
	if (!found)
	    l = SIEVE_DONE;
    }
    /* ok, ok, if we got here maybe we should reply */
    if (myaddr) free(myaddr);
    *from = found;
    *to = reply_to;
    return l;
}
Exemple #15
0
void masterconf_getsection(const char *section, masterconf_process *f,
			   void *rock)
{
    FILE *infile = NULL;
    int seclen = strlen(section);
    int level = 0;
    int lineno = 0;
    char buf[4096];
    const char *cyrus_path;

    /* try loading the copy inside CYRUS_PREFIX first */
    cyrus_path = getenv("CYRUS_PREFIX");
    if (cyrus_path) {
	strlcpy(buf, cyrus_path, sizeof(buf));
	strlcat(buf, MASTER_CONFIG_FILENAME, sizeof(buf));
	infile = fopen(buf, "r");
    }

    if (!infile)
	infile = fopen(MASTER_CONFIG_FILENAME, "r");

    if (!infile)
	fatalf(EX_CONFIG, "can't open configuration file %s: %m",
		MASTER_CONFIG_FILENAME);

    while (fgets(buf, sizeof(buf), infile)) {
	char *p, *q;

	lineno++;

	if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
	for (p = buf; *p && Uisspace(*p); p++);
	
	/* remove comments */
	q = strchr(p, '#');
	if (q) *q = '\0';

	/* skip empty lines or all comment lines */
	if (!*p) continue;
	
	if (level == 0 &&
	    *p == *section && !strncasecmp(p, section, seclen) &&
	    !Uisalnum(p[seclen])) {
	    for (p += seclen; *p; p++) {
		if (*p == '{') level++;
		if (*p == '}') level--;
	    }

	    /* valid opening; process the section */
	    if (level == 1) process_section(infile, &lineno, f, rock);

	    continue;
	}

	for (; *p; p++) {
	    if (*p == '{') level++;
	    if (*p == '}') level--;
	}
    }

    fclose(infile);
}
Exemple #16
0
static char next_nonspace(struct protstream *in, char c)
{
    while (Uisspace(c)) c = prot_getc(in);
    return c;
}
Exemple #17
0
/*
**  Parse the expiration control file.  Return TRUE if okay.
*/
BOOL EXPreadfile(FILE *F)
{
    char	        *p;
    int	                i;
    int	                j;
    int	                k;
    char	        mod;
    NEWSGROUP		v;
    BOOL		SawDefault;
    char		buff[BUFSIZ];
    char		*fields[7];
    char		**patterns;

    /* Scan all lines. */
    EXPremember = -1;
    SawDefault = FALSE;
    patterns = NEW(char*, nGroups);
    for (i = 0; i < NUM_STORAGE_CLASSES; i++)
	EXPclasses[i].ReportedMissing = EXPclasses[i].Missing = TRUE;
    
    for (i = 1; fgets(buff, sizeof buff, F) != NULL; i++) {
	if ((p = strchr(buff, '\n')) == NULL) {
	    (void)fprintf(stderr, "Line %d too long\n", i);
	    return FALSE;
	}
	*p = '\0';
        p = strchr(buff, '#');
	if (p)
	    *p = '\0';
	else
	    p = buff + strlen(buff);
	while (--p >= buff) {
	    if (Uisspace(*p))
                *p = '\0';
            else
                break;
        }
        if (buff[0] == '\0')
	    continue;
	if ((j = EXPsplit(buff, ':', fields, SIZEOF(fields))) == -1) {
	    (void)fprintf(stderr, "Line %d too many fields\n", i);
	    return FALSE;
	}

	/* Expired-article remember line? */
	if (EQ(fields[0], "/remember/")) {
	    if (j != 2) {
		(void)fprintf(stderr, "Line %d bad format\n", i);
		return FALSE;
	    }
	    if (EXPremember != -1) {
		(void)fprintf(stderr, "Line %d duplicate /remember/\n", i);
		return FALSE;
	    }
	    if (!EXPgetnum(i, fields[1], &EXPremember, "remember"))
		return FALSE;
	    continue;
	}

	/* Storage class line? */
	if (j == 4) {
	    j = atoi(fields[0]);
	    if ((j < 0) || (j > NUM_STORAGE_CLASSES)) {
		fprintf(stderr, "Line %d bad storage class %d\n", i, j);
	    }
	
	    if (!EXPgetnum(i, fields[1], &EXPclasses[j].Keep,    "keep")
		|| !EXPgetnum(i, fields[2], &EXPclasses[j].Default, "default")
		|| !EXPgetnum(i, fields[3], &EXPclasses[j].Purge,   "purge"))
		return FALSE;
	    /* These were turned into offsets, so the test is the opposite
	     * of what you think it should be.  If Purge isn't forever,
	     * make sure it's greater then the other two fields. */
	    if (EXPclasses[j].Purge) {
		/* Some value not forever; make sure other values are in range. */
		if (EXPclasses[j].Keep && EXPclasses[j].Keep < EXPclasses[j].Purge) {
		    (void)fprintf(stderr, "Line %d keep>purge\n", i);
		    return FALSE;
		}
		if (EXPclasses[j].Default && EXPclasses[j].Default < EXPclasses[j].Purge) {
		    (void)fprintf(stderr, "Line %d default>purge\n", i);
		    return FALSE;
		}
	    }
	    EXPclasses[j].Missing = FALSE;
	    continue;
	}

	/* Regular expiration line -- right number of fields? */
	if (j != 5) {
	    (void)fprintf(stderr, "Line %d bad format\n", i);
	    return FALSE;
	}

	/* Parse the fields. */
	if (strchr(fields[1], 'M') != NULL)
	    mod = 'm';
	else if (strchr(fields[1], 'U') != NULL)
	    mod = 'u';
	else if (strchr(fields[1], 'A') != NULL)
	    mod = 'a';
	else {
	    (void)fprintf(stderr, "Line %d bad modflag\n", i);
	    return FALSE;
	}
	v.Poison = (strchr(fields[1], 'X') != NULL);
	if (!EXPgetnum(i, fields[2], &v.Keep,    "keep")
	 || !EXPgetnum(i, fields[3], &v.Default, "default")
	 || !EXPgetnum(i, fields[4], &v.Purge,   "purge"))
	    return FALSE;
	/* These were turned into offsets, so the test is the opposite
	 * of what you think it should be.  If Purge isn't forever,
	 * make sure it's greater then the other two fields. */
	if (v.Purge) {
	    /* Some value not forever; make sure other values are in range. */
	    if (v.Keep && v.Keep < v.Purge) {
		(void)fprintf(stderr, "Line %d keep>purge\n", i);
		return FALSE;
	    }
	    if (v.Default && v.Default < v.Purge) {
		(void)fprintf(stderr, "Line %d default>purge\n", i);
		return FALSE;
	    }
	}

	/* Is this the default line? */
	if (fields[0][0] == '*' && fields[0][1] == '\0' && mod == 'a') {
	    if (SawDefault) {
		(void)fprintf(stderr, "Line %d duplicate default\n", i);
                return FALSE;
	    }
	    EXPdefault.Keep    = v.Keep;
	    EXPdefault.Default = v.Default;
	    EXPdefault.Purge   = v.Purge;
	    EXPdefault.Poison  = v.Poison;
	    SawDefault = TRUE;
	}

	/* Assign to all groups that match the pattern and flags. */
	if ((j = EXPsplit(fields[0], ',', patterns, nGroups)) == -1) {
	    (void)fprintf(stderr, "Line %d too many patterns\n", i);
	    return FALSE;
	}
	for (k = 0; k < j; k++)
	    EXPmatch(patterns[k], &v, mod);
    }
    DISPOSE(patterns);

    return TRUE;
}