GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mail/list.c Lines: 17 324 5.2 %
Date: 2017-11-07 Branches: 16 284 5.6 %

Line Branch Exec Source
1
/*	$OpenBSD: list.c,v 1.20 2015/10/16 17:56:07 mmcc Exp $	*/
2
/*	$NetBSD: list.c,v 1.7 1997/07/09 05:23:36 mikel Exp $	*/
3
4
/*
5
 * Copyright (c) 1980, 1993
6
 *	The Regents of the University of California.  All rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 * 3. Neither the name of the University nor the names of its contributors
17
 *    may be used to endorse or promote products derived from this software
18
 *    without specific prior written permission.
19
 *
20
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30
 * SUCH DAMAGE.
31
 */
32
33
#include "rcv.h"
34
#include <ctype.h>
35
#include "extern.h"
36
37
int matchto(char *, int);
38
39
/*
40
 * Mail -- a mail program
41
 *
42
 * Message list handling.
43
 */
44
45
/*
46
 * Convert the user string of message numbers and
47
 * store the numbers into vector.
48
 *
49
 * Returns the count of messages picked up or -1 on error.
50
 */
51
int
52
getmsglist(char *buf, int *vector, int flags)
53
{
54
	int *ip;
55
	struct message *mp;
56
57
	if (msgCount == 0) {
58
		*vector = 0;
59
		return(0);
60
	}
61
	if (markall(buf, flags) < 0)
62
		return(-1);
63
	ip = vector;
64
	for (mp = &message[0]; mp < &message[msgCount]; mp++)
65
		if (mp->m_flag & MMARK)
66
			*ip++ = mp - &message[0] + 1;
67
	*ip = 0;
68
	return(ip - vector);
69
}
70
71
/*
72
 * Mark all messages that the user wanted from the command
73
 * line in the message structure.  Return 0 on success, -1
74
 * on error.
75
 */
76
77
/*
78
 * Bit values for colon modifiers.
79
 */
80
#define	CMNEW		01		/* New messages */
81
#define	CMOLD		02		/* Old messages */
82
#define	CMUNREAD	04		/* Unread messages */
83
#define	CMDELETED	010		/* Deleted messages */
84
#define	CMREAD		020		/* Read messages */
85
86
/*
87
 * The following table describes the letters which can follow
88
 * the colon and gives the corresponding modifier bit.
89
 */
