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