Replaced mkinstalldirs with `mkdir -p'.
[mmh] / uip / mhshow.c
1 /*
2 ** mhshow.c -- display the contents of MIME messages
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 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/signals.h>
12 #include <h/md5.h>
13 #include <errno.h>
14 #include <signal.h>
15 #include <h/tws.h>
16 #include <h/mime.h>
17 #include <h/mhparse.h>
18 #include <h/mhcachesbr.h>
19 #include <h/utils.h>
20
21 #ifdef HAVE_SYS_WAIT_H
22 # include <sys/wait.h>
23 #endif
24
25 static struct swit switches[] = {
26 #define CHECKSW  0
27         { "check", 0 },
28 #define NCHECKSW  1
29         { "nocheck", 0 },
30 #define SERIALSW  2
31         { "serialonly", 0 },
32 #define NSERIALSW  3
33         { "noserialonly", 0 },
34 #define VERBSW  4
35         { "verbose", 0 },
36 #define NVERBSW  5
37         { "noverbose", 0 },
38 #define FILESW  6  /* interface from show */
39         { "file file", 0 },
40 #define FORMSW  7
41         { "form formfile", 0 },
42 #define PARTSW  8
43         { "part number", 0 },
44 #define TYPESW  9
45         { "type content", 0 },
46 #define RCACHESW  10
47         { "rcache policy", 0 },
48 #define WCACHESW  11
49         { "wcache policy", 0 },
50 #define VERSIONSW  12
51         { "version", 0 },
52 #define HELPSW  13
53         { "help", 0 },
54 /*
55 ** switches for moreproc/mhlproc
56 */
57 #define PROGSW  14
58         { "moreproc program", -4 },
59 #define WIDTHSW  15
60         { "width columns", -4 },
61 /*
62 ** switches for debugging
63 */
64 #define DEBUGSW  16
65         { "debug", -5 },
66         { NULL, 0 }
67 };
68
69
70 /* mhparse.c */
71 extern char *tmp;  /* directory to place temp files */
72
73 /* mhcachesbr.c */
74 extern int rcachesw;
75 extern int wcachesw;
76 extern char *cache_public;
77 extern char *cache_private;
78
79 /* mhshowsbr.c */
80 extern int serialsw;
81 extern char *progsw;
82 extern int nolist;
83 extern char *formsw;
84
85 /* mhmisc.c */
86 extern int npart;
87 extern int ntype;
88 extern char *parts[NPARTS + 1];
89 extern char *types[NTYPES + 1];
90 extern int userrs;
91
92 int debugsw = 0;
93 int verbosw = 0;
94
95 #define quitser pipeser
96
97 /* mhparse.c */
98 CT parse_mime(char *);
99
100 /* mhmisc.c */
101 int part_ok(CT, int);
102 int type_ok(CT, int);
103 void set_endian(void);
104 void flush_errors(void);
105
106 /* mhshowsbr.c */
107 void show_all_messages(CT *);
108
109 /* mhfree.c */
110 void free_content(CT);
111 extern CT *cts;
112 void freects_done(int) NORETURN;
113
114 /*
115 ** static prototypes
116 */
117 static RETSIGTYPE pipeser(int);
118
119
120 int
121 main(int argc, char **argv)
122 {
123         int msgnum, *icachesw;
124         char *cp, *file = NULL, *folder = NULL;
125         char *maildir, buf[100], **argp;
126         char **arguments;
127         struct msgs_array msgs = { 0, 0, NULL };
128         struct msgs *mp = NULL;
129         CT ct, *ctp;
130         FILE *fp;
131
132         done=freects_done;
133
134 #ifdef LOCALE
135         setlocale(LC_ALL, "");
136 #endif
137         invo_name = mhbasename(argv[0]);
138
139         /* read user profile/context */
140         context_read();
141
142         arguments = getarguments(invo_name, argc, argv, 1);
143         argp = arguments;
144
145         /*
146         ** Parse arguments
147         */
148         while ((cp = *argp++)) {
149                 if (*cp == '-') {
150                         switch (smatch(++cp, switches)) {
151                         case AMBIGSW:
152                                 ambigsw(cp, switches);
153                                 done(1);
154                         case UNKWNSW:
155                                 adios(NULL, "-%s unknown", cp);
156
157                         case HELPSW:
158                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
159                                 print_help(buf, switches, 1);
160                                 done(1);
161                         case VERSIONSW:
162                                 print_version(invo_name);
163                                 done(1);
164
165                         case RCACHESW:
166                                 icachesw = &rcachesw;
167                                 goto do_cache;
168                         case WCACHESW:
169                                 icachesw = &wcachesw;
170 do_cache:
171                                 if (!(cp = *argp++) || *cp == '-')
172                                         adios(NULL, "missing argument to %s",
173                                                         argp[-2]);
174                                 switch (*icachesw = smatch(cp, caches)) {
175                                 case AMBIGSW:
176                                         ambigsw(cp, caches);
177                                         done(1);
178                                 case UNKWNSW:
179                                         adios(NULL, "%s unknown", cp);
180                                 default:
181                                         break;
182                                 }
183                                 continue;
184
185                         case CHECKSW:
186                                 checksw++;
187                                 continue;
188                         case NCHECKSW:
189                                 checksw = 0;
190                                 continue;
191
192                         case SERIALSW:
193                                 serialsw = 1;
194                                 continue;
195                         case NSERIALSW:
196                                 serialsw = 0;
197                                 continue;
198
199                         case PARTSW:
200                                 if (!(cp = *argp++) || *cp == '-')
201                                         adios(NULL, "missing argument to %s",
202                                                         argp[-2]);
203                                 if (npart >= NPARTS)
204                                         adios(NULL, "too many parts (starting with %s), %d max", cp, NPARTS);
205                                 parts[npart++] = cp;
206                                 continue;
207
208                         case TYPESW:
209                                 if (!(cp = *argp++) || *cp == '-')
210                                         adios(NULL, "missing argument to %s",
211                                                         argp[-2]);
212                                 if (ntype >= NTYPES)
213                                         adios(NULL, "too many types (starting with %s), %d max", cp, NTYPES);
214                                 types[ntype++] = cp;
215                                 continue;
216
217                         case FILESW:
218                                 if (!(cp = *argp++) || (*cp == '-' && cp[1]))
219                                         adios(NULL, "missing argument to %s",
220                                                         argp[-2]);
221                                 file = *cp == '-' ? cp : getcpy(expanddir(cp));
222                                 continue;
223
224                         case FORMSW:
225                                 if (!(cp = *argp++) || *cp == '-')
226                                         adios(NULL, "missing argument to %s",
227                                                         argp[-2]);
228                                 if (formsw)
229                                         free(formsw);
230                                 formsw = getcpy(etcpath(cp));
231                                 continue;
232
233                         /*
234                         ** Switches for moreproc/mhlproc
235                         */
236                         case PROGSW:
237                                 if (!(progsw = *argp++) || *progsw == '-')
238                                         adios(NULL, "missing argument to %s",
239                                                         argp[-2]);
240                                 continue;
241
242                         case WIDTHSW:
243                                 if (!(cp = *argp++) || *cp == '-')
244                                         adios(NULL, "missing argument to %s",
245                                                         argp[-2]);
246                                 continue;
247
248                         case VERBSW:
249                                 verbosw = 1;
250                                 continue;
251                         case NVERBSW:
252                                 verbosw = 0;
253                                 continue;
254                         case DEBUGSW:
255                                 debugsw = 1;
256                                 continue;
257                         }
258                 }
259                 if (*cp == '+' || *cp == '@') {
260                         if (folder)
261                                 adios(NULL, "only one folder at a time!");
262                         else
263                                 folder = getcpy(expandfol(cp));
264                 } else
265                         app_msgarg(&msgs, cp);
266         }
267
268         /* null terminate the list of acceptable parts/types */
269         parts[npart] = NULL;
270         types[ntype] = NULL;
271
272         set_endian();
273
274         if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0) {
275                 nolist  = 1;
276         }
277
278         /*
279         ** Check if we've specified an additional profile
280         */
281         if ((cp = getenv("MHSHOW"))) {
282                 if ((fp = fopen(cp, "r"))) {
283                         readconfig((struct node **) 0, fp, cp, 0);
284                         fclose(fp);
285                 } else {
286                         admonish("", "unable to read $MHSHOW profile (%s)",
287                                         cp);
288                 }
289         }
290
291         /*
292         ** Read the standard profile setup
293         */
294         if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
295                 readconfig((struct node **) 0, fp, cp, 0);
296                 fclose(fp);
297         }
298
299         /* Check for public cache location */
300         if ((cache_public = context_find(nmhcache)) && *cache_public != '/')
301                 cache_public = NULL;
302
303         /* Check for private cache location */
304         if (!(cache_private = context_find(nmhprivcache)))
305                 cache_private = ".cache";
306         cache_private = getcpy(toabsdir(cache_private));
307
308         /*
309         ** Check for storage directory.  If specified,
310         ** then store temporary files there.  Else we
311         ** store them in standard nmh directory.
312         */
313         if ((cp = context_find(nmhstorage)) && *cp)
314                 tmp = concat(cp, "/", invo_name, NULL);
315         else
316                 tmp = getcpy(toabsdir(invo_name));
317
318         if (file && msgs.size)
319                 adios(NULL, "cannot specify msg and file at same time!");
320
321         /*
322         ** check if message is coming from file
323         */
324         if (file) {
325                 if (!(cts = (CT *) calloc((size_t) 2, sizeof(*cts))))
326                         adios(NULL, "out of memory");
327                 ctp = cts;
328
329                 if ((ct = parse_mime(file)));
330                         *ctp++ = ct;
331         } else {
332                 /*
333                 ** message(s) are coming from a folder
334                 */
335                 if (!msgs.size)
336                         app_msgarg(&msgs, seq_cur);
337                 if (!folder)
338                         folder = getcurfol();
339                 maildir = toabsdir(folder);
340
341                 if (chdir(maildir) == NOTOK)
342                         adios(maildir, "unable to change directory to");
343
344                 /* read folder and create message structure */
345                 if (!(mp = folder_read(folder)))
346                         adios(NULL, "unable to read folder %s", folder);
347
348                 /* check for empty folder */
349                 if (mp->nummsg == 0)
350                         adios(NULL, "no messages in %s", folder);
351
352                 /* parse all the message ranges/sequences and set SELECTED */
353                 for (msgnum = 0; msgnum < msgs.size; msgnum++)
354                         if (!m_convert(mp, msgs.msgs[msgnum]))
355                                 done(1);
356
357                 /*
358                 ** Set the SELECT_UNSEEN bit for all the SELECTED messages,
359                 ** since we will use that as a tag to know which messages
360                 ** to remove from the "unseen" sequence.
361                 */
362                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
363                         if (is_selected(mp, msgnum))
364                                 set_unseen(mp, msgnum);
365
366                 seq_setprev(mp);  /* set the Previous-Sequence */
367                 seq_setunseen(mp, 0);  /* unset unseen seqs for shown msgs */
368
369                 if (!(cts = (CT *) calloc((size_t) (mp->numsel + 1),
370                                 sizeof(*cts))))
371                         adios(NULL, "out of memory");
372                 ctp = cts;
373
374                 /*
375                 ** Parse all the SELECTED messages.
376                 */
377                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
378                         if (is_selected(mp, msgnum)) {
379                                 char *msgnam;
380
381                                 msgnam = m_name(msgnum);
382                                 if ((ct = parse_mime(msgnam)))
383                                         *ctp++ = ct;
384                         }
385                 }
386         }
387
388         if (!*cts)
389                 done(1);
390
391         userrs = 1;
392         SIGNAL(SIGQUIT, quitser);
393         SIGNAL(SIGPIPE, pipeser);
394
395         /*
396         ** Get the associated umask for the relevant contents.
397         */
398         for (ctp = cts; *ctp; ctp++) {
399                 struct stat st;
400
401                 ct = *ctp;
402                 if (type_ok(ct, 1) && !ct->c_umask) {
403                         if (stat(ct->c_file, &st) != NOTOK)
404                                 ct->c_umask = ~(st.st_mode & 0777);
405                         else
406                                 ct->c_umask = ~m_gmprot();
407                 }
408         }
409
410         /*
411         ** Show the message content
412         */
413         show_all_messages(cts);
414
415         /* Now free all the structures for the content */
416         for (ctp = cts; *ctp; ctp++)
417                 free_content(*ctp);
418
419         free((char *) cts);
420         cts = NULL;
421
422         /* If reading from a folder, do some updating */
423         if (mp) {
424                 context_replace(curfolder, folder); /* update current folder */
425                 seq_setcur(mp, mp->hghsel);        /* update current message */
426                 seq_save(mp);                      /* synchronize sequences */
427                 context_save();                    /* save the context file */
428         }
429
430         done(0);
431         return 1;
432 }
433
434
435 static RETSIGTYPE
436 pipeser(int i)
437 {
438         if (i == SIGQUIT) {
439                 unlink("core");
440                 fflush(stdout);
441                 fprintf(stderr, "\n");
442                 fflush(stderr);
443         }
444
445         done(1);
446         /* NOTREACHED */
447 }