90
struct coltab {
91
	char	co_char;		/* What to find past : */
92
	int	co_bit;			/* Associated modifier bit */
93
	int	co_mask;		/* m_status bits to mask */
94
	int	co_equal;		/* ... must equal this */
95
} coltab[] = {
96
	{ 'n',		CMNEW,		MNEW,		MNEW },
97
	{ 'o',		CMOLD,		MNEW,		0 },
98
	{ 'u',		CMUNREAD,	MREAD,		0 },
99
	{ 'd',		CMDELETED,	MDELETED,	MDELETED },
100
	{ 'r',		CMREAD,		MREAD,		MREAD },
101
	{ 0,		0,		0,		0 }
102
};
103
104
static int lastcolmod;
105
106
int
107
markall(char *buf, int f)
108
{
109
	char **np;
110
	int i;
111
	struct message *mp;
112
	char *namelist[NMLSIZE], *bufp;
113
	int tok, beg, mc, star, other, valdot, colmod, colresult;
114
115
	valdot = dot - &message[0] + 1;
116
	colmod = 0;
117
	for (i = 1; i <= msgCount; i++)
118
		unmark(i);
119
	bufp = buf;
120
	mc = 0;
121
	np = &namelist[0];
122
	scaninit();
123
	tok = scan(&bufp);
124
	star = 0;
125
	other = 0;
126
	beg = 0;
127
	while (tok != TEOL) {
128
		switch (tok) {
129
		case TNUMBER:
130
number:
131
			if (star) {
132
				puts("No numbers mixed with *");
133
				return(-1);
134
			}
135
			mc++;
136
			other++;
137
			if (beg != 0) {
138
				if (check(lexnumber, f))
139
					return(-1);
140
				for (i = beg; i <= lexnumber; i++)
141
					if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
142
						mark(i);
143
				beg = 0;
144
				break;
145
			}
146
			beg = lexnumber;
147
			if (check(beg, f))
148
				return(-1);
149
			tok = scan(&bufp);
150
			regret(tok);
151
			if (tok != TDASH) {
152
				mark(beg);
153
				beg = 0;
154
			}
155
			break;
156
157
		case TPLUS:
158
			if (beg != 0) {
159
				puts("Non-numeric second argument");
160
				return(-1);
161
			}
162
			i = valdot;
163
			do {
164
				i++;
165
				if (i > msgCount) {
166
					puts("Referencing beyond EOF");
167
					return(-1);
168
				}
169
			} while ((message[i - 1].m_flag & MDELETED) != f);
170
			mark(i);
171
			break;
172
173
		case TDASH:
174
			if (beg == 0) {
175
				i = valdot;
176
				do {
177
					i--;
178
					if (i <= 0) {
179
						puts("Referencing before 1");
180
						return(-1);
181
					}
182
				} while ((message[i - 1].m_flag & MDELETED) != f);
183
				mark(i);
184
			}
185
			break;
186
187
		case TSTRING:
188
			if (beg != 0) {
189
				puts("Non-numeric second argument");
190
				return(-1);
191
			}
192
			other++;
193
			if (lexstring[0] == ':') {
194
				colresult = evalcol(lexstring[1]);
195
				if (colresult == 0) {
196
					printf("Unknown colon modifier \"%s\"\n",
197
					    lexstring);
198
					return(-1);
199
				}
200
				colmod |= colresult;
201
			} else {
202
				if ((com->c_argtype & ~(F|P|I|M|T|W|R))
203
							!= (MSGLIST|STRLIST))
204
					*np++ = savestr(lexstring);
205
			}
206
			break;
207
208
		case TDOLLAR:
209
		case TUP:
210
		case TDOT:
211
			lexnumber = metamess(lexstring[0], f);
212
			if (lexnumber == -1)
213
				return(-1);
214
			goto number;
215
216
		case TSTAR:
217
			if (other) {
218
				puts("Can't mix \"*\" with anything");
219
				return(-1);
220
			}
221
			star++;
222
			break;
223
224
		case TERROR:
225
			return(-1);
226
		}
227
		tok = scan(&bufp);
228
	}
229
	lastcolmod = colmod;
230
	*np = NULL;
231
	mc = 0;
232
	if (star) {
233
		for (i = 0; i < msgCount; i++)
234
			if ((message[i].m_flag & MDELETED) == f) {
235
				mark(i+1);
236
				mc++;
237
			}
238
		if (mc == 0) {
239
			puts("No applicable messages.");
240
			return(-1);
241
		}
242
		return(0);
243
	}
244
245
	/*
246
	 * If no numbers were given, mark all of the messages,
247
	 * so that we can unmark any whose sender was not selected
248
	 * if any user names were given.
249
	 */
250
	if ((np > namelist || colmod != 0) && mc == 0)
251
		for (i = 1; i <= msgCount; i++)
252
			if ((message[i-1].m_flag & MDELETED) == f)
253
				mark(i);
254
255
	/*
256
	 * If any names were given, go through and eliminate any
257
	 * messages whose senders were not requested.
258
	 */
259
	if (np > namelist) {
260
		for (i = 1; i <= msgCount; i++) {
261
			for (mc = 0, np = &namelist[0]; *np != NULL; np++)
262
				if (**np == '/') {
263
					if (matchsubj(*np, i)) {
264
						mc++;
265
						break;
266
					}
267
				}
268
				else {
269
					if (matchsender(*np, i)) {
270
						mc++;
271
						break;
272
					}
273
				}
274
			if (mc == 0)
275
				unmark(i);
276
		}
277
278
		/*
279
		 * Make sure we got some decent messages.
280
		 */
281
		mc = 0;
282
		for (i = 1; i <= msgCount; i++)
283
			if (message[i-1].m_flag & MMARK) {
284
				mc++;
285
				break;
286
			}
287
		if (mc == 0) {
288
			printf("No applicable messages from {%s",
289
				namelist[0]);
290
			for (np = &namelist[1]; *np != NULL; np++)
291
				printf(", %s", *np);
292
			puts("}");
293
			return(-1);
294
		}
295
	}
296
297
	/*
298
	 * If any colon modifiers were given, go through and
299
	 * unmark any messages which do not satisfy the modifiers.
300
	 */
301
	if (colmod != 0) {
302
		for (i = 1; i <= msgCount; i++) {
303
			struct coltab *colp;
304
305
			mp = &message[i - 1];
306
			for (colp = &coltab[0]; colp->co_char; colp++)
307
				if (colp->co_bit & colmod)
308
					if ((mp->m_flag & colp->co_mask)
309
					    != colp->co_equal)
310
						unmark(i);
311
		}
312
		for (mp = &message[0]; mp < &message[msgCount]; mp++)
313
			if (mp->m_flag & MMARK)
314
				break;
315
		if (mp >= &message[msgCount]) {
316
			struct coltab *colp;
317
318
			fputs("No messages satisfy", stdout);
319
			for (colp = &coltab[0]; colp->co_char; colp++)
320
				if (colp->co_bit & colmod)
321
					printf(" :%c", colp->co_char);
322
			putchar('\n');
323
			return(-1);
324
		}
325
	}
326
	return(0);
327
}
328
329
/*
330
 * Turn the character after a colon modifier into a bit
331
 * value.
332
 */
