Removed all POP support
[mmh] / uip / inc.c
1
2 /*
3  * inc.c -- incorporate messages from a maildrop into a folder
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 #ifdef MAILGROUP
11 /* Revised: Sat Apr 14 17:08:17 PDT 1990 (marvit@hplabs)
12  *    Added hpux hacks to set and reset gid to be "mail" as needed. The reset
13  *    is necessary so inc'ed mail is the group of the inc'er, rather than
14  *    "mail". We setgid to egid only when [un]locking the mail file. This
15  *    is also a major security precaution which will not be explained here.
16  *
17  * Fri Feb  7 16:04:57 PST 1992         John Romine <bug-mh@ics.uci.edu>
18  *   NB: I'm not 100% sure that this setgid stuff is secure even now.
19  *
20  * See the *GROUPPRIVS() macros later. I'm reasonably happy with the setgid
21  * attribute. Running setuid root is probably not a terribly good idea, though.
22  *       -- Peter Maydell <pmaydell@chiark.greenend.org.uk>, 04/1998
23  *
24  * Peter Maydell's patch slightly modified for nmh 0.28-pre2.
25  * Ruud de Rooij <ruud@debian.org>  Wed, 22 Jul 1998 13:24:22 +0200
26  */
27 #endif
28
29 #include <h/mh.h>
30 #include <h/utils.h>
31 #include <fcntl.h>
32
33 #include <h/fmt_scan.h>
34 #include <h/scansbr.h>
35 #include <h/signals.h>
36 #include <h/tws.h>
37 #include <h/mts.h>
38 #include <errno.h>
39 #include <signal.h>
40
41 static struct swit switches[] = {
42 #define AUDSW                      0
43     { "audit audit-file", 0 },
44 #define NAUDSW                     1
45     { "noaudit", 0 },
46 #define CHGSW                      2
47     { "changecur", 0 },
48 #define NCHGSW                     3
49     { "nochangecur", 0 },
50 #define FILESW                     4
51     { "file name", 0 },
52 #define FORMSW                     5
53     { "form formatfile", 0 },
54 #define FMTSW                      6
55     { "format string", 5 },
56 #define SILSW                     12
57     { "silent", 0 },
58 #define NSILSW                    13
59     { "nosilent", 0 },
60 #define TRNCSW                    14
61     { "truncate", 0 },
62 #define NTRNCSW                   15
63     { "notruncate", 0 },
64 #define WIDTHSW                   16
65     { "width columns", 0 },
66 #define VERSIONSW                 17
67     { "version", 0 },
68 #define HELPSW                    18
69     { "help", 0 },
70 };
71
72 /* This is an attempt to simplify things by putting all the
73  * privilege ops into macros.
74  * *GROUPPRIVS() is related to handling the setgid MAIL property,
75  * and only applies if MAILGROUP is defined.
76  * Basically, SAVEGROUPPRIVS() is called right at the top of main()
77  * to initialise things, and then DROPGROUPPRIVS() and GETGROUPPRIVS()
78  * do the obvious thing. TRYDROPGROUPPRIVS() has to be safe to call
79  * before DROPUSERPRIVS() is called [this is needed because setgid()
80  * sets both effective and real uids if euid is root.]
81  *
82  * There's probably a better implementation if we're allowed to use
83  * BSD-style setreuid() rather than using POSIX saved-ids.
84  * Anyway, if you're euid root it's a bit pointless to drop the group
85  * permissions...
86  *
87  * I'm pretty happy that the security is good provided we aren't setuid root.
88  * The only things we trust with group=mail privilege are lkfopen()
89  * and lkfclose().
90  */
91
92 /*
93  * For setting and returning to "mail" gid
94  */
95 #ifdef MAILGROUP
96 static int return_gid;
97 /* easy case; we're not setuid root, so can drop group privs
98  * immediately.
99  */
100 #define TRYDROPGROUPPRIVS() DROPGROUPPRIVS()
101 #define DROPGROUPPRIVS() setgid(getgid())
102 #define GETGROUPPRIVS() setgid(return_gid)
103 #define SAVEGROUPPRIVS() return_gid = getegid()
104 #else
105 /* define *GROUPPRIVS() as null; this avoids having lots of "#ifdef MAILGROUP"s */
106 #define TRYDROPGROUPPRIVS()
107 #define DROPGROUPPRIVS()
108 #define GETGROUPPRIVS()
109 #define SAVEGROUPPRIVS()
110 #endif /* not MAILGROUP */
111
112 /* these variables have to be globals so that done() can correctly clean up the lockfile */
113 static int locked = 0;
114 static char *newmail;
115 static FILE *in;
116
117 /*
118  * prototypes
119  */
120 char *map_name(char *);
121
122 static void inc_done(int) NORETURN;
123
124
125 int
126 main (int argc, char **argv)
127 {
128     int chgflag = 1, trnflag = 1;
129     int noisy = 1, width = 0;
130     int hghnum = 0, msgnum = 0;
131     int incerr = 0; /* <0 if inc hits an error which means it should not truncate mailspool */
132     char *cp, *maildir = NULL, *folder = NULL;
133     char *format = NULL, *form = NULL;
134     char *audfile = NULL, *from = NULL;
135     char buf[BUFSIZ], **argp, *nfs, **arguments;
136     struct msgs *mp = NULL;
137     struct stat st, s1;
138     FILE *aud = NULL;
139     char b[MAXPATHLEN + 1];
140     char *maildir_copy = NULL;  /* copy of mail directory because the static gets overwritten */
141
142 #ifdef MHE
143     FILE *mhe = NULL;
144 #endif
145
146     done=inc_done;
147
148 /* absolutely the first thing we do is save our privileges,
149  * and drop them if we can.
150  */
151     SAVEGROUPPRIVS();
152     TRYDROPGROUPPRIVS();
153
154 #ifdef LOCALE
155     setlocale(LC_ALL, "");
156 #endif
157     invo_name = r1bindex (argv[0], '/');
158
159     /* read user profile/context */
160     context_read();
161
162     mts_init (invo_name);
163     arguments = getarguments (invo_name, argc, argv, 1);
164     argp = arguments;
165
166     while ((cp = *argp++)) {
167         if (*cp == '-') {
168             switch (smatch (++cp, switches)) {
169             case AMBIGSW: 
170                 ambigsw (cp, switches);
171                 done (1);
172             case UNKWNSW: 
173                 adios (NULL, "-%s unknown", cp);
174
175             case HELPSW: 
176                 snprintf (buf, sizeof(buf), "%s [+folder] [switches]", invo_name);
177                 print_help (buf, switches, 1);
178                 done (1);
179             case VERSIONSW:
180                 print_version(invo_name);
181                 done (1);
182
183             case AUDSW: 
184                 if (!(cp = *argp++) || *cp == '-')
185                     adios (NULL, "missing argument to %s", argp[-2]);
186                 audfile = getcpy (m_maildir (cp));
187                 continue;
188             case NAUDSW: 
189                 audfile = NULL;
190                 continue;
191
192             case CHGSW: 
193                 chgflag++;
194                 continue;
195             case NCHGSW: 
196                 chgflag = 0;
197                 continue;
198
199             /*
200              * The flag `trnflag' has the value:
201              *
202              * 2 if -truncate is given
203              * 1 by default (truncating is default)
204              * 0 if -notruncate is given
205              */
206             case TRNCSW: 
207                 trnflag = 2;
208                 continue;
209             case NTRNCSW: 
210                 trnflag = 0;
211                 continue;
212
213             case FILESW: 
214                 if (!(cp = *argp++) || *cp == '-')
215                     adios (NULL, "missing argument to %s", argp[-2]);
216                 from = path (cp, TFILE);
217
218                 /*
219                  * If the truncate file is in default state,
220                  * change to not truncate.
221                  */
222                 if (trnflag == 1)
223                     trnflag = 0;
224                 continue;
225
226             case SILSW: 
227                 noisy = 0;
228                 continue;
229             case NSILSW: 
230                 noisy++;
231                 continue;
232
233             case FORMSW: 
234                 if (!(form = *argp++) || *form == '-')
235                     adios (NULL, "missing argument to %s", argp[-2]);
236                 format = NULL;
237                 continue;
238             case FMTSW: 
239                 if (!(format = *argp++) || *format == '-')
240                     adios (NULL, "missing argument to %s", argp[-2]);
241                 form = NULL;
242                 continue;
243
244             case WIDTHSW: 
245                 if (!(cp = *argp++) || *cp == '-')
246                     adios (NULL, "missing argument to %s", argp[-2]);
247                 width = atoi (cp);
248                 continue;
249             }
250         }
251         if (*cp == '+' || *cp == '@') {
252             if (folder)
253                 adios (NULL, "only one folder at a time!");
254             else
255                 folder = pluspath (cp);
256         } else {
257             adios (NULL, "usage: %s [+folder] [switches]", invo_name);
258         }
259     }
260
261     /* NOTE: above this point you should use TRYDROPGROUPPRIVS(),
262      * not DROPGROUPPRIVS().
263      */
264     /* guarantee dropping group priveleges; we might not have done so earlier */
265     DROPGROUPPRIVS();
266
267     /*
268      * We will get the mail from a file
269      * (typically the standard maildrop)
270      */
271     if (from)
272         newmail = from;
273     else if ((newmail = getenv ("MAILDROP")) && *newmail)
274         newmail = m_mailpath (newmail);
275     else if ((newmail = context_find ("maildrop")) && *newmail)
276         newmail = m_mailpath (newmail);
277     else {
278         newmail = concat (MAILDIR, "/", MAILFIL, NULL);
279     }
280     if (stat (newmail, &s1) == NOTOK || s1.st_size == 0)
281         adios (NULL, "no mail to incorporate");
282
283     if ((cp = strdup(newmail)) == (char *)0)
284         adios (NULL, "error allocating memory to copy newmail");
285
286     newmail = cp;
287
288     if (!context_find ("path"))
289         free (path ("./", TFOLDER));
290     if (!folder)
291         folder = getfolder (0);
292     maildir = m_maildir (folder);
293
294     if ((maildir_copy = strdup(maildir)) == (char *)0)
295         adios (maildir, "error allocating memory to copy maildir");
296
297     if (!folder_exists(maildir)) {
298         /* If the folder doesn't exist, and we're given the -silent flag,
299          * just fail.
300          */
301         if (noisy)
302             create_folder(maildir, 0, done);
303         else
304             done (1);
305     }
306
307     if (chdir (maildir) == NOTOK)
308         adios (maildir, "unable to change directory to");
309
310     /* read folder and create message structure */
311     if (!(mp = folder_read (folder)))
312         adios (NULL, "unable to read folder %s", folder);
313
314     if (access (newmail, W_OK) != NOTOK) {
315         locked++;
316         if (trnflag) {
317         SIGNAL (SIGHUP, SIG_IGN);
318         SIGNAL (SIGINT, SIG_IGN);
319         SIGNAL (SIGQUIT, SIG_IGN);
320         SIGNAL (SIGTERM, SIG_IGN);
321     }
322
323     GETGROUPPRIVS();       /* Reset gid to lock mail file */
324     in = lkfopen (newmail, "r");
325     DROPGROUPPRIVS();
326     if (in == NULL)
327         adios (NULL, "unable to lock and fopen %s", newmail);
328         fstat (fileno(in), &s1);
329     } else {
330         trnflag = 0;
331         if ((in = fopen (newmail, "r")) == NULL)
332         adios (newmail, "unable to read");
333     }
334
335     /* This shouldn't be necessary but it can't hurt. */
336     DROPGROUPPRIVS();
337
338     if (audfile) {
339         int i;
340         if ((i = stat (audfile, &st)) == NOTOK)
341             advise (NULL, "Creating Receive-Audit: %s", audfile);
342         if ((aud = fopen (audfile, "a")) == NULL)
343             adios (audfile, "unable to append to");
344         else if (i == NOTOK)
345             chmod (audfile, m_gmprot ());
346
347         fprintf (aud, from ? "<<inc>> %s  -ms %s\n" : "<<inc>> %s\n",
348                  dtimenow (0), from);
349     }
350
351 #ifdef MHE
352     if (context_find ("mhe")) {
353         int i;
354         cp = concat (maildir, "/++", NULL);
355         i = stat (cp, &st);
356         if ((mhe = fopen (cp, "a")) == NULL)
357             admonish (cp, "unable to append to");
358         else
359             if (i == NOTOK)
360                 chmod (cp, m_gmprot ());
361         free (cp);
362     }
363 #endif /* MHE */
364
365     /* Get new format string */
366     nfs = new_fs (form, format, FORMAT);
367
368     if (noisy) {
369         printf ("Incorporating new mail into %s...\n\n", folder);
370         fflush (stdout);
371     }
372
373     /*
374      * Get the mail from file (usually mail spool)
375      */
376     m_unknown (in);             /* the MAGIC invocation... */
377     hghnum = msgnum = mp->hghmsg;
378     for (;;) {
379         /*
380          * Check if we need to allocate more space for message status.
381          * If so, then add space for an additional 100 messages.
382          */
383         if (msgnum >= mp->hghoff
384             && !(mp = folder_realloc (mp, mp->lowoff, mp->hghoff + 100))) {
385             advise (NULL, "unable to allocate folder storage");
386             incerr = NOTOK;
387             break;
388         }
389
390         /* create scanline for new message */
391         switch (incerr = scan (in, msgnum + 1, msgnum + 1, nfs, width,
392                       msgnum == hghnum && chgflag, 1, NULL, 0L, noisy)) {
393         case SCNFAT:
394         case SCNEOF: 
395         break;
396
397         case SCNERR:
398         if (aud)
399             fputs ("inc aborted!\n", aud);
400         advise (NULL, "aborted!");      /* doesn't clean up locks! */
401         break;
402
403         case SCNNUM: 
404         advise (NULL, "BUG in %s, number out of range", invo_name);
405         break;
406
407         default: 
408         advise (NULL, "BUG in %s, scan() botch (%d)", invo_name, incerr);
409         break;
410
411         case SCNMSG:
412         case SCNENC:
413         /*
414          *  Run the external program hook on the message.
415          */
416
417         (void)snprintf(b, sizeof (b), "%s/%d", maildir_copy, msgnum + 1);
418         (void)ext_hook("add-hook", b, (char *)0);
419
420         if (aud)
421             fputs (scanl, aud);
422 #ifdef MHE
423         if (mhe)
424             fputs (scanl, mhe);
425 #endif /* MHE */
426         if (noisy)
427             fflush (stdout);
428             msgnum++;
429             mp->hghmsg++;
430             mp->nummsg++;
431             if (mp->lowmsg == 0)
432                 mp->lowmsg = 1;
433             clear_msg_flags (mp, msgnum);
434             set_exists (mp, msgnum);
435             set_unseen (mp, msgnum);
436             mp->msgflags |= SEQMOD;
437             continue;
438         }
439         /* If we get here there was some sort of error from scan(),
440          * so stop processing anything more from the spool.
441          */
442         break;
443     }
444
445     if (incerr < 0) {           /* error */
446         if (locked) {
447             GETGROUPPRIVS();      /* Be sure we can unlock mail file */
448             (void) lkfclose (in, newmail); in = NULL;
449             DROPGROUPPRIVS();    /* And then return us to normal privileges */
450         } else {
451             fclose (in); in = NULL;
452         }
453         adios (NULL, "failed");
454     }
455
456     if (aud)
457         fclose (aud);
458
459 #ifdef MHE
460     if (mhe)
461         fclose (mhe);
462 #endif /* MHE */
463
464     if (noisy)
465         fflush (stdout);
466
467     /*
468      * truncate file we are incorporating from
469      */
470     if (trnflag) {
471         if (stat (newmail, &st) != NOTOK && s1.st_mtime != st.st_mtime)
472             advise (NULL, "new messages have arrived!\007");
473         else {
474             int newfd;
475             if ((newfd = creat (newmail, 0600)) != NOTOK)
476                 close (newfd);
477             else
478                 admonish (newmail, "error zero'ing");
479             unlink(map_name(newmail));
480         }
481     } else {
482         if (noisy)
483             printf ("%s not zero'd\n", newmail);
484     }
485
486     if (msgnum == hghnum) {
487         admonish (NULL, "no messages incorporated");
488     } else {
489         context_replace (pfolder, folder);      /* update current folder */
490         if (chgflag)
491             mp->curmsg = hghnum + 1;
492         mp->hghmsg = msgnum;
493         if (mp->lowmsg == 0)
494             mp->lowmsg = 1;
495         if (chgflag)            /* sigh... */
496             seq_setcur (mp, mp->curmsg);
497     }
498
499     /*
500      * unlock the mail spool
501      */
502     if (locked) {
503         GETGROUPPRIVS();        /* Be sure we can unlock mail file */
504         (void) lkfclose (in, newmail); in = NULL;
505         DROPGROUPPRIVS();       /* And then return us to normal privileges */
506     } else {
507         fclose (in); in = NULL;
508     }
509
510     seq_setunseen (mp, 0);      /* set the Unseen-Sequence */
511     seq_save (mp);              /* synchronize sequences   */
512     context_save ();            /* save the context file   */
513     done (0);
514     return 1;
515 }
516
517 static void
518 inc_done (int status)
519 {
520     if (locked)
521     {
522         GETGROUPPRIVS();
523         lkfclose(in, newmail);
524         DROPGROUPPRIVS();
525     }
526     exit (status);
527 }