1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/time.h>
33
34 #include <fcntl.h>
35
36 #include "rcv.h"
37 #include "extern.h"
38
39 /*
40 * Mail -- a mail program
41 *
42 * Auxiliary functions.
43 */
44
45 static char *save2str(char *, char *);
46
47 /*
48 * Return a pointer to a dynamic copy of the argument.
49 */
50 char *
savestr(char * str)51 savestr(char *str)
52 {
53 char *new;
54 int size = strlen(str) + 1;
55
56 if ((new = salloc(size)) != NULL)
57 bcopy(str, new, size);
58 return (new);
59 }
60
61 /*
62 * Make a copy of new argument incorporating old one.
63 */
64 static char *
save2str(char * str,char * old)65 save2str(char *str, char *old)
66 {
67 char *new;
68 int newsize = strlen(str) + 1;
69 int oldsize = old ? strlen(old) + 1 : 0;
70
71 if ((new = salloc(newsize + oldsize)) != NULL) {
72 if (oldsize) {
73 bcopy(old, new, oldsize);
74 new[oldsize - 1] = ' ';
75 }
76 bcopy(str, new + oldsize, newsize);
77 }
78 return (new);
79 }
80
81 /*
82 * Touch the named message by setting its MTOUCH flag.
83 * Touched messages have the effect of not being sent
84 * back to the system mailbox on exit.
85 */
86 void
touch(struct message * mp)87 touch(struct message *mp)
88 {
89
90 mp->m_flag |= MTOUCH;
91 if ((mp->m_flag & MREAD) == 0)
92 mp->m_flag |= MREAD|MSTATUS;
93 }
94
95 /*
96 * Test to see if the passed file name is a directory.
97 * Return true if it is.
98 */
99 int
isdir(char name[])100 isdir(char name[])
101 {
102 struct stat sbuf;
103
104 if (stat(name, &sbuf) < 0)
105 return (0);
106 return (S_ISDIR(sbuf.st_mode));
107 }
108
109 /*
110 * Count the number of arguments in the given string raw list.
111 */
112 int
argcount(char ** argv)113 argcount(char **argv)
114 {
115 char **ap;
116
117 for (ap = argv; *ap++ != NULL;)
118 ;
119 return (ap - argv - 1);
120 }
121
122 /*
123 * Return the desired header line from the passed message
124 * pointer (or NULL if the desired header field is not available).
125 */
126 char *
hfield(const char * field,struct message * mp)127 hfield(const char *field, struct message *mp)
128 {
129 FILE *ibuf;
130 char linebuf[LINESIZE];
131 int lc;
132 char *hfield;
133 char *colon, *oldhfield = NULL;
134
135 ibuf = setinput(mp);
136 if ((lc = mp->m_lines - 1) < 0)
137 return (NULL);
138 if (readline(ibuf, linebuf, LINESIZE) < 0)
139 return (NULL);
140 while (lc > 0) {
141 if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
142 return (oldhfield);
143 if ((hfield = ishfield(linebuf, colon, field)) != NULL)
144 oldhfield = save2str(hfield, oldhfield);
145 }
146 return (oldhfield);
147 }
148
149 /*
150 * Return the next header field found in the given message.
151 * Return >= 0 if something found, < 0 elsewise.
152 * "colon" is set to point to the colon in the header.
153 * Must deal with \ continuations & other such fraud.
154 */
155 int
gethfield(FILE * f,char linebuf[],int rem,char ** colon)156 gethfield(FILE *f, char linebuf[], int rem, char **colon)
157 {
158 char line2[LINESIZE];
159 char *cp, *cp2;
160 int c;
161
162 for (;;) {
163 if (--rem < 0)
164 return (-1);
165 if ((c = readline(f, linebuf, LINESIZE)) <= 0)
166 return (-1);
167 for (cp = linebuf; isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
168 cp++)
169 ;
170 if (*cp != ':' || cp == linebuf)
171 continue;
172 /*
173 * I guess we got a headline.
174 * Handle wraparounding
175 */
176 *colon = cp;
177 cp = linebuf + c;
178 for (;;) {
179 while (--cp >= linebuf && (*cp == ' ' || *cp == '\t'))
180 ;
181 cp++;
182 if (rem <= 0)
183 break;
184 ungetc(c = getc(f), f);
185 if (c != ' ' && c != '\t')
186 break;
187 if ((c = readline(f, line2, LINESIZE)) < 0)
188 break;
189 rem--;
190 for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++)
191 ;
192 c -= cp2 - line2;
193 if (cp + c >= linebuf + LINESIZE - 2)
194 break;
195 *cp++ = ' ';
196 bcopy(cp2, cp, c);
197 cp += c;
198 }
199 *cp = 0;
200 return (rem);
201 }
202 /* NOTREACHED */
203 }
204
205 /*
206 * Check whether the passed line is a header line of
207 * the desired breed. Return the field body, or 0.
208 */
209
210 char*
ishfield(char * linebuf,char * colon,const char * field)211 ishfield(char *linebuf, char *colon, const char *field)
212 {
213 char *cp = colon;
214
215 *cp = 0;
216 if (strcasecmp(linebuf, field) != 0) {
217 *cp = ':';
218 return (0);
219 }
220 *cp = ':';
221 for (cp++; *cp == ' ' || *cp == '\t'; cp++)
222 ;
223 return (cp);
224 }
225
226 /*
227 * Copy a string and lowercase the result.
228 * dsize: space left in buffer (including space for NULL)
229 */
230 void
istrncpy(char * dest,const char * src,size_t dsize)231 istrncpy(char *dest, const char *src, size_t dsize)
232 {
233
234 strlcpy(dest, src, dsize);
235 for (; *dest; dest++)
236 *dest = tolower((unsigned char)*dest);
237 }
238
239 /*
240 * The following code deals with input stacking to do source
241 * commands. All but the current file pointer are saved on
242 * the stack.
243 */
244
245 static int ssp; /* Top of file stack */
246 struct sstack {
247 FILE *s_file; /* File we were in. */
248 int s_cond; /* Saved state of conditionals */
249 int s_loading; /* Loading .mailrc, etc. */
250 };
251 #define SSTACK_SIZE 64 /* XXX was NOFILE. */
252 static struct sstack sstack[SSTACK_SIZE];
253
254 /*
255 * Pushdown current input file and switch to a new one.
256 * Set the global flag "sourcing" so that others will realize
257 * that they are no longer reading from a tty (in all probability).
258 */
259 int
source(void * arg)260 source(void *arg)
261 {
262 char **arglist = arg;
263 FILE *fi;
264 char *cp;
265
266 if ((cp = expand(*arglist)) == NULL)
267 return (1);
268 if ((fi = Fopen(cp, "r")) == NULL) {
269 warn("%s", cp);
270 return (1);
271 }
272 if (ssp >= SSTACK_SIZE - 1) {
273 printf("Too much \"sourcing\" going on.\n");
274 (void)Fclose(fi);
275 return (1);
276 }
277 sstack[ssp].s_file = input;
278 sstack[ssp].s_cond = cond;
279 sstack[ssp].s_loading = loading;
280 ssp++;
281 loading = 0;
282 cond = CANY;
283 input = fi;
284 sourcing++;
285 return (0);
286 }
287
288 /*
289 * Pop the current input back to the previous level.
290 * Update the "sourcing" flag as appropriate.
291 */
292 int
unstack(void)293 unstack(void)
294 {
295 if (ssp <= 0) {
296 printf("\"Source\" stack over-pop.\n");
297 sourcing = 0;
298 return (1);
299 }
300 (void)Fclose(input);
301 if (cond != CANY)
302 printf("Unmatched \"if\"\n");
303 ssp--;
304 cond = sstack[ssp].s_cond;
305 loading = sstack[ssp].s_loading;
306 input = sstack[ssp].s_file;
307 if (ssp == 0)
308 sourcing = loading;
309 return (0);
310 }
311
312 /*
313 * Touch the indicated file.
314 * This is nifty for the shell.
315 */
316 void
alter(char * name)317 alter(char *name)
318 {
319 struct timespec ts[2];
320
321 (void)clock_gettime(CLOCK_REALTIME, &ts[0]);
322 ts[0].tv_sec++;
323 ts[1].tv_sec = 0;
324 ts[1].tv_nsec = UTIME_OMIT;
325 (void)utimensat(AT_FDCWD, name, ts, 0);
326 }
327
328 /*
329 * Get sender's name from this message. If the message has
330 * a bunch of arpanet stuff in it, we may have to skin the name
331 * before returning it.
332 */
333 char *
nameof(struct message * mp,int reptype)334 nameof(struct message *mp, int reptype)
335 {
336 char *cp, *cp2;
337
338 cp = skin(name1(mp, reptype));
339 if (reptype != 0 || charcount(cp, '!') < 2)
340 return (cp);
341 cp2 = strrchr(cp, '!');
342 cp2--;
343 while (cp2 > cp && *cp2 != '!')
344 cp2--;
345 if (*cp2 == '!')
346 return (cp2 + 1);
347 return (cp);
348 }
349
350 /*
351 * Start of a "comment".
352 * Ignore it.
353 */
354 char *
skip_comment(char * cp)355 skip_comment(char *cp)
356 {
357 int nesting = 1;
358
359 for (; nesting > 0 && *cp; cp++) {
360 switch (*cp) {
361 case '\\':
362 if (cp[1])
363 cp++;
364 break;
365 case '(':
366 nesting++;
367 break;
368 case ')':
369 nesting--;
370 break;
371 }
372 }
373 return (cp);
374 }
375
376 /*
377 * Skin an arpa net address according to the RFC 822 interpretation
378 * of "host-phrase."
379 */
380 char *
skin(char * name)381 skin(char *name)
382 {
383 char *nbuf, *bufend, *cp, *cp2;
384 int c, gotlt, lastsp;
385
386 if (name == NULL)
387 return (NULL);
388 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
389 && strchr(name, ' ') == NULL)
390 return (name);
391
392 /* We assume that length(input) <= length(output) */
393 if ((nbuf = malloc(strlen(name) + 1)) == NULL)
394 err(1, "Out of memory");
395 gotlt = 0;
396 lastsp = 0;
397 bufend = nbuf;
398 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
399 switch (c) {
400 case '(':
401 cp = skip_comment(cp);
402 lastsp = 0;
403 break;
404
405 case '"':
406 /*
407 * Start of a "quoted-string".
408 * Copy it in its entirety.
409 */
410 while ((c = *cp) != '\0') {
411 cp++;
412 if (c == '"')
413 break;
414 if (c != '\\')
415 *cp2++ = c;
416 else if ((c = *cp) != '\0') {
417 *cp2++ = c;
418 cp++;
419 }
420 }
421 lastsp = 0;
422 break;
423
424 case ' ':
425 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
426 cp += 3, *cp2++ = '@';
427 else
428 if (cp[0] == '@' && cp[1] == ' ')
429 cp += 2, *cp2++ = '@';
430 else
431 lastsp = 1;
432 break;
433
434 case '<':
435 cp2 = bufend;
436 gotlt++;
437 lastsp = 0;
438 break;
439
440 case '>':
441 if (gotlt) {
442 gotlt = 0;
443 while ((c = *cp) != '\0' && c != ',') {
444 cp++;
445 if (c == '(')
446 cp = skip_comment(cp);
447 else if (c == '"')
448 while ((c = *cp) != '\0') {
449 cp++;
450 if (c == '"')
451 break;
452 if (c == '\\' && *cp != '\0')
453 cp++;
454 }
455 }
456 lastsp = 0;
457 break;
458 }
459 /* FALLTHROUGH */
460
461 default:
462 if (lastsp) {
463 lastsp = 0;
464 *cp2++ = ' ';
465 }
466 *cp2++ = c;
467 if (c == ',' && !gotlt &&
468 (*cp == ' ' || *cp == '"' || *cp == '<')) {
469 *cp2++ = ' ';
470 while (*cp == ' ')
471 cp++;
472 lastsp = 0;
473 bufend = cp2;
474 }
475 }
476 }
477 *cp2 = '\0';
478
479 if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
480 nbuf = cp;
481 return (nbuf);
482 }
483
484 /*
485 * Fetch the sender's name from the passed message.
486 * Reptype can be
487 * 0 -- get sender's name for display purposes
488 * 1 -- get sender's name for reply
489 * 2 -- get sender's name for Reply
490 */
491 char *
name1(struct message * mp,int reptype)492 name1(struct message *mp, int reptype)
493 {
494 char namebuf[LINESIZE];
495 char linebuf[LINESIZE];
496 char *cp, *cp2;
497 FILE *ibuf;
498 int first = 1;
499
500 if ((cp = hfield("from", mp)) != NULL)
501 return (cp);
502 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
503 return (cp);
504 ibuf = setinput(mp);
505 namebuf[0] = '\0';
506 if (readline(ibuf, linebuf, LINESIZE) < 0)
507 return (savestr(namebuf));
508 newname:
509 for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++)
510 ;
511 for (; *cp == ' ' || *cp == '\t'; cp++)
512 ;
513 for (cp2 = &namebuf[strlen(namebuf)];
514 *cp != '\0' && *cp != ' ' && *cp != '\t' &&
515 cp2 < namebuf + LINESIZE - 1;)
516 *cp2++ = *cp++;
517 *cp2 = '\0';
518 if (readline(ibuf, linebuf, LINESIZE) < 0)
519 return (savestr(namebuf));
520 if ((cp = strchr(linebuf, 'F')) == NULL)
521 return (savestr(namebuf));
522 if (strncmp(cp, "From", 4) != 0)
523 return (savestr(namebuf));
524 while ((cp = strchr(cp, 'r')) != NULL) {
525 if (strncmp(cp, "remote", 6) == 0) {
526 if ((cp = strchr(cp, 'f')) == NULL)
527 break;
528 if (strncmp(cp, "from", 4) != 0)
529 break;
530 if ((cp = strchr(cp, ' ')) == NULL)
531 break;
532 cp++;
533 if (first) {
534 cp2 = namebuf;
535 first = 0;
536 } else
537 cp2 = strrchr(namebuf, '!') + 1;
538 strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
539 strcat(namebuf, "!");
540 goto newname;
541 }
542 cp++;
543 }
544 return (savestr(namebuf));
545 }
546
547 /*
548 * Count the occurrences of c in str
549 */
550 int
charcount(char * str,int c)551 charcount(char *str, int c)
552 {
553 char *cp;
554 int i;
555
556 for (i = 0, cp = str; *cp != '\0'; cp++)
557 if (*cp == c)
558 i++;
559 return (i);
560 }
561
562 /*
563 * See if the given header field is supposed to be ignored.
564 */
565 int
isign(const char * field,struct ignoretab ignore[2])566 isign(const char *field, struct ignoretab ignore[2])
567 {
568 char realfld[LINESIZE];
569
570 if (ignore == ignoreall)
571 return (1);
572 /*
573 * Lower-case the string, so that "Status" and "status"
574 * will hash to the same place.
575 */
576 istrncpy(realfld, field, sizeof(realfld));
577 if (ignore[1].i_count > 0)
578 return (!member(realfld, ignore + 1));
579 else
580 return (member(realfld, ignore));
581 }
582
583 int
member(char * realfield,struct ignoretab * table)584 member(char *realfield, struct ignoretab *table)
585 {
586 struct ignore *igp;
587
588 for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link)
589 if (*igp->i_field == *realfield &&
590 equal(igp->i_field, realfield))
591 return (1);
592 return (0);
593 }
594