333
int
334
evalcol(int col)
335
{
336
	struct coltab *colp;
337
338
	if (col == 0)
339
		return(lastcolmod);
340
	for (colp = &coltab[0]; colp->co_char; colp++)
341
		if (colp->co_char == col)
342
			return(colp->co_bit);
343
	return(0);
344
}
345
346
/*
347
 * Check the passed message number for legality and proper flags.
348
 * If f is MDELETED, then either kind will do.  Otherwise, the message
349
 * has to be undeleted.
350
 */
351
int
352
check(int mesg, int f)
353
{
354
	struct message *mp;
355
356
	if (mesg < 1 || mesg > msgCount) {
357
		printf("%d: Invalid message number\n", mesg);
358
		return(-1);
359
	}
360
	mp = &message[mesg-1];
361
	if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
362
		printf("%d: Inappropriate message\n", mesg);
363
		return(-1);
364
	}
365
	return(0);
366
}
367
368
/*
369
 * Scan out the list of string arguments, shell style
370
 * for a RAWLIST.
371
 */
372
int
373
getrawlist(char *line, char **argv, int argc)
374
{
375
	char c, *cp, *cp2, quotec;
376
	int argn;
377
	char *linebuf, *linebuf2;
378
	size_t newsize, linebufsize = BUFSIZ;
379
380
8
	if ((linebuf = malloc(linebufsize)) == NULL)
381
		err(1, "malloc");
382
383
	argn = 0;
384
	cp = line;
385
26
	for (;;) {
386

86
		for (; *cp == ' ' || *cp == '\t'; cp++)
387
			;
388
26
		if (*cp == '\0')
389
			break;
390
22
		if (argn >= argc - 1) {
391
			puts("Too many elements in the list; excess discarded.");
392
			break;
393
		}
394
		cp2 = linebuf;
395
		quotec = '\0';
396
210
		while ((c = *cp) != '\0') {
397
			/* Alloc more space if necessary */
398
184
			if (cp2 - linebuf == linebufsize - 1) {
399
				newsize = linebufsize + BUFSIZ;
400
				linebuf2 = realloc(linebuf, newsize);
401
				if (linebuf2 == NULL)
402
					err(1, "realloc");
403
				linebuf = linebuf2;
404
				linebufsize = newsize;
405
				cp2 = linebuf + linebufsize - BUFSIZ - 1;
406
			}
407
184
			cp++;
408
184
			if (quotec != '\0') {
409
				if (c == quotec)
410
					quotec = '\0';
411
				else if (c == '\\')
412
					switch (c = *cp++) {
413
					case '\0':
414
						*cp2++ = '\\';
415
						cp--;
416
						break;
417
					case '0': case '1': case '2': case '3':
418
					case '4': case '5': case '6': case '7':
419
						c -= '0';
420
						if (*cp >= '0' && *cp <= '7')
421
							c = c * 8 + *cp++ - '0';
422
						if (*cp >= '0' && *cp <= '7')
423
							c = c * 8 + *cp++ - '0';
424
						*cp2++ = c;
425
						break;
426
					case 'b':
427
						*cp2++ = '\b';
428
						break;
429
					case 'f':
430
						*cp2++ = '\f';
431
						break;
432
					case 'n':
433
						*cp2++ = '\n';
434
						break;
435
					case 'r':
436
						*cp2++ = '\r';
437
						break;
438
					case 't':
439
						*cp2++ = '\t';
440
						break;
441
					case 'v':
442
						*cp2++ = '\v';
443
						break;
444
					default:
445
						*cp2++ = c;
446
					}
447
				else if (c == '^') {
448
					c = *cp++;
449
					if (c == '?')
450
						*cp2++ = '\177';
451
					/* null doesn't show up anyway */
452
					else if ((c >= 'A' && c <= '_') ||
453
						 (c >= 'a' && c <= 'z'))
454
						*cp2++ = c & 037;
455
					else {
456
						*cp2++ = '^';
457
						cp--;
458
					}
459
				} else
460
					*cp2++ = c;
461

368
			} else if (c == '"' || c == '\'')
462
				quotec = c;
463

350
			else if (c == ' ' || c == '\t')
464
				break;
465
			else
466
166
				*cp2++ = c;
467
		}
468
22
		*cp2 = '\0';
469
22
		argv[argn++] = savestr(linebuf);
470
	}
471
4
	argv[argn] = NULL;
472
4
	(void)free(linebuf);
473
4
	return(argn);
474
}
475
476
/*
477
 * Scan out a single lexical item and return its token number,
478
 * updating the string pointer passed **p.  Also, store the value
479
 * of the number or string scanned in lexnumber or lexstring as
480
 * appropriate.  In any event, store the scanned `thing' in lexstring.
481
 */
