Fixed broken swit numbering (there are more files to fix)
[mmh] / uip / inc.c
1 /*
2  * inc.c -- incorporate messages from a maildrop into a folder
3  *
4  * This code is Copyright (c) 2002, by the authors of nmh.  See the
5  * COPYRIGHT file in the root directory of the nmh distribution for
6  * complete copyright information.
7  */
8
9 #ifdef MAILGROUP
10 /* Revised: Sat Apr 14 17:08:17 PDT 1990 (marvit@hplabs)
11  * Added hpux hacks to set and reset gid to be "mail" as needed. The reset
12  * is necessary so inc'ed mail is the group of the inc'er, rather than
13  * "mail". We setgid to egid only when [un]locking the mail file. This
14  * is also a major security precaution which will not be explained here.
15  *
16  * Fri Feb  7 16:04:57 PST 1992  John Romine <bug-mh@ics.uci.edu>
17  *   NB: I'm not 100% sure that this setgid stuff is secure even now.
18  *
19  * See the *GROUPPRIVS() macros later. I'm reasonably happy with the setgid
20  * attribute. Running setuid root is probably not a terribly good idea, though.
21  *       -- Peter Maydell <pmaydell@chiark.greenend.org.uk>, 04/1998
22  *
23  * Peter Maydell's patch slightly modified for nmh 0.28-pre2.
24  * Ruud de Rooij <ruud@debian.org>  Wed, 22 Jul 1998 13:24:22 +0200
25  */
26 #endif
27
28 #include <h/mh.h>
29 #include <h/utils.h>
30 #include <fcntl.h>
31
32 #include <h/fmt_scan.h>
33 #include <h/scansbr.h>
34 #include <h/signals.h>
35 #include <h/tws.h>
36 #include <h/mts.h>
37 #include <errno.h>
38 #include <signal.h>
39
40 static struct swit switches[] = {
41 #define AUDSW  0
42         { "audit audit-file", 0 },
43 #define NAUDSW  1
44         { "noaudit", 0 },
45 #define CHGSW  2
46         { "changecur", 0 },
47 #define NCHGSW  3
48         { "nochangecur", 0 },
49 #define FILESW  4
50         { "file name", 0 },
51 #define FORMSW  5
52         { "form formatfile", 0 },
53 #define FMTSW  6
54         { "format string", 5 },
55 #define SILSW  7
56         { "silent", 0 },
57 #define NSILSW  8
58         { "nosilent", 0 },
59 #define TRNCSW  9
60         { "truncate", 0 },
61 #define NTRNCSW  10
62         { "notruncate", 0 },
63 #define WIDTHSW  11
64         { "width columns", 0 },
65 #define VERSIONSW  12
66         { "version", 0 },
67 #define HELPSW  13
68         { "help", 0 },
69 };
70
71 /* This is an attempt to simplify things by putting all the
72  * privilege ops into macros.
73  * *GROUPPRIVS() is related to handling the setgid MAIL property,
74  * and only applies if MAILGROUP is defined.
75  * Basically, SAVEGROUPPRIVS() is called right at the top of main()
76  * to initialise things, and then DROPGROUPPRIVS() and GETGROUPPRIVS()
77  * do the obvious thing. TRYDROPGROUPPRIVS() has to be safe to call
78  * before DROPUSERPRIVS() is called [this is needed because setgid()
79  * sets both effective and real uids if euid is root.]
80  *
81  * There's probably a better implementation if we're allowed to use
82  * BSD-style setreuid() rather than using POSIX saved-ids.
83  * Anyway, if you're euid root it's a bit pointless to drop the group
84  * permissions...
85  *
86  * I'm pretty happy that the security is good provided we aren't setuid root.
87  * The only things we trust with group=mail privilege are lkfopen()
88  * and lkfclose().
89  */
90
91 /*
92  * For setting and returning to "mail" gid
93  */
94 #ifdef MAILGROUP
95 static int return_gid;
96 /* easy case; we're not setuid root, so can drop group privs
97  * immediately.
98  */
99 #define TRYDROPGROUPPRIVS() DROPGROUPPRIVS()
100 #define DROPGROUPPRIVS() setgid(getgid())
101 #define GETGROUPPRIVS() setgid(return_gid)
102 #define SAVEGROUPPRIVS() return_gid = getegid()
103 #else
104 /* define *GROUPPRIVS() as null; this avoids having lots of "#ifdef MAILGROUP"s */
105 #define TRYDROPGROUPPRIVS()
106 #define DROPGROUPPRIVS()
107 #define GETGROUPPRIVS()
108 #define SAVEGROUPPRIVS()
109 #endif /* not MAILGROUP */
110
111 /* these variables have to be globals so that done() can correctly clean up the lockfile */
112 static int locked = 0;
113 static char *newmail;
114 static FILE *in;
115
116 /*
117  * prototypes
118  */
119 char *map_name(char *);
120
121 static void inc_done(int) NORETURN;
122
123
124 int
125 main (int argc, char **argv)
126 {
127         int chgflag = 1, trnflag = 1;
128         int noisy = 1, width = 0;
129         int hghnum = 0, msgnum = 0;
130         int incerr = 0; /* <0 if inc hits an error which means it should not truncate mailspool */
131         char *cp, *maildir = NULL, *folder = NULL;
132         char *format = NULL, *form = NULL;
133         char *audfile = NULL, *from = NULL;
134         char buf[BUFSIZ], **argp, *nfs, **arguments;
135         struct msgs *mp = NULL;
136         struct stat st, s1;
137         FILE *aud = NULL;
138         char b[MAXPATHLEN + 1];
139         /* copy of mail directory because the static gets overwritten */
140         char *maildir_copy = NULL;
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                         /* doesn't clean up locks! */
401                         advise (NULL, "aborted!");
402                         break;
403
404                 case SCNNUM:
405                         advise (NULL, "BUG in %s, number out of range", invo_name);
406                         break;
407
408                 default:
409                         advise (NULL, "BUG in %s, scan() botch (%d)", invo_name, incerr);
410                         break;
411
412                 case SCNMSG:
413                 case SCNENC:
414                         /*
415                          *  Run the external program hook on the message.
416                          */
417
418                         (void)snprintf(b, sizeof (b), "%s/%d", maildir_copy, msgnum + 1);
419                         (void)ext_hook("add-hook", b, (char *)0);
420
421                         if (aud)
422                                 fputs (scanl, aud);
423 #ifdef MHE
424                         if (mhe)
425                                 fputs (scanl, mhe);
426 #endif /* MHE */
427                         if (noisy)
428                                 fflush (stdout);
429                         msgnum++;
430                         mp->hghmsg++;
431                         mp->nummsg++;
432                         if (mp->lowmsg == 0)
433                                 mp->lowmsg = 1;
434                         clear_msg_flags (mp, msgnum);
435                         set_exists (mp, msgnum);
436                         set_unseen (mp, msgnum);
437                         mp->msgflags |= SEQMOD;
438                         continue;
439                 }
440                 /* If we get here there was some sort of error from scan(),
441                  * so stop processing anything more from the spool.
442                  */
443                 break;
444         }
445
446         if (incerr < 0) {  /* error */
447                 if (locked) {
448                         GETGROUPPRIVS();  /* Be sure we can unlock mail file */
449                         (void) lkfclose (in, newmail); in = NULL;
450                         DROPGROUPPRIVS();  /* And then return us to normal privileges */
451                 } else {
452                         fclose (in); in = NULL;
453                 }
454                 adios (NULL, "failed");
455         }
456
457         if (aud)
458                 fclose (aud);
459
460 #ifdef MHE
461         if (mhe)
462                 fclose (mhe);
463 #endif /* MHE */
464
465         if (noisy)
466                 fflush (stdout);
467
468         /*
469          * truncate file we are incorporating from
470          */
471         if (trnflag) {
472                 if (stat (newmail, &st) != NOTOK && s1.st_mtime != st.st_mtime)
473                         advise (NULL, "new messages have arrived!\007");
474                 else {
475                         int newfd;
476                         if ((newfd = creat (newmail, 0600)) != NOTOK)
477                                 close (newfd);
478                         else
479                                 admonish (newmail, "error zero'ing");
480                         unlink(map_name(newmail));
481                 }
482         } else {
483                 if (noisy)
484                         printf ("%s not zero'd\n", newmail);
485         }
486
487         if (msgnum == hghnum) {
488                 admonish (NULL, "no messages incorporated");
489         } else {
490                 context_replace (pfolder, folder);  /* update current folder */
491                 if (chgflag)
492                         mp->curmsg = hghnum + 1;
493                 mp->hghmsg = msgnum;
494                 if (mp->lowmsg == 0)
495                         mp->lowmsg = 1;
496                 if (chgflag)  /* sigh... */
497                         seq_setcur (mp, mp->curmsg);
498         }
499
500         /*
501          * unlock the mail spool
502          */
503         if (locked) {
504                 GETGROUPPRIVS();  /* Be sure we can unlock mail file */
505                 (void) lkfclose (in, newmail); in = NULL;
506                 DROPGROUPPRIVS();  /* And then return us to normal privileges */
507         } else {
508                 fclose (in); in = NULL;
509         }
510
511         seq_setunseen (mp, 0);  /* set the Unseen-Sequence */
512         seq_save (mp);  /* synchronize sequences   */
513         context_save ();  /* save the context file   */
514         done (0);
515         return 1;
516 }
517
518 static void
519 inc_done (int status)
520 {
521         if (locked) {
522                 GETGROUPPRIVS();
523                 lkfclose(in, newmail);
524                 DROPGROUPPRIVS();
525         }
526         exit (status);
527 }