Fixed make_bcc_file () to use contents of From: in draft, if draft_from masquerade...
[mmh] / uip / dropsbr.c
1
2 /*
3  * dropsbr.c -- create/read/manipulate mail drops
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 #include <h/nmh.h>
13 #include <h/utils.h>
14
15 #include <h/mh.h>
16 #include <h/dropsbr.h>
17 #include <h/mts.h>
18 #include <h/tws.h>
19
20 #ifdef HAVE_ERRNO_H
21 # include <errno.h>
22 #endif
23
24 #ifdef NTOHLSWAP
25 # include <netinet/in.h>
26 #else
27 # undef ntohl
28 # define ntohl(n) (n)
29 #endif
30
31 #include <fcntl.h>
32
33 /*
34  * static prototypes
35  */
36 static int mbx_chk_mbox (int);
37 static int mbx_chk_mmdf (int);
38 static int map_open (char *, int);
39
40
41 /*
42  * Main entry point to open/create and lock
43  * a file or maildrop.
44  */
45
46 int
47 mbx_open (char *file, int mbx_style, uid_t uid, gid_t gid, mode_t mode)
48 {
49     int j, count, fd;
50     struct stat st;
51
52     j = 0;
53
54     /* attempt to open and lock file */
55     for (count = 4; count > 0; count--) {
56         if ((fd = lkopen (file, O_RDWR | O_CREAT | O_NONBLOCK, mode)) == NOTOK) {
57             switch (errno) {
58 #if defined(FCNTL_LOCKING) || defined(LOCKF_LOCKING)
59                 case EACCES:
60                 case EAGAIN:
61 #endif
62
63 #ifdef FLOCK_LOCKING
64                 case EWOULDBLOCK:
65 #endif
66                 case ETXTBSY: 
67                     j = errno;
68                     sleep (5);
69                     break;
70
71                 default: 
72                     /* just return error */
73                     return NOTOK;
74             }
75         }
76
77         /* good file descriptor */
78         break;
79     }
80
81     errno = j;
82
83     /*
84      * Return if we still failed after 4 attempts,
85      * or we just want to skip the sanity checks.
86      */
87     if (fd == NOTOK || mbx_style == OTHER_FORMAT)
88         return fd;
89
90     /*
91      * Do sanity checks on maildrop.
92      */
93     if (fstat (fd, &st) == NOTOK) {
94         /*
95          * The stat failed.  So we make sure file
96          * has right ownership/modes
97          */
98         chown (file, uid, gid);
99         chmod (file, mode);
100     } else if (st.st_size > (off_t) 0) {
101         int status;
102
103         /* check the maildrop */
104         switch (mbx_style) {
105             case MMDF_FORMAT: 
106             default: 
107                 status = mbx_chk_mmdf (fd);
108                 break;
109
110             case MBOX_FORMAT: 
111                 status = mbx_chk_mbox (fd);
112                 break;
113         }
114
115         /* if error, attempt to close it */
116         if (status == NOTOK) {
117             close (fd);
118             return NOTOK;
119         }
120     }
121
122     return fd;
123 }
124
125
126 /*
127  * Check/prepare MBOX style maildrop for appending.
128  */
129
130 static int
131 mbx_chk_mbox (int fd)
132 {
133     /* just seek to the end */
134     if (lseek (fd, (off_t) 0, SEEK_END) == (off_t) NOTOK)
135         return NOTOK;
136
137     return OK;
138 }
139
140
141 /*
142  * Check/prepare MMDF style maildrop for appending.
143  */
144
145 static int
146 mbx_chk_mmdf (int fd)
147 {
148     size_t count;
149     char ldelim[BUFSIZ];
150
151     count = strlen (mmdlm2);
152
153     /* casting -count to off_t, seem to break FreeBSD 2.2.6 */
154     if (lseek (fd, (long) (-count), SEEK_END) == (off_t) NOTOK)
155         return NOTOK;
156     if (read (fd, ldelim, count) != count)
157         return NOTOK;
158
159     ldelim[count] = 0;
160
161     if (strcmp (ldelim, mmdlm2)
162             && write (fd, "\n", 1) != 1
163             && write (fd, mmdlm2, count) != count)
164         return NOTOK;
165
166     return OK;
167 }
168
169
170 int
171 mbx_read (FILE *fp, long pos, struct drop **drops, int noisy)
172 {
173     register int len, size;
174     register long ld1, ld2;
175     register char *bp;
176     char buffer[BUFSIZ];
177     register struct drop *cp, *dp, *ep, *pp;
178
179     pp = (struct drop *) calloc ((size_t) (len = MAXFOLDER), sizeof(*dp));
180     if (pp == NULL) {
181         if (noisy)
182             admonish (NULL, "unable to allocate drop storage");
183         return NOTOK;
184     }
185
186     ld1 = (long) strlen (mmdlm1);
187     ld2 = (long) strlen (mmdlm2);
188
189     fseek (fp, pos, SEEK_SET);
190     for (ep = (dp = pp) + len - 1; fgets (buffer, sizeof(buffer), fp);) {
191         size = 0;
192         if (strcmp (buffer, mmdlm1) == 0)
193             pos += ld1, dp->d_start = (long) pos;
194         else {
195             dp->d_start = (long)pos , pos += (long) strlen (buffer);
196             for (bp = buffer; *bp; bp++, size++)
197                 if (*bp == '\n')
198                     size++;
199         }
200
201         while (fgets (buffer, sizeof(buffer), fp) != NULL)
202             if (strcmp (buffer, mmdlm2) == 0)
203                 break;
204             else {
205                 pos += (long) strlen (buffer);
206                 for (bp = buffer; *bp; bp++, size++)
207                     if (*bp == '\n')
208                         size++;
209             }
210
211         if (dp->d_start != (long) pos) {
212             dp->d_id = 0;
213             dp->d_size = (long) size;
214             dp->d_stop = pos;
215             dp++;
216         }
217         pos += ld2;
218
219         if (dp >= ep) {
220             register int    curlen = dp - pp;
221
222             cp = (struct drop *) mh_xrealloc ((char *) pp,
223                                     (size_t) (len += MAXFOLDER) * sizeof(*pp));
224             dp = cp + curlen, ep = (pp = cp) + len - 1;
225         }
226     }
227
228     if (dp == pp)
229         free ((char *) pp);
230     else
231         *drops = pp;
232     return (dp - pp);
233 }
234
235
236 int
237 mbx_write(char *mailbox, int md, FILE *fp, int id, long last,
238            long pos, off_t stop, int mapping, int noisy)
239 {
240     register int i, j, size;
241     off_t start;
242     long off;
243     register char *cp;
244     char buffer[BUFSIZ];
245
246     off = (long) lseek (md, (off_t) 0, SEEK_CUR);
247     j = strlen (mmdlm1);
248     if (write (md, mmdlm1, j) != j)
249         return NOTOK;
250     start = lseek (md, (off_t) 0, SEEK_CUR);
251     size = 0;
252
253     fseek (fp, pos, SEEK_SET);
254     while (fgets (buffer, sizeof(buffer), fp) && (pos < stop)) {
255         i = strlen (buffer);
256         for (j = 0; (j = stringdex (mmdlm1, buffer)) >= 0; buffer[j]++)
257             continue;
258         for (j = 0; (j = stringdex (mmdlm2, buffer)) >= 0; buffer[j]++)
259             continue;
260         if (write (md, buffer, i) != i)
261             return NOTOK;
262         pos += (long) i;
263         if (mapping)
264             for (cp = buffer; i-- > 0; size++)
265                 if (*cp++ == '\n')
266                     size++;
267     }
268
269     stop = lseek (md, (off_t) 0, SEEK_CUR);
270     j = strlen (mmdlm2);
271     if (write (md, mmdlm2, j) != j)
272         return NOTOK;
273     if (mapping)
274         map_write (mailbox, md, id, last, start, stop, off, size, noisy);
275
276     return OK;
277 }
278
279
280 /*
281  * Append message to end of file or maildrop.
282  */
283
284 int
285 mbx_copy (char *mailbox, int mbx_style, int md, int fd,
286           int mapping, char *text, int noisy)
287 {
288     int i, j, size;
289     off_t start, stop;
290     long pos;
291     char *cp, buffer[BUFSIZ];
292     FILE *fp;
293
294     pos = (long) lseek (md, (off_t) 0, SEEK_CUR);
295     size = 0;
296
297     switch (mbx_style) {
298         case MMDF_FORMAT: 
299         default: 
300             j = strlen (mmdlm1);
301             if (write (md, mmdlm1, j) != j)
302                 return NOTOK;
303             start = lseek (md, (off_t) 0, SEEK_CUR);
304
305             if (text) {
306                 i = strlen (text);
307                 if (write (md, text, i) != i)
308                     return NOTOK;
309                 for (cp = text; *cp++; size++)
310                     if (*cp == '\n')
311                         size++;
312             }
313                     
314             while ((i = read (fd, buffer, sizeof(buffer))) > 0) {
315                 for (j = 0;
316                         (j = stringdex (mmdlm1, buffer)) >= 0;
317                         buffer[j]++)
318                     continue;
319                 for (j = 0;
320                         (j = stringdex (mmdlm2, buffer)) >= 0;
321                         buffer[j]++)
322                     continue;
323                 if (write (md, buffer, i) != i)
324                     return NOTOK;
325                 if (mapping)
326                     for (cp = buffer; i-- > 0; size++)
327                         if (*cp++ == '\n')
328                             size++;
329             }
330
331             stop = lseek (md, (off_t) 0, SEEK_CUR);
332             j = strlen (mmdlm2);
333             if (write (md, mmdlm2, j) != j)
334                 return NOTOK;
335             if (mapping)
336                 map_write (mailbox, md, 0, (long) 0, start, stop, pos, size, noisy);
337
338             return (i != NOTOK ? OK : NOTOK);
339
340         case MBOX_FORMAT:
341             if ((j = dup (fd)) == NOTOK)
342                 return NOTOK;
343             if ((fp = fdopen (j, "r")) == NULL) {
344                 close (j);
345                 return NOTOK;
346             }
347             start = lseek (md, (off_t) 0, SEEK_CUR);
348
349             /* If text is given, we add it to top of message */
350             if (text) {
351                 i = strlen (text);
352                 if (write (md, text, i) != i)
353                     return NOTOK;
354                 for (cp = text; *cp++; size++)
355                     if (*cp == '\n')
356                         size++;
357             }
358                     
359             for (j = 0; fgets (buffer, sizeof(buffer), fp) != NULL; j++) {
360
361                 /*
362                  * Check the first line, and make some changes.
363                  */
364                 if (j == 0 && !text) {
365                     /*
366                      * Change the "Return-Path:" field (if in first line)
367                      * back to "From ".
368                      */
369                     if (!strncmp (buffer, "Return-Path:", 12)) {
370                         char tmpbuffer[BUFSIZ];
371                         char *tp, *ep, *fp;
372
373                         strncpy(tmpbuffer, buffer, sizeof(tmpbuffer));
374                         ep = tmpbuffer + 13;
375                         if (!(fp = strchr(ep + 1, ' ')))
376                             fp = strchr(ep + 1, '\n');
377                         tp = dctime(dlocaltimenow());
378                         snprintf (buffer, sizeof(buffer), "From %.*s  %s",
379                                 fp - ep, ep, tp);
380                     } else if (!strncmp (buffer, "X-Envelope-From:", 16)) {
381                         /*
382                          * Change the "X-Envelope-From:" field
383                          * (if first line) back to "From ".
384                          */
385                         char tmpbuffer[BUFSIZ];
386                         char *ep;
387
388                         strncpy(tmpbuffer, buffer, sizeof(tmpbuffer));
389                         ep = tmpbuffer + 17;
390                         snprintf (buffer, sizeof(buffer), "From %s", ep);
391                     } else if (strncmp (buffer, "From ", 5)) {
392                         /*
393                          * If there is already a "From " line,
394                          * then leave it alone.  Else we add one.
395                          */
396                         char tmpbuffer[BUFSIZ];
397                         char *tp, *ep;
398
399                         strncpy(tmpbuffer, buffer, sizeof(tmpbuffer));
400                         ep = "nobody@nowhere";
401                         tp = dctime(dlocaltimenow());
402                         snprintf (buffer, sizeof(buffer), "From %s  %s%s", ep, tp, tmpbuffer);
403                     }
404                 }
405
406                 /*
407                  * If this is not first line, and begins with
408                  * "From ", then prepend line with ">".
409                  */
410                 if (j != 0 && strncmp (buffer, "From ", 5) == 0) {
411                     write (md, ">", 1);
412                     size++;
413                 }
414                 i = strlen (buffer);
415                 if (write (md, buffer, i) != i) {
416                     fclose (fp);
417                     return NOTOK;
418                 }
419                 if (mapping)
420                     for (cp = buffer; i-- > 0; size++)
421                         if (*cp++ == '\n')
422                             size++;
423             }
424             if (write (md, "\n", 1) != 1) {
425                 fclose (fp);
426                 return NOTOK;
427             }
428             if (mapping)
429                 size += 2;
430
431             fclose (fp);
432             lseek (fd, (off_t) 0, SEEK_END);
433             stop = lseek (md, (off_t) 0, SEEK_CUR);
434             if (mapping)
435                 map_write (mailbox, md, 0, (long) 0, start, stop, pos, size, noisy);
436
437             return OK;
438     }
439 }
440
441
442 int
443 mbx_size (int md, off_t start, off_t stop)
444 {
445     register int i, fd;
446     register long pos;
447     register FILE *fp;
448
449     if ((fd = dup (md)) == NOTOK || (fp = fdopen (fd, "r")) == NULL) {
450         if (fd != NOTOK)
451             close (fd);
452         return NOTOK;
453     }
454
455     fseek (fp, start, SEEK_SET);
456     for (i = 0, pos = stop - start; pos-- > 0; i++)
457         if (fgetc (fp) == '\n')
458             i++;
459
460     fclose (fp);
461     return i;
462 }
463
464
465 /*
466  * Close and unlock file/maildrop.
467  */
468
469 int
470 mbx_close (char *mailbox, int md)
471 {
472     lkclose (md, mailbox);
473     return OK;
474 }
475
476
477 /*
478  * This function is performed implicitly by getbbent.c:
479  *     bb->bb_map = map_name (bb->bb_file);
480  */
481
482 char *
483 map_name (char *file)
484 {
485     register char *cp, *dp;
486     static char buffer[BUFSIZ];
487
488     if ((dp = strchr(cp = r1bindex (file, '/'), '.')) == NULL)
489         dp = cp + strlen (cp);
490     if (cp == file)
491         snprintf (buffer, sizeof(buffer), ".%.*s%s", dp - cp, cp, ".map");
492     else
493         snprintf (buffer, sizeof(buffer), "%.*s.%.*s%s",
494                 cp - file, file, dp - cp, cp, ".map");
495
496     return buffer;
497 }
498
499
500 int
501 map_read (char *file, long pos, struct drop **drops, int noisy)
502 {
503     register int i, md, msgp;
504     register char *cp;
505     struct drop d;
506     register struct drop *mp, *dp;
507
508     if ((md = open (cp = map_name (file), O_RDONLY)) == NOTOK
509             || map_chk (cp, md, mp = &d, pos, noisy)) {
510         if (md != NOTOK)
511             close (md);
512         return 0;
513     }
514
515     msgp = mp->d_id;
516     dp = (struct drop *) calloc ((size_t) (msgp + 1), sizeof(*dp));
517     if (dp == NULL) {
518         close (md);
519         return 0;
520     }
521
522     memcpy((char *) dp, (char *) mp, sizeof(*dp));
523
524     lseek (md, (off_t) sizeof(*mp), SEEK_SET);
525     if ((i = read (md, (char *) (dp + 1), msgp * sizeof(*dp))) < sizeof(*dp)) {
526         i = 0;
527         free ((char *) dp);
528     } else {
529 #ifdef NTOHLSWAP
530         register struct drop *tdp;
531         int j;
532
533         for (j = 0, tdp = dp; j < i / sizeof(*dp); j++, tdp++) {
534             tdp->d_id = ntohl(tdp->d_id);
535             tdp->d_size = ntohl(tdp->d_size);
536             tdp->d_start = ntohl(tdp->d_start);
537             tdp->d_stop = ntohl(tdp->d_stop);
538         }
539 #endif
540         *drops = dp;
541     }
542
543     close (md);
544
545     return (i / sizeof(*dp));
546 }
547
548
549 int
550 map_write (char *mailbox, int md, int id, long last, off_t start,
551            off_t stop, long pos, int size, int noisy)
552 {
553     register int i;
554     int clear, fd, td;
555     char *file;
556     register struct drop *dp;
557     struct drop d1, d2, *rp;
558     register FILE *fp;
559     struct stat st;
560
561     if ((fd = map_open (file = map_name (mailbox), md)) == NOTOK)
562         return NOTOK;
563
564     if ((fstat (fd, &st) == OK) && (st.st_size > 0))
565         clear = 0;
566     else
567         clear = 1;
568
569     if (!clear && map_chk (file, fd, &d1, pos, noisy)) {
570         unlink (file);
571         mbx_close (file, fd);
572         if ((fd = map_open (file, md)) == NOTOK)
573             return NOTOK;
574         clear++;
575     }
576
577     if (clear) {
578         if ((td = dup (md)) == NOTOK || (fp = fdopen (td, "r")) == NULL) {
579             if (noisy)
580                 admonish (file, "unable to %s", td != NOTOK ? "fdopen" : "dup");
581             if (td != NOTOK)
582                 close (td);
583             mbx_close (file, fd);
584             return NOTOK;
585         }
586
587         switch (i = mbx_read (fp, 0, &rp, noisy)) {
588             case NOTOK:
589                 fclose (fp);
590                 mbx_close (file, fd);
591                 return NOTOK;
592
593             case OK:
594                 fclose (fp);
595                 break;
596
597             default:
598                 d1.d_id = 0;
599                 for (dp = rp; i-- >0; dp++) {
600                     if (dp->d_start == start)
601                         dp->d_id = id;
602                     lseek (fd, (off_t) (++d1.d_id * sizeof(*dp)), SEEK_SET);
603                     if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) {
604                         if (noisy)
605                             admonish (file, "write error");
606                         mbx_close (file, fd);
607                         fclose (fp);
608                         return NOTOK;
609                     }
610                 }
611                 free ((char *) rp);
612                 fclose (fp);
613                 break;
614         }
615     }
616     else {
617         if (last == 0)
618             last = d1.d_start;
619         dp = &d2;
620         dp->d_id = id;
621         dp->d_size = (long) (size ? size : mbx_size (fd, start, stop));
622         dp->d_start = start;
623         dp->d_stop = stop;
624         lseek (fd, (off_t) (++d1.d_id * sizeof(*dp)), SEEK_SET);
625         if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) {
626             if (noisy)
627                 admonish (file, "write error");
628             mbx_close (file, fd);
629             return NOTOK;
630         }
631     }
632
633     dp = &d1;
634     dp->d_size = DRVRSN;
635     dp->d_start = (long) last;
636     dp->d_stop = lseek (md, (off_t) 0, SEEK_CUR);
637
638     lseek (fd, (off_t) 0, SEEK_SET);
639     if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) {
640         if (noisy)
641             admonish (file, "write error");
642         mbx_close (file, fd);
643         return NOTOK;
644     }
645
646     mbx_close (file, fd);
647
648     return OK;
649 }
650
651
652 static int
653 map_open (char *file, int md)
654 {
655     mode_t mode;
656     struct stat st;
657
658     mode = fstat (md, &st) != NOTOK ? (mode_t) (st.st_mode & 0777) : m_gmprot ();
659     return mbx_open (file, OTHER_FORMAT, st.st_uid, st.st_gid, mode);
660 }
661
662
663 int
664 map_chk (char *file, int fd, struct drop *dp, long pos, int noisy)
665 {
666     long count;
667     struct drop d, tmpd;
668     register struct drop *dl;
669
670     if (read (fd, (char *) &tmpd, sizeof(*dp)) != sizeof(*dp)) {
671 #ifdef notdef
672         admonish (NULL, "%s: missing or partial index", file);
673 #endif /* notdef */
674         return NOTOK;
675     }
676 #ifndef NTOHLSWAP
677     *dp = tmpd;         /* if ntohl(n)=(n), can use struct assign */
678 #else
679     dp->d_id    = ntohl(tmpd.d_id);
680     dp->d_size  = ntohl(tmpd.d_size);
681     dp->d_start = ntohl(tmpd.d_start);
682     dp->d_stop  = ntohl(tmpd.d_stop);
683 #endif
684     
685     if (dp->d_size != DRVRSN) {
686         if (noisy)
687             admonish (NULL, "%s: version mismatch (%d != %d)", file,
688                                 dp->d_size, DRVRSN);
689         return NOTOK;
690     }
691
692     if (dp->d_stop != pos) {
693         if (noisy && pos != (long) 0)
694             admonish (NULL,
695                     "%s: pointer mismatch or incomplete index (%ld!=%ld)", 
696                     file, dp->d_stop, (long) pos);
697         return NOTOK;
698     }
699
700     if ((long) ((dp->d_id + 1) * sizeof(*dp)) != (long) lseek (fd, (off_t) 0, SEEK_END)) {
701         if (noisy)
702             admonish (NULL, "%s: corrupt index(1)", file);
703         return NOTOK;
704     }
705
706     dl = &d;
707     count = (long) strlen (mmdlm2);
708     lseek (fd, (off_t) (dp->d_id * sizeof(*dp)), SEEK_SET);
709     if (read (fd, (char *) dl, sizeof(*dl)) != sizeof(*dl)
710             || (ntohl(dl->d_stop) != dp->d_stop
711                 && ntohl(dl->d_stop) + count != dp->d_stop)) {
712         if (noisy)
713             admonish (NULL, "%s: corrupt index(2)", file);
714         return NOTOK;
715     }
716
717     return OK;
718 }