482
struct lex {
483
	char	l_char;
484
	char	l_token;
485
} singles[] = {
486
	{ '$',	TDOLLAR },
487
	{ '.',	TDOT },
488
	{ '^',	TUP },
489
	{ '*',	TSTAR },
490
	{ '-',	TDASH },
491
	{ '+',	TPLUS },
492
	{ '(',	TOPEN },
493
	{ ')',	TCLOSE },
494
	{ 0,	0 }
495
};
496
497
int
498
scan(char **sp)
499
{
500
	char *cp, *cp2;
501
	int c;
502
	struct lex *lp;
503
	int quotec;
504
505
	if (regretp >= 0) {
506
		strlcpy(lexstring, string_stack[regretp], STRINGLEN);
507
		lexnumber = numberstack[regretp];
508
		return(regretstack[regretp--]);
509
	}
510
	cp = *sp;
511
	cp2 = lexstring;
512
	c = (unsigned char)*cp++;
513
514
	/*
515
	 * strip away leading white space.
516
	 */
517
	while (c == ' ' || c == '\t')
518
		c = (unsigned char)*cp++;
519
520
	/*
521
	 * If no characters remain, we are at end of line,
522
	 * so report that.
523
	 */
524
	if (c == '\0') {
525
		*sp = --cp;
526
		return(TEOL);
527
	}
528
529
	/*
530
	 * If the leading character is a digit, scan
531
	 * the number and convert it on the fly.
532
	 * Return TNUMBER when done.
533
	 */
534
	if (isdigit(c)) {
535
		lexnumber = 0;
536
		while (isdigit(c)) {
537
			lexnumber = lexnumber*10 + c - '0';
538
			if (cp2 - lexstring < STRINGLEN - 1)
539
				*cp2++ = c;
540
			c = (unsigned char)*cp++;
541
		}
542
		*cp2 = '\0';
543
		*sp = --cp;
544
		return(TNUMBER);
545
	}
546
547
	/*
548
	 * Check for single character tokens; return such
549
	 * if found.
550
	 */
551
	for (lp = &singles[0]; lp->l_char != 0; lp++)
552
		if (c == lp->l_char) {
553
			lexstring[0] = c;
554
			lexstring[1] = '\0';
555
			*sp = cp;
556
			return(lp->l_token);
557
		}
558
559
	/*
560
	 * We've got a string!  Copy all the characters
561
	 * of the string into lexstring, until we see
562
	 * a null, space, or tab.
563
	 * If the lead character is a " or ', save it
564
	 * and scan until you get another.
565
	 */
566
	quotec = 0;
567
	if (c == '\'' || c == '"') {
568
		quotec = c;
569
		c = (unsigned char)*cp++;
570
	}
571
	while (c != '\0') {
572
		if (c == quotec) {
573
			cp++;
574
			break;
575
		}
576
		if (quotec == 0 && (c == ' ' || c == '\t'))
577
			break;
578
		if (cp2 - lexstring < STRINGLEN-1)
579
			*cp2++ = c;
580
		c = (unsigned char)*cp++;
581
	}
582
	if (quotec && c == 0) {
583
		fprintf(stderr, "Missing %c\n", quotec);
584
		return(TERROR);
585
	}
586
	*sp = --cp;
587
	*cp2 = '\0';
588
	return(TSTRING);
589
}
590
591
/*
592
 * Unscan the named token by pushing it onto the regret stack.
593
 */
