2 ** inc.c -- incorporate messages from a maildrop into a folder
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.
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.
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.
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
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
32 #include <h/fmt_scan.h>
33 #include <h/scansbr.h>
34 #include <h/signals.h>
43 #ifdef HAVE_SYS_PARAM_H
44 # include <sys/param.h>
47 static struct swit switches[] = {
49 { "audit audit-file", 0 },
59 { "form formatfile", 0 },
69 { "width columns", 0 },
77 char *version=VERSION;
80 ** This is an attempt to simplify things by putting all the
81 ** privilege ops into macros.
82 ** *GROUPPRIVS() is related to handling the setgid MAIL property,
83 ** and only applies if MAILGROUP is defined.
84 ** Basically, SAVEGROUPPRIVS() is called right at the top of main()
85 ** to initialise things, and then DROPGROUPPRIVS() and GETGROUPPRIVS()
86 ** do the obvious thing. TRYDROPGROUPPRIVS() has to be safe to call
87 ** before DROPUSERPRIVS() is called [this is needed because setgid()
88 ** sets both effective and real uids if euid is root.]
90 ** There's probably a better implementation if we're allowed to use
91 ** BSD-style setreuid() rather than using POSIX saved-ids.
92 ** Anyway, if you're euid root it's a bit pointless to drop the group
95 ** I'm pretty happy that the security is good provided we aren't setuid root.
96 ** The only things we trust with group=mail privilege are lkfopen()
101 ** For setting and returning to "mail" gid
104 static int return_gid;
106 ** easy case; we're not setuid root, so can drop group privs immediately.
108 #define TRYDROPGROUPPRIVS() DROPGROUPPRIVS()
109 #define DROPGROUPPRIVS() \
110 if (setegid(getgid()) != 0) { \
111 advise ("setegid", "unable to set group to %ld", (long) getgid()); \
114 #define GETGROUPPRIVS() \
115 if (setegid(return_gid) != 0) { \
116 advise ("setegid", "unable to set group to %ld", (long) return_gid); \
119 #define SAVEGROUPPRIVS() return_gid = getegid()
121 /* define *GROUPPRIVS() as null; this avoids having lots of "#ifdef MAILGROUP"s */
122 #define TRYDROPGROUPPRIVS()
123 #define DROPGROUPPRIVS()
124 #define GETGROUPPRIVS()
125 #define SAVEGROUPPRIVS()
126 #endif /* not MAILGROUP */
129 ** these variables have to be globals so that done() can correctly clean
132 static int locked = 0;
133 static char *newmail;
143 main(int argc, char **argv)
145 int chgflag = 1, trnflag = 1;
146 int noisy = 1, width = 0;
147 int hghnum = 0, msgnum = 0;
149 ** <0 if inc hits an error which means it should
150 ** not truncate mailspool
152 char *cp, *maildir = NULL, *folder = NULL;
154 char *audfile = NULL, *from = NULL;
155 char buf[BUFSIZ], **argp, *fmtstr, **arguments;
156 struct msgs *mp = NULL;
159 char b[MAXPATHLEN + 1];
160 /* copy of mail directory because the static gets overwritten */
161 char *maildir_copy = NULL;
163 if (atexit(inc_done) != 0) {
164 adios(EX_OSERR, NULL, "atexit failed");
168 ** absolutely the first thing we do is save our privileges,
169 ** and drop them if we can.
174 setlocale(LC_ALL, "");
175 invo_name = mhbasename(argv[0]);
179 arguments = getarguments(invo_name, argc, argv, 1);
182 while ((cp = *argp++)) {
184 switch (smatch(++cp, switches)) {
186 ambigsw(cp, switches);
189 adios(EX_USAGE, NULL, "-%s unknown", cp);
192 snprintf(buf, sizeof(buf), "%s [+folder] [switches]", invo_name);
193 print_help(buf, switches, 1);
194 exit(argc == 2 ? EX_OK : EX_USAGE);
196 print_version(invo_name);
197 exit(argc == 2 ? EX_OK : EX_USAGE);
200 if (!(cp = *argp++) || *cp == '-')
201 adios(EX_USAGE, NULL, "missing argument to %s", argp[-2]);
202 audfile = mh_xstrdup(expanddir(cp));
216 ** The flag `trnflag' has the value:
218 ** 2 if -truncate is given
219 ** 1 by default (truncating is default)
220 ** 0 if -notruncate is given
231 adios(EX_USAGE, NULL, "missing argument to %s",
233 from = (strcmp(cp, "-")==0) ? "-" : mh_xstrdup(expanddir(cp));
236 ** If the truncate file is in default state,
237 ** change to not truncate.
251 if (!(form = *argp++) || *form == '-')
252 adios(EX_USAGE, NULL, "missing argument to %s",
257 if (!(cp = *argp++) || *cp == '-')
258 adios(EX_USAGE, NULL, "missing argument to %s",
264 if (*cp == '+' || *cp == '@') {
266 adios(EX_USAGE, NULL, "only one folder at a time!");
268 folder = mh_xstrdup(expandfol(cp));
270 adios(EX_USAGE, NULL, "usage: %s [+folder] [switches]",
276 ** NOTE: above this point you should use TRYDROPGROUPPRIVS(),
277 ** not DROPGROUPPRIVS().
279 /* guarantee dropping group privileges; we might not have done so earlier */
282 if (from && strcmp(from, "-")==0) {
283 /* We'll read mail from stdin. */
286 /* We'll read mail from a file. */
289 else if ((newmail = getenv("MAILDROP")) && *newmail)
290 newmail = toabsdir(newmail);
291 else if ((newmail = context_find("maildrop")) && *newmail)
292 newmail = toabsdir(newmail);
294 newmail = concat(mailspool, "/", getusername(), NULL);
296 if (stat(newmail, &s1) == NOTOK || s1.st_size == 0)
297 adios(EX_DATAERR, NULL, "no mail to incorporate");
299 if ((cp = strdup(newmail)) == NULL)
300 adios(EX_OSERR, NULL, "error allocating memory to copy newmail");
306 folder = getdeffol();
307 maildir = toabsdir(folder);
309 if ((maildir_copy = strdup(maildir)) == NULL)
310 adios(EX_OSERR, maildir, "error allocating memory to copy maildir");
312 create_folder(maildir, noisy ? 0 : 1, exit);
314 if (chdir(maildir) == NOTOK)
315 adios(EX_OSERR, maildir, "unable to change directory to");
317 if (!(mp = folder_read(folder)))
318 adios(EX_IOERR, NULL, "unable to read folder %s", folder);
323 } else if (access(newmail, W_OK) != NOTOK) {
326 SIGNAL(SIGHUP, SIG_IGN);
327 SIGNAL(SIGINT, SIG_IGN);
328 SIGNAL(SIGQUIT, SIG_IGN);
329 SIGNAL(SIGTERM, SIG_IGN);
332 GETGROUPPRIVS(); /* Reset gid to lock mail file */
333 in = lkfopen(newmail, "r");
336 adios(EX_IOERR, NULL, "unable to lock and fopen %s", newmail);
337 fstat(fileno(in), &s1);
340 if ((in = fopen(newmail, "r")) == NULL)
341 adios(EX_IOERR, newmail, "unable to read");
344 /* This shouldn't be necessary but it can't hurt. */
349 if ((i = stat(audfile, &st)) == NOTOK)
350 advise(NULL, "Creating Receive-Audit: %s", audfile);
351 if ((aud = fopen(audfile, "a")) == NULL)
352 adios(EX_IOERR, audfile, "unable to append to");
354 chmod(audfile, m_gmprot());
356 fprintf(aud, from ? "<<inc>> %s -ms %s\n" : "<<inc>> %s\n",
360 /* Set format string */
361 fmtstr = new_fs(form, scanformat);
364 printf("Incorporating new mail into %s...\n\n", folder);
368 /* check if readable and nonempty */
369 if (!fgets(buf, sizeof(buf), in)) {
371 advise("read", "unable to");
378 if (strncmp("From ", buf, 5)!=0) {
379 advise(NULL, "not in mbox format");
385 ** Get the mail from file (usually mail spool)
387 hghnum = msgnum = mp->hghmsg;
390 ** Check if we need to allocate more space for message status.
391 ** If so, then add space for an additional 100 messages.
393 if (msgnum >= mp->hghoff && !(mp = folder_realloc(mp, mp->lowoff, mp->hghoff + 100))) {
394 advise(NULL, "unable to allocate folder storage");
399 /* create scanline for new message */
400 switch (incerr = scan(in, msgnum + 1, msgnum + 1,
401 noisy ? fmtstr : NULL, width,
402 msgnum == hghnum && chgflag, 1)) {
409 fputs("inc aborted!\n", aud);
410 /* doesn't clean up locks! */
411 advise(NULL, "aborted!");
415 advise(NULL, "BUG in %s, number out of range",
420 advise(NULL, "BUG in %s, scan() botch (%d)",
426 ** Run the external program hook on the message.
429 snprintf(b, sizeof (b), "%s/%d", maildir_copy,
431 ext_hook("add-hook", b, NULL);
442 clear_msg_flags(mp, msgnum);
443 set_exists(mp, msgnum);
444 set_unseen(mp, msgnum);
445 mp->msgflags |= SEQMOD;
449 ** If we get here there was some sort of error from scan(),
450 ** so stop processing anything more from the spool.
455 mh_free0(&maildir_copy);
457 if (incerr < 0) { /* error */
459 GETGROUPPRIVS(); /* Be sure we can unlock mail file */
460 lkfclose(in, newmail); in = NULL;
462 ** And then return us to normal
466 fclose(in); in = NULL;
468 adios(EX_SOFTWARE, NULL, "failed");
478 ** truncate file we are incorporating from
481 if (stat(newmail, &st) != NOTOK && s1.st_mtime != st.st_mtime)
482 advise(NULL, "new messages have arrived!\007");
485 if ((newfd = creat(newmail, 0600)) != NOTOK)
488 admonish(newmail, "error zero'ing");
490 } else if (noisy && newmail) {
491 printf("%s not zero'd\n", newmail);
494 if (msgnum == hghnum) {
495 admonish(NULL, "no messages incorporated");
497 context_replace(curfolder, folder); /* update current folder */
499 mp->curmsg = hghnum + 1;
503 if (chgflag) /* sigh... */
504 seq_setcur(mp, mp->curmsg);
508 ** unlock the mail spool
511 GETGROUPPRIVS(); /* Be sure we can unlock mail file */
512 lkfclose(in, newmail); in = NULL;
513 DROPGROUPPRIVS(); /* And then return us to normal privileges */
515 fclose(in); in = NULL;
518 seq_setunseen(mp, 1);
529 lkfclose(in, newmail);