Remove RCS keywords, since they no longer work after git migration.
[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     lkclose (md, mailbox);
471     return OK;
472 }
473
474
475 /*
476  * This function is performed implicitly by getbbent.c:
477  *     bb->bb_map = map_name (bb->bb_file);
478  */
479
480 char *
481 map_name (char *file)
482 {
483     register char *cp, *dp;
484     static char buffer[BUFSIZ];
485
486     if ((dp = strchr(cp = r1bindex (file, '/'), '.')) == NULL)
487         dp = cp + strlen (cp);
488     if (cp == file)
489         snprintf (buffer, sizeof(buffer), ".%.*s%s", (int)(dp - cp), cp, ".map");
490     else
491         snprintf (buffer, sizeof(buffer), "%.*s.%.*s%s",
492                 (int)(cp - file), file, (int)(dp - cp), cp, ".map");
493
494     return buffer;
495 }
496
497
498 int
499 map_read (char *file, long pos, struct drop **drops, int noisy)
500 {
501     register int i, md, msgp;
502     register char *cp;
503     struct drop d;
504     register struct drop *mp, *dp;
505
506     if ((md = open (cp = map_name (file), O_RDONLY)) == NOTOK
507             || map_chk (cp, md, mp = &d, pos, noisy)) {
508         if (md != NOTOK)
509             close (md);
510         return 0;
511     }
512
513     msgp = mp->d_id;
514     dp = (struct drop *) calloc ((size_t) (msgp + 1), sizeof(*dp));
515     if (dp == NULL) {
516         close (md);
517         return 0;
518     }
519
520     memcpy((char *) dp, (char *) mp, sizeof(*dp));
521
522     lseek (md, (off_t) sizeof(*mp), SEEK_SET);
523     if ((i = read (md, (char *) (dp + 1), msgp * sizeof(*dp))) < sizeof(*dp)) {
524         i = 0;
525         free ((char *) dp);
526     } else {
527 #ifdef NTOHLSWAP
528         register struct drop *tdp;
529         int j;
530
531         for (j = 0, tdp = dp; j < i / sizeof(*dp); j++, tdp++) {
532             tdp->d_id = ntohl(tdp->d_id);
533             tdp->d_size = ntohl(tdp->d_size);
534             tdp->d_start = ntohl(tdp->d_start);
535             tdp->d_stop = ntohl(tdp->d_stop);
536         }
537 #endif
538         *drops = dp;
539     }
540
541     close (md);
542
543     return (i / sizeof(*dp));
544 }
545
546
547 int
548 map_write (char *mailbox, int md, int id, long last, off_t start,
549            off_t stop, long pos, int size, int noisy)
550 {
551     register int i;
552     int clear, fd, td;
553     char *file;
554     register struct drop *dp;
555     struct drop d1, d2, *rp;
556     register FILE *fp;
557     struct stat st;
558
559     if ((fd = map_open (file = map_name (mailbox), md)) == NOTOK)
560         return NOTOK;
561
562     if ((fstat (fd, &st) == OK) && (st.st_size > 0))
563         clear = 0;
564     else
565         clear = 1;
566
567     if (!clear && map_chk (file, fd, &d1, pos, noisy)) {
568         unlink (file);
569         mbx_close (file, fd);
570         if ((fd = map_open (file, md)) == NOTOK)
571             return NOTOK;
572         clear++;
573     }
574
575     if (clear) {
576         if ((td = dup (md)) == NOTOK || (fp = fdopen (td, "r")) == NULL) {
577             if (noisy)
578                 admonish (file, "unable to %s", td != NOTOK ? "fdopen" : "dup");
579             if (td != NOTOK)
580                 close (td);
581             mbx_close (file, fd);
582             return NOTOK;
583         }
584
585         switch (i = mbx_read (fp, 0, &rp, noisy)) {
586             case NOTOK:
587                 fclose (fp);
588                 mbx_close (file, fd);
589                 return NOTOK;
590
591             case OK:
592                 fclose (fp);
593                 break;
594
595             default:
596                 d1.d_id = 0;
597                 for (dp = rp; i-- >0; dp++) {
598                     if (dp->d_start == start)
599                         dp->d_id = id;
600                     lseek (fd, (off_t) (++d1.d_id * sizeof(*dp)), SEEK_SET);
601                     if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) {
602                         if (noisy)
603                             admonish (file, "write error");
604                         mbx_close (file, fd);
605                         fclose (fp);
606                         return NOTOK;
607                     }
608                 }
609                 free ((char *) rp);
610                 fclose (fp);
611                 break;
612         }
613     }
614     else {
615         if (last == 0)
616             last = d1.d_start;
617         dp = &d2;
618         dp->d_id = id;
619         dp->d_size = (long) (size ? size : mbx_size (fd, start, stop));
620         dp->d_start = start;
621         dp->d_stop = stop;
622         lseek (fd, (off_t) (++d1.d_id * sizeof(*dp)), SEEK_SET);
623         if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) {
624             if (noisy)
625                 admonish (file, "write error");
626             mbx_close (file, fd);
627             return NOTOK;
628         }
629     }
630
631     dp = &d1;
632     dp->d_size = DRVRSN;
633     dp->d_start = (long) last;
634     dp->d_stop = lseek (md, (off_t) 0, SEEK_CUR);
635
636     lseek (fd, (off_t) 0, SEEK_SET);
637     if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) {
638         if (noisy)
639             admonish (file, "write error");
640         mbx_close (file, fd);
641         return NOTOK;
642     }
643
644     mbx_close (file, fd);
645
646     return OK;
647 }
648
649
650 static int
651 map_open (char *file, int md)
652 {
653     mode_t mode;
654     struct stat st;
655
656     mode = fstat (md, &st) != NOTOK ? (mode_t) (st.st_mode & 0777) : m_gmprot ();
657     return mbx_open (file, OTHER_FORMAT, st.st_uid, st.st_gid, mode);
658 }
659
660
661 int
662 map_chk (char *file, int fd, struct drop *dp, long pos, int noisy)
663 {
664     long count;
665     struct drop d, tmpd;
666     register struct drop *dl;
667
668     if (read (fd, (char *) &tmpd, sizeof(*dp)) != sizeof(*dp)) {
669 #ifdef notdef
670         admonish (NULL, "%s: missing or partial index", file);
671 #endif /* notdef */
672         return NOTOK;
673     }
674 #ifndef NTOHLSWAP
675     *dp = tmpd;         /* if ntohl(n)=(n), can use struct assign */
676 #else
677     dp->d_id    = ntohl(tmpd.d_id);
678     dp->d_size  = ntohl(tmpd.d_size);
679     dp->d_start = ntohl(tmpd.d_start);
680     dp->d_stop  = ntohl(tmpd.d_stop);
681 #endif
682     
683     if (dp->d_size != DRVRSN) {
684         if (noisy)
685             admonish (NULL, "%s: version mismatch (%d != %d)", file,
686                                 dp->d_size, DRVRSN);
687         return NOTOK;
688     }
689
690     if (dp->d_stop != pos) {
691         if (noisy && pos != (long) 0)
692             admonish (NULL,
693                     "%s: pointer mismatch or incomplete index (%ld!=%ld)", 
694                     file, dp->d_stop, (long) pos);
695         return NOTOK;
696     }
697
698     if ((long) ((dp->d_id + 1) * sizeof(*dp)) != (long) lseek (fd, (off_t) 0, SEEK_END)) {
699         if (noisy)
700             admonish (NULL, "%s: corrupt index(1)", file);
701         return NOTOK;
702     }
703
704     dl = &d;
705     count = (long) strlen (mmdlm2);
706     lseek (fd, (off_t) (dp->d_id * sizeof(*dp)), SEEK_SET);
707     if (read (fd, (char *) dl, sizeof(*dl)) != sizeof(*dl)
708             || (ntohl(dl->d_stop) != dp->d_stop
709                 && ntohl(dl->d_stop) + count != dp->d_stop)) {
710         if (noisy)
711             admonish (NULL, "%s: corrupt index(2)", file);
712         return NOTOK;
713     }
714
715     return OK;
716 }