594
void
595
regret(int token)
596
{
597
598
	if (++regretp >= REGDEP)
599
		errx(1, "Too many regrets");
600
	regretstack[regretp] = token;
601
	lexstring[STRINGLEN-1] = '\0';
602
	string_stack[regretp] = savestr(lexstring);
603
	numberstack[regretp] = lexnumber;
604
}
605
606
/*
607
 * Reset all the scanner global variables.
608
 */
609
void
610
scaninit(void)
611
{
612
613
	regretp = -1;
614
}
615
616
/*
617
 * Find the first message whose flags & m == f  and return
618
 * its message number.
619
 */
620
int
621
first(int f, int m)
622
{
623
	struct message *mp;
624
625
	if (msgCount == 0)
626
		return(0);
627
	f &= MDELETED;
628
	m &= MDELETED;
629
	for (mp = dot; mp < &message[msgCount]; mp++)
630
		if ((mp->m_flag & m) == f)
631
			return(mp - message + 1);
632
	for (mp = dot-1; mp >= &message[0]; mp--)
633
		if ((mp->m_flag & m) == f)
634
			return(mp - message + 1);
635
	return(0);
636
}
637
638
/*
639
 * See if the passed name sent the passed message number.  Return true
640
 * if so.
641
 */
642
int
643
matchsender(char *str, int mesg)
644
{
645
	char *cp;
646
647
	if (!*str)	/* null string matches nothing instead of everything */
648
		return(0);
649
	cp = nameof(&message[mesg - 1], 0);
650
	return (strcasestr(cp, str) != NULL);
651
}
652
653
/*
654
 * See if the passed name received the passed message number.  Return true
655
 * if so.
656
 */
