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