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