657
static char *to_fields[] = { "to", "cc", "bcc", NULL };
658
659
int
660
matchto(char *str, int mesg)
661
{
662
	struct message *mp;
663
	char *cp, **to;
664
665
	str++;
666
667
	if (*str == 0)	/* null string matches nothing instead of everything */
668
		return(0);
669
670
	mp = &message[mesg-1];
671
672
	for (to = to_fields; *to; to++) {
673
		cp = hfield(*to, mp);
674
		if (cp != NULL && strcasestr(cp, str) != NULL)
675
			return(1);
676
	}
677
	return(0);
678
}
679
680
/*
681
 * See if the given string matches inside the subject field of the
682
 * given message.  For the purpose of the scan, we ignore case differences.
683
 * If it does, return true.  The string search argument is assumed to
684
 * have the form "/search-string."  If it is of the form "/," we use the
685
 * previous search string.
686
 */
687
char lastscan[STRINGLEN];
688
689
int
690
matchsubj(char *str, int mesg)
691
{
692
	struct message *mp;
693
	char *cp, *cp2;
694
695
	str++;
696
	if (*str == '\0')
697
		str = lastscan;
698
	else
699
		strlcpy(lastscan, str, sizeof(lastscan));
700
	mp = &message[mesg-1];
701
702
	/*
703
	 * Now look, ignoring case, for the word in the string.
704
	 */
705
	if (value("searchheaders") && (cp = strchr(str, ':'))) {
706
		/* Check for special case "/To:" */
707
		if (strncasecmp(str, "to:", 3) == 0)
708
			return(matchto(cp, mesg));
709
		*cp++ = '\0';
710
		cp2 = hfield(*str ? str : "subject", mp);
711
		cp[-1] = ':';
712
		str = cp;
713
		cp = cp2;
714
	} else {
715
		cp = hfield("subject", mp);
716
	}
717
	if (cp == NULL)
718
		return(0);
719
720
	return (strcasestr(cp, str) != NULL);
721
}
722
723
/*
724
 * Mark the named message by setting its mark bit.
725
 */
726
void
727
mark(int mesg)
728
{
729
	int i;
730
731
	i = mesg;
732
	if (i < 1 || i > msgCount)
733
		errx(1, "Bad message number to mark");
734
	message[i-1].m_flag |= MMARK;
735
}
736
737
/*
738
 * Unmark the named message.
739
 */
740
void
741
unmark(int mesg)
742
{
743
	int i;
744
745
	i = mesg;
746
	if (i < 1 || i > msgCount)
747
		errx(1, "Bad message number to unmark");
748
	message[i-1].m_flag &= ~MMARK;
749
}
750
751
/*
752
 * Return the message number corresponding to the passed meta character.
753
 */
754
int
755
metamess(int meta, int f)
756
{
757
	int c, m;
758
	struct message *mp;
759
760
	c = meta;
761
	switch (c) {
762
	case '^':
763
		/*
764
		 * First 'good' message left.
765
		 */
766
		for (mp = &message[0]; mp < &message[msgCount]; mp++)
767
			if ((mp->m_flag & MDELETED) == f)
768
				return(mp - &message[0] + 1);
769
		puts("No applicable messages");
770
		return(-1);
771
772
	case '$':
773
		/*
774
		 * Last 'good message left.
775
		 */
776
		for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
777
			if ((mp->m_flag & MDELETED) == f)
778
				return(mp - &message[0] + 1);
779
		puts("No applicable messages");
780
		return(-1);
781
782
	case '.':
783
		/*
784
		 * Current message.
785
		 */
786
		m = dot - &message[0] + 1;
787
		if ((dot->m_flag & MDELETED) != f) {
788
			printf("%d: Inappropriate message\n", m);
789
			return(-1);
790
		}
791
		return(m);
792
793
	default:
794
		printf("Unknown metachar (%c)\n", c);
795
		return(-1);
796
	}
797
}