#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95";
#else
__RCSID("$NetBSD: list.c,v 1.29 2021/12/07 21:37:37 andvar Exp $");
#endif
#endif
#include <assert.h>
#include <regex.h>
#include <util.h>
#include "rcv.h"
#include "extern.h"
#include "format.h"
#include "thread.h"
#include "mime.h"
enum token_e {
TEOL,
TNUMBER,
TDASH,
TSTRING,
TDOT,
TUP,
TDOLLAR,
TSTAR,
TOPEN,
TCLOSE,
TPLUS,
TAND,
TOR,
TXOR,
TNOT,
TERROR
};
#define REGDEP 2
#define STRINGLEN 1024
static int lexnumber;
static char lexstring[STRINGLEN];
static int regretp;
static int regretstack[REGDEP];
static char *string_stack[REGDEP];
static int numberstack[REGDEP];
PUBLIC int
getrawlist(const char line[], char **argv, int argc)
{
char c, *cp2, quotec;
const char *cp;
int argn;
char linebuf[LINESIZE];
argn = 0;
cp = line;
for (;;) {
cp = skip_WSP(cp);
if (*cp == '\0')
break;
if (argn >= argc - 1) {
(void)printf(
"Too many elements in the list; excess discarded.\n");
break;
}
cp2 = linebuf;
quotec = '\0';
while ((c = *cp) != '\0') {
cp++;
if (quotec != '\0') {
if (c == quotec)
quotec = '\0';
else if (quotec != '\'' && c == '\\')
switch (c = *cp++) {
case '\0':
*cp2++ = '\\';
cp--;
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c -= '0';
if (*cp >= '0' && *cp <= '7')
c = c * 8 + *cp++ - '0';
if (*cp >= '0' && *cp <= '7')
c = c * 8 + *cp++ - '0';
*cp2++ = c;
break;
case 'b':
*cp2++ = '\b';
break;
case 'f':
*cp2++ = '\f';
break;
case 'n':
*cp2++ = '\n';
break;
case 'r':
*cp2++ = '\r';
break;
case 't':
*cp2++ = '\t';
break;
case 'v':
*cp2++ = '\v';
break;
default:
*cp2++ = c;
}
else if (c == '^') {
c = *cp++;
if (c == '?')
*cp2++ = '\177';
else if ((c >= 'A' && c <= '_') ||
(c >= 'a' && c <= 'z'))
*cp2++ = c & 037;
else {
*cp2++ = '^';
cp--;
}
} else
*cp2++ = c;
} else if (c == '"' || c == '\'')
quotec = c;
else if (is_WSP(c))
break;
else
*cp2++ = c;
}
*cp2 = '\0';
argv[argn++] = savestr(linebuf);
}
argv[argn] = NULL;
return argn;
}
#define CMBOX 0x001
#define CMDELETED 0x002
#define CMMODIFY 0x004
#define CMNEW 0x008
#define CMOLD 0x010
#define CMPRESERVE 0x020
#define CMREAD 0x040
#define CMSAVED 0x080
#define CMTAGGED 0x100
#define CMUNREAD 0x200
#define CMNEGATE 0x400
#define CMMASK 0x7ff
static const struct coltab {
char co_char;
int co_bit;
int co_mask;
int co_equal;
} coltab[] = {
{ '!', CMNEGATE, 0, 0 },
{ 'd', CMDELETED, MDELETED, MDELETED },
{ 'e', CMMODIFY, MMODIFY, MMODIFY },
{ 'm', CMBOX, MBOX, MBOX },
{ 'n', CMNEW, MNEW, MNEW },
{ 'o', CMOLD, MNEW, 0 },
{ 'p', CMPRESERVE, MPRESERVE, MPRESERVE },
{ 'r', CMREAD, MREAD, MREAD },
{ 's', CMSAVED, MSAVED, MSAVED },
{ 't', CMTAGGED, MTAGGED, MTAGGED },
{ 'u', CMUNREAD, MREAD|MNEW, 0 },
{ 0, 0, 0, 0 }
};
static int lastcolmod;
static int
ignore_message(int m_flag, int colmod)
{
int ignore_msg;
const struct coltab *colp;
ignore_msg = !(colmod & CMNEGATE);
colmod &= (~CMNEGATE & CMMASK);
for (colp = &coltab[0]; colp->co_char; colp++)
if (colp->co_bit & colmod &&
(m_flag & colp->co_mask) == colp->co_equal)
return !ignore_msg;
return ignore_msg;
}
static int
evalcol(int col)
{
const struct coltab *colp;
if (col == 0)
return lastcolmod;
for (colp = &coltab[0]; colp->co_char; colp++)
if (colp->co_char == col)
return colp->co_bit;
return 0;
}
static int
get_colmod(int colmod, char *cp)
{
if ((cp[0] == '\0') ||
(cp[0] == '!' && cp[1] == '\0'))
colmod |= lastcolmod;
for (; *cp; cp++) {
int colresult;
if ((colresult = evalcol(*cp)) == 0) {
(void)printf("Unknown colon modifier \"%s\"\n", lexstring);
return -1;
}
if (colresult == CMNEGATE)
colmod ^= CMNEGATE;
else
colmod |= colresult;
}
return colmod;
}
static int
syntax_error(const char *msg)
{
(void)printf("Syntax error: %s\n", msg);
return -1;
}
static enum token_e
scan(char **sp)
{
static const struct lex {
char l_char;
enum token_e l_token;
} singles[] = {
{ '$', TDOLLAR },
{ '.', TDOT },
{ '^', TUP },
{ '*', TSTAR },
{ '-', TDASH },
{ '+', TPLUS },
{ '(', TOPEN },
{ ')', TCLOSE },
{ '&', TAND },
{ '|', TOR },
{ '!', TNOT },
{ 0, 0 }
};
const struct lex *lp;
char *cp, *cp2;
int c;
int quotec;
if (regretp >= 0) {
(void)strcpy(lexstring, string_stack[regretp]);
lexnumber = numberstack[regretp];
return regretstack[regretp--];
}
cp = *sp;
cp2 = lexstring;
lexstring[0] = '\0';
cp = skip_WSP(cp);
if (*cp == '\0') {
*sp = cp;
return TEOL;
}
c = (unsigned char)*cp++;
if (isdigit(c)) {
lexnumber = 0;
while (isdigit(c)) {
lexnumber = lexnumber * 10 + c - '0';
*cp2++ = c;
c = (unsigned char)*cp++;
}
*cp2 = '\0';
*sp = --cp;
return TNUMBER;
}
for (lp = &singles[0]; lp->l_char != 0; lp++)
if (c == lp->l_char) {
lexstring[0] = c;
lexstring[1] = '\0';
*sp = cp;
return lp->l_token;
}
quotec = 0;
while (c != '\0') {
if (c == quotec) {
quotec = 0;
c = *cp++;
continue;
}
if (quotec) {
if (c == '\\' && (*cp == quotec || *cp == '\\'))
c = *cp++;
}
else {
switch (c) {
case '\'':
case '"':
quotec = c;
c = *cp++;
continue;
case ' ':
case '\t':
c = '\0';
continue;
default:
break;
}
}
if (cp2 - lexstring < STRINGLEN - 1)
*cp2++ = c;
c = *cp++;
}
if (quotec && c == 0) {
(void)fprintf(stderr, "Missing %c\n", quotec);
return TERROR;
}
*sp = --cp;
*cp2 = '\0';
return TSTRING;
}
static void
regret(int token)
{
if (++regretp >= REGDEP)
errx(EXIT_FAILURE, "Too many regrets");
regretstack[regretp] = token;
lexstring[sizeof(lexstring) - 1] = '\0';
string_stack[regretp] = savestr(lexstring);
numberstack[regretp] = lexnumber;
}
static void
scaninit(void)
{
regretp = -1;
}
#define DELIM " \t,"
static int
is_substr(const char *big, const char *little)
{
const char *cp;
if ((cp = strstr(big, little)) == NULL)
return 0;
return strchr(DELIM, cp[strlen(little)]) != 0 &&
(cp == big || strchr(DELIM, cp[-1]) != 0);
}
#undef DELIM
static int
regexcmp(void *pattern, char *line, size_t len)
{
regmatch_t pmatch[1];
regmatch_t *pmp;
int eflags;
int rval;
regex_t *preg;
preg = pattern;
if (line == NULL)
return 0;
if (len == 0) {
pmp = NULL;
eflags = 0;
}
else {
pmatch[0].rm_so = 0;
pmatch[0].rm_eo = line[len - 1] == '\n' ? len - 1 : len;
pmp = pmatch;
eflags = REG_STARTEND;
}
switch ((rval = regexec(preg, line, 0, pmp, eflags))) {
case 0:
case REG_NOMATCH:
return rval == 0;
default: {
char errbuf[LINESIZE];
(void)regerror(rval, preg, errbuf, sizeof(errbuf));
(void)printf("regexec failed: '%s': %s\n", line, errbuf);
return -1;
}}
}
static int
substrcmp(void *pattern, char *line, size_t len)
{
char *substr;
substr = pattern;
if (line == NULL)
return 0;
if (len) {
if (line[len - 1] == '\n') {
line[len - 1] = '\0';
}
else {
char *cp;
cp = salloc(len + 1);
(void)strlcpy(cp, line, len + 1);
line = cp;
}
}
return strcasestr(line, substr) != NULL;
}
static int
hasfieldcmp(void *pattern __unused, char *line, size_t len __unused)
{
#ifdef __lint__
pattern = pattern;
len = len;
#endif
return line != NULL;
}
static regex_t preg;
static int (*
get_cmpfn(void **pattern, char *str)
)(void *, char *, size_t)
{
char *val;
int cflags;
int e;
if (*str == 0) {
*pattern = NULL;
return hasfieldcmp;
}
if ((val = value(ENAME_REGEX_SEARCH)) != NULL) {
cflags = REG_NOSUB;
val = skip_WSP(val);
if (*val) {
if (is_substr(val, "icase"))
cflags |= REG_ICASE;
if (is_substr(val, "extended"))
cflags |= REG_EXTENDED;
if (is_substr(val, "nospec"))
cflags |= REG_NOSPEC;
}
if ((e = regcomp(&preg, str, cflags)) != 0) {
char errbuf[LINESIZE];
(void)regerror(e, &preg, errbuf, sizeof(errbuf));
(void)printf("regcomp failed: '%s': %s\n", str, errbuf);
return NULL;
}
*pattern = &preg;
return regexcmp;
}
*pattern = str;
return substrcmp;
}
static void
free_cmparg(void *pattern)
{
if (pattern == &preg)
regfree(&preg);
}
static int
matchbody(int (*cmpfn)(void *, char *, size_t),
void *pattern, struct message *mp, char const *fieldname __unused)
{
FILE *fp;
char *line;
size_t len;
int gotmatch;
#ifdef __lint__
fieldname = fieldname;
#endif
{
char *tempname;
int fd;
(void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir);
fp = NULL;
if ((fd = mkstemp(tempname)) != -1) {
(void)unlink(tempname);
if ((fp = Fdopen(fd, "wef+")) == NULL)
(void)close(fd);
}
if (fp == NULL) {
warn("%s", tempname);
return -1;
}
}
{
#ifdef MIME_SUPPORT
struct mime_info *mip;
int retval;
mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp)
: NULL;
retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip);
mime_decode_close(mip);
if (retval == -1)
#else
if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1)
#endif
{
warn("matchbody: mesg=%d", get_msgnum(mp));
return -1;
}
}
rewind(fp);
gotmatch = 0;
while ((line = fgetln(fp, &len)) != NULL && len > 0) {
gotmatch = cmpfn(pattern, line, len);
if (gotmatch)
break;
}
(void)Fclose(fp);
return gotmatch;
}
static int
matchto(int (*cmpfn)(void *, char *, size_t),
void *pattern, struct message *mp, char const *fieldname __unused)
{
static const char *to_fields[] = { "to", "cc", "bcc", 0 };
const char **to;
int gotmatch;
#ifdef __lint__
fieldname = fieldname;
#endif
gotmatch = 0;
for (to = to_fields; *to; to++) {
char *field;
field = hfield(*to, mp);
gotmatch = cmpfn(pattern, field, 0);
if (gotmatch)
break;
}
return gotmatch;
}
static int
matchfield(int (*cmpfn)(void *, char *, size_t),
void *pattern, struct message *mp, char const *fieldname)
{
char *field;
#ifdef __lint__
fieldname = fieldname;
#endif
field = hfield(fieldname, mp);
return cmpfn(pattern, field, 0);
}
static int
matchfrom(int (*cmpfn)(void *, char *, size_t),
void *pattern, struct message *mp, char const *fieldname __unused)
{
char headline[LINESIZE];
char *field;
#ifdef __lint__
fieldname = fieldname;
#endif
(void)readline(setinput(mp), headline, (int)sizeof(headline), 0);
field = savestr(headline);
if (strncmp(field, "From ", 5) != 0)
return 1;
return cmpfn(pattern, field + 5, 0);
}
static int
matchsender(int (*cmpfn)(void *, char *, size_t),
void *pattern, struct message *mp, char const *fieldname __unused)
{
char *field;
#ifdef __lint__
fieldname = fieldname;
#endif
field = nameof(mp, 0);
return cmpfn(pattern, field, 0);
}
static int
match_string(int *markarray, char *str, int msgCount)
{
int i;
int rval;
int (*matchfn)(int (*)(void *, char *, size_t),
void *, struct message *, char const *);
int (*cmpfn)(void *, char *, size_t);
void *cmparg;
char const *fieldname;
if (*str != '/') {
matchfn = matchsender;
fieldname = NULL;
}
else {
static char lastscan[STRINGLEN];
char *cp;
str++;
if (*str == '\0')
str = lastscan;
else
(void)strlcpy(lastscan, str, sizeof(lastscan));
if (value(ENAME_SEARCHHEADERS) == NULL ||
(cp = strchr(str, ':')) == NULL) {
matchfn = matchfield;
fieldname = "subject";
}
else {
static const struct matchtbl_s {
char const *key;
size_t len;
char const *fieldname;
int (*matchfn)(int (*)(void *, char *, size_t),
void *, struct message *, char const *);
} matchtbl[] = {
#define X(a) a, sizeof(a) - 1
#define X_NULL NULL, 0
{ X(":"), "subject", matchfield },
{ X("body:"), NULL, matchbody },
{ X("from:"), NULL, matchfrom },
{ X("to:"), NULL, matchto },
{ X_NULL, NULL, matchfield }
#undef X_NULL
#undef X
};
const struct matchtbl_s *mtp;
size_t len;
cp++;
len = cp - str;
for (mtp = matchtbl; mtp->key; mtp++) {
if (len == mtp->len &&
strncmp(str, mtp->key, len) == 0)
break;
}
matchfn = mtp->matchfn;
if (mtp->key)
fieldname = mtp->fieldname;
else {
char *p;
p = salloc(len);
(void)strlcpy(p, str, len);
fieldname = p;
}
str = cp;
}
}
cmpfn = get_cmpfn(&cmparg, str);
if (cmpfn == NULL)
return -1;
rval = 0;
for (i = 1; i <= msgCount; i++) {
struct message *mp;
mp = get_message(i);
rval = matchfn(cmpfn, cmparg, mp, fieldname);
if (rval == -1)
break;
if (rval)
markarray[i - 1] = 1;
rval = 0;
}
free_cmparg(cmparg);
return rval;
}
static int
metamess(int meta, int f)
{
int c, m;
struct message *mp;
c = meta;
switch (c) {
case '^':
for (mp = get_message(1); mp; mp = next_message(mp))
if ((mp->m_flag & MDELETED) == f)
return get_msgnum(mp);
(void)printf("No applicable messages\n");
return -1;
case '$':
for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
if ((mp->m_flag & MDELETED) == f)
return get_msgnum(mp);
(void)printf("No applicable messages\n");
return -1;
case '.':
if (dot == NULL) {
(void)printf("No applicable messages\n");
return -1;
}
m = get_msgnum(dot);
if ((dot->m_flag & MDELETED) != f) {
(void)printf("%d: Inappropriate message\n", m);
return -1;
}
return m;
default:
(void)printf("Unknown metachar (%c)\n", c);
return -1;
}
}
static int
check(int mesg, int f)
{
struct message *mp;
if ((mp = get_message(mesg)) == NULL) {
(void)printf("%d: Invalid message number\n", mesg);
return -1;
}
if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
(void)printf("%d: Inappropriate message\n", mesg);
return -1;
}
return 0;
}
static int
markall_core(int *markarray, char **bufp, int f, int level)
{
enum token_e tok;
enum logic_op_e {
LOP_AND,
LOP_OR,
LOP_XOR
} logic_op;
int logic_invert;
int *tmparray;
int msgCount;
int beg;
int colmod;
int got_not;
int got_one;
int got_bin;
int i;
logic_op = LOP_OR;
logic_invert = 0;
colmod = 0;
msgCount = get_msgCount();
tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
beg = 0;
got_one = 0;
got_not = 0;
got_bin = 0;
while ((tok = scan(bufp)) != TEOL) {
if (tok == TERROR)
return -1;
switch (tok) {
case TDASH:
case TPLUS:
case TDOLLAR:
case TUP:
case TDOT:
case TNUMBER:
break;
case TAND:
case TOR:
case TXOR:
if (!got_one)
return syntax_error("missing left operand");
default:
if (beg)
return syntax_error("end of range missing");
break;
}
switch (tok) {
struct message *mp;
case TERROR:
case TEOL:
assert(0);
break;
case TUP:
if (got_one) {
enum token_e t;
t = scan(bufp);
regret(t);
lexstring[0] = '^';
lexstring[1] = '\0';
if (t != TDASH && t != TEOL && t != TCLOSE) {
tok = TXOR;
regret(tok);
continue;
}
}
case TDOLLAR:
case TDOT:
lexnumber = metamess(lexstring[0], f);
if (lexnumber == -1)
return -1;
case TNUMBER:
if (check(lexnumber, f))
return -1;
number:
got_one = 1;
if (beg != 0) {
if (lexnumber < beg) {
(void)printf("invalid range: %d-%d\n", beg, lexnumber);
return -1;
}
for (i = beg; i <= lexnumber; i++)
tmparray[i - 1] = 1;
beg = 0;
break;
}
beg = lexnumber;
tok = scan(bufp);
if (tok == TDASH) {
continue;
}
else {
regret(tok);
tmparray[beg - 1] = 1;
beg = 0;
}
break;
case TDASH:
for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
if ((mp->m_flag & MDELETED) == 0)
break;
}
if (mp == NULL) {
(void)printf("Referencing before 1\n");
return -1;
}
lexnumber = get_msgnum(mp);
goto number;
case TPLUS:
for (mp = next_message(dot); mp; mp = next_message(mp)) {
if ((mp->m_flag & MDELETED) == 0)
break;
}
if (mp == NULL) {
(void)printf("Referencing beyond EOF\n");
return -1;
}
lexnumber = get_msgnum(mp);
goto number;
case TSTRING:
if (lexstring[0] == ':') {
colmod = get_colmod(colmod, lexstring + 1);
if (colmod == -1)
return -1;
continue;
}
got_one = 1;
if (match_string(tmparray, lexstring, msgCount) == -1)
return -1;
break;
case TSTAR:
got_one = 1;
for (i = 1; i <= msgCount; i++)
tmparray[i - 1] = 1;
break;
case TOPEN:
if (markall_core(tmparray, bufp, f, level + 1) == -1)
return -1;
break;
case TCLOSE:
if (level == 0)
return syntax_error("extra ')'");
goto done;
case TNOT:
got_not = 1;
logic_invert = ! logic_invert;
continue;
case TAND:
if (got_not)
return syntax_error("'!' precedes '&'");
got_bin = 1;
logic_op = LOP_AND;
continue;
case TOR:
if (got_not)
return syntax_error("'!' precedes '|'");
got_bin = 1;
logic_op = LOP_OR;
continue;
case TXOR:
if (got_not)
return syntax_error("'!' precedes logical '^'");
got_bin = 1;
logic_op = LOP_XOR;
continue;
}
if (logic_invert)
for (i = 0; i < msgCount; i++)
tmparray[i] = ! tmparray[i];
switch (logic_op) {
case LOP_AND:
for (i = 0; i < msgCount; i++)
markarray[i] &= tmparray[i];
break;
case LOP_OR:
for (i = 0; i < msgCount; i++)
markarray[i] |= tmparray[i];
break;
case LOP_XOR:
for (i = 0; i < msgCount; i++)
markarray[i] ^= tmparray[i];
break;
}
for (i = 0; i < msgCount; i++)
tmparray[i] = 0;
logic_op = LOP_OR;
logic_invert = 0;
got_not = 0;
got_bin = 0;
}
if (beg)
return syntax_error("end of range missing");
if (level)
return syntax_error("missing ')'");
done:
if (got_not)
return syntax_error("trailing '!'");
if (got_bin)
return syntax_error("missing right operand");
if (colmod != 0) {
if (got_one == 0)
for (i = 1; i <= msgCount; i++)
markarray[i - 1] = 1;
for (i = 1; i <= msgCount; i++) {
struct message *mp;
if ((mp = get_message(i)) != NULL &&
ignore_message(mp->m_flag, colmod))
markarray[i - 1] = 0;
}
}
return 0;
}
static int
markall(char buf[], int f)
{
int i;
int mc;
int *markarray;
int msgCount;
struct message *mp;
msgCount = get_msgCount();
for (i = 1; i <= msgCount; i++)
if ((mp = get_message(i)) != NULL)
mp->m_flag &= ~MMARK;
buf = skip_WSP(buf);
if (*buf == '\0')
return 0;
scaninit();
markarray = csalloc((size_t)msgCount, sizeof(*markarray));
if (markall_core(markarray, &buf, f, 0) == -1)
return -1;
mc = 0;
for (i = 1; i <= msgCount; i++) {
if (markarray[i - 1] &&
(mp = get_message(i)) != NULL &&
(f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
mp->m_flag |= MMARK;
mc++;
}
}
if (mc == 0) {
(void)printf("No applicable messages.\n");
return -1;
}
return 0;
}
PUBLIC int
getmsglist(char *buf, int *vector, int flags)
{
int *ip;
struct message *mp;
if (get_msgCount() == 0) {
*vector = 0;
return 0;
}
if (markall(buf, flags) < 0)
return -1;
ip = vector;
for (mp = get_message(1); mp; mp = next_message(mp))
if (mp->m_flag & MMARK)
*ip++ = get_msgnum(mp);
*ip = 0;
return (int)(ip - vector);
}
PUBLIC int
first(int f, int m)
{
struct message *mp;
if (get_msgCount() == 0)
return 0;
f &= MDELETED;
m &= MDELETED;
for (mp = dot; mp; mp = next_message(mp))
if ((mp->m_flag & m) == f)
return get_msgnum(mp);
for (mp = prev_message(dot); mp; mp = prev_message(mp))
if ((mp->m_flag & m) == f)
return get_msgnum(mp);
return 0;
}
__dead
PUBLIC int
show_headers_and_exit(int flags)
{
struct message *mp;
if (signal(SIGINT, SIG_DFL) == SIG_IGN)
(void)signal(SIGINT, SIG_IGN);
flags &= CMMASK;
for (mp = get_message(1); mp; mp = next_message(mp))
if (flags == 0 || !ignore_message(mp->m_flag, flags))
printhead(get_msgnum(mp));
exit(0);
}
PUBLIC int
get_Hflag(char **argv)
{
int flags;
flags = ~CMMASK;
if (optarg == NULL)
return flags;
if (*optarg != ':' || optarg == argv[optind - 1]) {
optind--;
optreset = 1;
if (optarg != argv[optind]) {
static char temparg[LINESIZE];
size_t optlen;
size_t arglen;
char *p;
optlen = strlen(optarg);
arglen = strlen(argv[optind]);
p = argv[optind] + arglen - optlen;
optlen = MIN(optlen, sizeof(temparg) - 1);
temparg[0] = '-';
(void)memmove(temparg + 1, p, optlen + 1);
argv[optind] = temparg;
}
}
else {
flags = get_colmod(flags, optarg + 1);
}
return flags;
}