* Bug #15213, #18635: The use of the insecure m_scratch() and
[mmh] / uip / burst.c
1
2 /*
3  * burst.c -- explode digests into individual messages
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 #include <h/mh.h>
13
14 static struct swit switches[] = {
15 #define INPLSW  0
16     { "inplace", 0 },
17 #define NINPLSW 1
18     { "noinplace", 0 },
19 #define QIETSW  2
20     { "quiet", 0 },
21 #define NQIETSW 3
22     { "noquiet", 0 },
23 #define VERBSW  4
24     { "verbose", 0 },
25 #define NVERBSW 5
26     { "noverbose", 0 },
27 #define VERSIONSW 6
28     { "version", 0 },
29 #define HELPSW  7
30     { "help", 0 },
31     { NULL, 0 }
32 };
33
34 static char delim3[] = "-------";
35
36 struct smsg {
37     long s_start;
38     long s_stop;
39 };
40
41 /*
42  * static prototypes
43  */
44 static int find_delim (int, struct smsg *);
45 static void burst (struct msgs **, int, struct smsg *, int, int, int, char *);
46 static void cpybrst (FILE *, FILE *, char *, char *, int);
47
48
49 int
50 main (int argc, char **argv)
51 {
52     int inplace = 0, quietsw = 0, verbosw = 0;
53     int msgp = 0, hi, msgnum, numburst;
54     char *cp, *maildir, *folder = NULL, buf[BUFSIZ];
55     char **argp, **arguments, *msgs[MAXARGS];
56     struct smsg *smsgs;
57     struct msgs *mp;
58
59 #ifdef LOCALE
60     setlocale(LC_ALL, "");
61 #endif
62     invo_name = r1bindex (argv[0], '/');
63
64     /* read user profile/context */
65     context_read();
66
67     arguments = getarguments (invo_name, argc, argv, 1);
68     argp = arguments;
69
70     while ((cp = *argp++)) {
71         if (*cp == '-') {
72             switch (smatch (++cp, switches)) {
73             case AMBIGSW: 
74                 ambigsw (cp, switches);
75                 done (1);
76             case UNKWNSW: 
77                 adios (NULL, "-%s unknown\n", cp);
78
79             case HELPSW: 
80                 snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
81                         invo_name);
82                 print_help (buf, switches, 1);
83                 done (1);
84             case VERSIONSW:
85                 print_version(invo_name);
86                 done (1);
87
88             case INPLSW: 
89                 inplace++;
90                 continue;
91             case NINPLSW: 
92                 inplace = 0;
93                 continue;
94
95             case QIETSW: 
96                 quietsw++;
97                 continue;
98             case NQIETSW: 
99                 quietsw = 0;
100                 continue;
101
102             case VERBSW: 
103                 verbosw++;
104                 continue;
105             case NVERBSW: 
106                 verbosw = 0;
107                 continue;
108             }
109         }
110         if (*cp == '+' || *cp == '@') {
111             if (folder)
112                 adios (NULL, "only one folder at a time!");
113             else
114                 folder = pluspath (cp);
115         } else {
116             msgs[msgp++] = cp;
117         }
118     }
119
120     if (!context_find ("path"))
121         free (path ("./", TFOLDER));
122     if (!msgp)
123         msgs[msgp++] = "cur";
124     if (!folder)
125         folder = getfolder (1);
126     maildir = m_maildir (folder);
127
128     if (chdir (maildir) == NOTOK)
129         adios (maildir, "unable to change directory to");
130
131     /* read folder and create message structure */
132     if (!(mp = folder_read (folder)))
133         adios (NULL, "unable to read folder %s", folder);
134
135     /* check for empty folder */
136     if (mp->nummsg == 0)
137         adios (NULL, "no messages in %s", folder);
138
139     /* parse all the message ranges/sequences and set SELECTED */
140     for (msgnum = 0; msgnum < msgp; msgnum++)
141         if (!m_convert (mp, msgs[msgnum]))
142             done (1);
143     seq_setprev (mp);   /* set the previous-sequence */
144
145     smsgs = (struct smsg *)
146         calloc ((size_t) (MAXFOLDER + 2), sizeof(*smsgs));
147     if (smsgs == NULL)
148         adios (NULL, "unable to allocate burst storage");
149
150     hi = mp->hghmsg + 1;
151
152     /* burst all the SELECTED messages */
153     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
154         if (is_selected (mp, msgnum)) {
155             if ((numburst = find_delim (msgnum, smsgs)) >= 1) {
156                 if (verbosw)
157                     printf ("%d message%s exploded from digest %d\n",
158                             numburst, numburst > 1 ? "s" : "", msgnum);
159                 burst (&mp, msgnum, smsgs, numburst, inplace, verbosw, maildir);
160             } else {
161                 if (numburst == 0) {
162                     if (!quietsw)
163                         admonish (NULL, "message %d not in digest format",
164                                   msgnum);
165                 }  /* this pair of braces was missing before 1999-07-15 */
166                 else
167                     adios (NULL, "burst() botch -- you lose big");
168             }
169         }
170     }
171
172     free ((char *) smsgs);
173     context_replace (pfolder, folder);  /* update current folder */
174
175     /*
176      * If -inplace is given, then the first message burst becomes
177      * the current message (which will now show a table of contents).
178      * Otherwise, the first message extracted from the first digest
179      * becomes the current message.
180      */
181     if (inplace) {
182         if (mp->lowsel != mp->curmsg)
183             seq_setcur (mp, mp->lowsel);
184     } else {
185         if (hi <= mp->hghmsg)
186             seq_setcur (mp, hi);
187     }
188
189     seq_save (mp);      /* synchronize message sequences */
190     context_save ();    /* save the context file         */
191     folder_free (mp);   /* free folder/message structure */
192     done (0);
193     return 1;
194 }
195
196
197 /*
198  * Scan the message and find the beginning and
199  * end of all the messages in the digest.
200  */
201
202 static int
203 find_delim (int msgnum, struct smsg *smsgs)
204 {
205     int ld3, wasdlm, msgp;
206     long pos;
207     char c, *msgnam;
208     int cc;
209     char buffer[BUFSIZ];
210     FILE *in;
211
212     ld3 = strlen (delim3);
213
214     if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL)
215         adios (msgnam, "unable to read message");
216
217     for (msgp = 0, pos = 0L; msgp <= MAXFOLDER;) {
218         while (fgets (buffer, sizeof(buffer), in) && buffer[0] == '\n')
219             pos += (long) strlen (buffer);
220         if (feof (in))
221             break;
222         fseek (in, pos, SEEK_SET);
223         smsgs[msgp].s_start = pos;
224
225         for (c = 0; fgets (buffer, sizeof(buffer), in); c = buffer[0]) {
226             if (strncmp (buffer, delim3, ld3) == 0
227                     && (msgp == 1 || c == '\n')
228                     && ((cc = peekc (in)) == '\n' || cc == EOF))
229                 break;
230             else
231                 pos += (long) strlen (buffer);
232         }
233
234         wasdlm = strncmp (buffer, delim3, ld3) == 0;
235         if (smsgs[msgp].s_start != pos)
236             smsgs[msgp++].s_stop = (c == '\n' && wasdlm) ? pos - 1 : pos;
237         if (feof (in)) {
238 #if 0
239             if (wasdlm) {
240                 smsgs[msgp - 1].s_stop -= ((long) strlen (buffer) + 1);
241                 msgp++;         /* fake "End of XXX Digest" */
242             }
243 #endif
244             break;
245         }
246         pos += (long) strlen (buffer);
247     }
248
249     fclose (in);
250     return (msgp - 1);          /* toss "End of XXX Digest" */
251 }
252
253
254 /*
255  * Burst out the messages in the digest into the folder
256  */
257
258 static void
259 burst (struct msgs **mpp, int msgnum, struct smsg *smsgs, int numburst,
260         int inplace, int verbosw, char *maildir)
261 {
262     int i, j, mode;
263     char *msgnam;
264     char f1[BUFSIZ], f2[BUFSIZ], f3[BUFSIZ];
265     FILE *in, *out;
266     struct stat st;
267     struct msgs *mp;
268
269     if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL)
270         adios (msgnam, "unable to read message");
271
272     mode = fstat (fileno(in), &st) != NOTOK ? (st.st_mode & 0777) : m_gmprot();
273     mp = *mpp;
274
275     /*
276      * See if we have enough space in the folder
277      * structure for all the new messages.
278      */
279     if ((mp->hghmsg + numburst > mp->hghoff) &&
280         !(mp = folder_realloc (mp, mp->lowoff, mp->hghmsg + numburst)))
281         adios (NULL, "unable to allocate folder storage");
282     *mpp = mp;
283
284     j = mp->hghmsg;             /* old value */
285     mp->hghmsg += numburst;
286     mp->nummsg += numburst;
287
288     /*
289      * If this is not the highest SELECTED message, then
290      * increment mp->hghsel by numburst, since the highest
291      * SELECTED is about to be slid down by that amount.
292      */
293     if (msgnum < mp->hghsel)
294         mp->hghsel += numburst;
295
296     /*
297      * If -inplace is given, renumber the messages after the
298      * source message, to make room for each of the messages
299      * contained within the digest.
300      *
301      * This is equivalent to refiling a message from the point
302      * of view of the external hooks.
303      */
304     if (inplace) {
305         for (i = mp->hghmsg; j > msgnum; i--, j--) {
306             strncpy (f1, m_name (i), sizeof(f1));
307             strncpy (f2, m_name (j), sizeof(f2));
308             if (does_exist (mp, j)) {
309                 if (verbosw)
310                     printf ("message %d becomes message %d\n", j, i);
311
312                 if (rename (f2, f1) == NOTOK)
313                     admonish (f1, "unable to rename %s to", f2);
314
315                 (void)snprintf(f1, sizeof (f1), "%s/%d", maildir, i);
316                 (void)snprintf(f2, sizeof (f2), "%s/%d", maildir, j);
317                 ext_hook("ref-hook", f1, f2);
318
319                 copy_msg_flags (mp, i, j);
320                 clear_msg_flags (mp, j);
321                 mp->msgflags |= SEQMOD;
322             }
323         }
324     }
325     
326     unset_selected (mp, msgnum);
327
328     /* new hghmsg is hghmsg + numburst
329      *
330      * At this point, there is an array of numburst smsgs, each element of
331      * which contains the starting and stopping offsets (seeks) of the message
332      * in the digest.  The inplace flag is set if the original digest is replaced
333      * by a message containing the table of contents.  smsgs[0] is that table of
334      * contents.  Go through the message numbers in reverse order (high to low).
335      *
336      * Set f1 to the name of the destination message, f2 to the name of a scratch
337      * file.  Extract a message from the digest to the scratch file.  Move the
338      * original message to a backup file if the destination message number is the
339      * same as the number of the original message, which only happens if the
340      * inplace flag is set.  Then move the scratch file to the destination message.
341      *
342      * Moving the original message to the backup file is equivalent to deleting the
343      * message from the point of view of the external hooks.  And bursting each
344      * message is equivalent to adding a new message.
345      */
346
347     i = inplace ? msgnum + numburst : mp->hghmsg;
348     for (j = numburst; j >= (inplace ? 0 : 1); i--, j--) {
349         strncpy (f1, m_name (i), sizeof(f1));
350         strncpy (f2, m_mktemp(invo_name, NULL, &out), sizeof(f2));
351
352         if (verbosw && i != msgnum)
353             printf ("message %d of digest %d becomes message %d\n", j, msgnum, i);
354
355         chmod (f2, mode);
356         fseek (in, smsgs[j].s_start, SEEK_SET);
357         cpybrst (in, out, msgnam, f2,
358                 (int) (smsgs[j].s_stop - smsgs[j].s_start));
359         fclose (out);
360
361         if (i == msgnum) {
362             strncpy (f3, m_backup (f1), sizeof(f3));
363             if (rename (f1, f3) == NOTOK)
364                 admonish (f3, "unable to rename %s to", f1);
365
366             (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
367             ext_hook("del-hook", f3, (char *)0);
368         }
369         if (rename (f2, f1) == NOTOK)
370             admonish (f1, "unable to rename %s to", f2);
371
372         (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
373         ext_hook("add-hook", f3, (char *)0);
374
375         copy_msg_flags (mp, i, msgnum);
376         mp->msgflags |= SEQMOD;
377     }
378
379     fclose (in);
380 }
381
382
383 #define S1  0
384 #define S2  1
385 #define S3  2
386
387 /*
388  * Copy a mesage which is being burst out of a digest.
389  * It will remove any "dashstuffing" in the message.
390  */
391
392 static void
393 cpybrst (FILE *in, FILE *out, char *ifile, char *ofile, int len)
394 {
395     register int c, state;
396
397     for (state = S1; (c = fgetc (in)) != EOF && len > 0; len--) {
398         if (c == 0)
399             continue;
400         switch (state) {
401             case S1: 
402                 switch (c) {
403                     case '-': 
404                         state = S3;
405                         break;
406
407                     default: 
408                         state = S2;
409                     case '\n': 
410                         fputc (c, out);
411                         break;
412                 }
413                 break;
414
415             case S2: 
416                 switch (c) {
417                     case '\n': 
418                         state = S1;
419                     default: 
420                         fputc (c, out);
421                         break;
422                 }
423                 break;
424
425             case S3: 
426                 switch (c) {
427                     case ' ': 
428                         state = S2;
429                         break;
430
431                     default: 
432                         state = (c == '\n') ? S1 : S2;
433                         fputc ('-', out);
434                         fputc (c, out);
435                         break;
436                 }
437                 break;
438         }
439     }
440
441     if (ferror (in) && !feof (in))
442         adios (ifile, "error reading");
443     if (ferror (out))
444         adios (ofile, "error writing");
445 }