Fix name argument quoting (Gavin Troy)..
[s-mailx.git] / cmd2.c
blobd3d1a0a37b7b9f9ff2ec13d961309f840a032138
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ More user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "nail.h"
42 #include <sys/wait.h>
44 static int save1(char *str, int domark, char const *cmd,
45 struct ignoretab *ignore, int convert,
46 int sender_record, int domove);
47 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
48 static int delm(int *msgvec);
49 #ifdef HAVE_ASSERTS
50 static void clob1(int n);
51 #endif
52 static int ignore1(char **list, struct ignoretab *tab, char const *which);
53 static int igshow(struct ignoretab *tab, char const *which);
54 static int igcomp(const void *l, const void *r);
55 static void unignore_one(const char *name, struct ignoretab *tab);
56 static int unignore1(char **list, struct ignoretab *tab,
57 char const *which);
60 * If any arguments were given, go to the next applicable argument
61 * following dot, otherwise, go to the next applicable message.
62 * If given as first command with no arguments, print first message.
64 int
65 next(void *v)
67 int *msgvec = v;
68 struct message *mp;
69 int *ip, *ip2;
70 int list[2], mdot;
72 if (*msgvec != 0) {
75 * If some messages were supplied, find the
76 * first applicable one following dot using
77 * wrap around.
80 mdot = dot - &message[0] + 1;
83 * Find the first message in the supplied
84 * message list which follows dot.
87 for (ip = msgvec; *ip != 0; ip++) {
88 #ifdef _CRAY
90 * Work around an optimizer bug in Cray Standard C Version 4.0.3 (057126).
91 * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
93 #pragma _CRI suppress ip
94 #endif /* _CRAY */
95 if (mb.mb_threaded ? message[*ip-1].m_threadpos >
96 dot->m_threadpos :
97 *ip > mdot)
98 break;
100 if (*ip == 0)
101 ip = msgvec;
102 ip2 = ip;
103 do {
104 mp = &message[*ip2 - 1];
105 if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
106 setdot(mp);
107 goto hitit;
109 if (*ip2 != 0)
110 ip2++;
111 if (*ip2 == 0)
112 ip2 = msgvec;
113 } while (ip2 != ip);
114 printf(tr(21, "No messages applicable\n"));
115 return(1);
119 * If this is the first command, select message 1.
120 * Note that this must exist for us to get here at all.
123 if (!sawcom) {
124 if (msgCount == 0)
125 goto ateof;
126 goto hitit;
130 * Just find the next good message after dot, no
131 * wraparound.
134 if (mb.mb_threaded == 0) {
135 for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
136 if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN)) == 0)
137 break;
138 } else {
139 mp = dot;
140 if (did_print_dot)
141 mp = next_in_thread(mp);
142 while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN))
143 mp = next_in_thread(mp);
145 if (mp == NULL || mp >= &message[msgCount]) {
146 ateof:
147 printf(tr(22, "At EOF\n"));
148 return(0);
150 setdot(mp);
151 hitit:
153 * Print dot.
156 list[0] = dot - &message[0] + 1;
157 list[1] = 0;
158 return(type(list));
162 * Save a message in a file. Mark the message as saved
163 * so we can discard when the user quits.
165 int
166 save(void *v)
168 char *str = v;
170 return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
173 int
174 Save(void *v)
176 char *str = v;
178 return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
182 * Copy a message to a file without affected its saved-ness
184 int
185 copycmd(void *v)
187 char *str = v;
189 return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
192 int
193 Copycmd(void *v)
195 char *str = v;
197 return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
201 * Move a message to a file.
203 int
204 cmove(void *v)
206 char *str = v;
208 return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
211 int
212 cMove(void *v)
214 char *str = v;
216 return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
220 * Decrypt and copy a message to a file.
222 int
223 cdecrypt(void *v)
225 char *str = v;
227 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
230 int
231 cDecrypt(void *v)
233 char *str = v;
235 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
239 * Save/copy the indicated messages at the end of the passed file name.
240 * If mark is true, mark the message "saved."
242 static int
243 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
244 int convert, int sender_record, int domove)
246 off_t mstats[2], tstats[2];
247 struct stat st;
248 int newfile = 0, compressed = 0, success = 1, last = 0, *msgvec, *ip;
249 struct message *mp;
250 char *file = NULL, *cp, *cq;
251 char const *disp = "";
252 FILE *obuf;
253 enum protocol prot;
254 bool_t f;
256 /*LINTED*/
257 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
258 if (sender_record) {
259 for (cp = str; *cp && blankchar(*cp); cp++)
261 f = (*cp != '\0');
262 } else {
263 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
264 return(1);
266 if (!f) {
267 *msgvec = first(0, MMNORM);
268 if (*msgvec == 0) {
269 if (inhook)
270 return 0;
271 printf(tr(23, "No messages to %s.\n"), cmd);
272 return(1);
274 msgvec[1] = 0;
275 } else if (getmsglist(str, msgvec, 0) < 0)
276 return(1);
277 if (*msgvec == 0) {
278 if (inhook)
279 return 0;
280 printf("No applicable messages.\n");
281 return 1;
283 if (sender_record) {
284 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
285 printf(tr(24,
286 "Cannot determine message sender to %s.\n"),
287 cmd);
288 return 1;
290 for (cq = cp; *cq && *cq != '@'; cq++);
291 *cq = '\0';
292 if (value("outfolder")) {
293 size_t sz = strlen(cp) + 1;
294 file = salloc(sz + 1);
295 file[0] = '+';
296 memcpy(file + 1, cp, sz);
297 } else
298 file = cp;
300 if ((file = expand(file)) == NULL)
301 return (1);
302 prot = which_protocol(file);
303 if (prot != PROTO_IMAP) {
304 if (access(file, 0) >= 0) {
305 newfile = 0;
306 disp = tr(25, "[Appended]");
307 } else {
308 newfile = 1;
309 disp = tr(26, "[New file]");
312 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
313 Zopen(file, "a+", &compressed)) == NULL) {
314 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
315 Zopen(file, "wx", &compressed)) == NULL) {
316 perror(file);
317 return(1);
319 } else {
320 if (compressed) {
321 newfile = 0;
322 disp = tr(25, "[Appended]");
324 if (!newfile && fstat(fileno(obuf), &st) &&
325 S_ISREG(st.st_mode) &&
326 fseek(obuf, -2L, SEEK_END) == 0) {
327 char buf[2];
328 int prependnl = 0;
330 switch (fread(buf, sizeof *buf, 2, obuf)) {
331 case 2:
332 if (buf[1] != '\n') {
333 prependnl = 1;
334 break;
336 /*FALLTHRU*/
337 case 1:
338 if (buf[0] != '\n')
339 prependnl = 1;
340 break;
341 default:
342 if (ferror(obuf)) {
343 perror(file);
344 return(1);
346 prependnl = 0;
348 fflush(obuf);
349 if (prependnl) {
350 putc('\n', obuf);
351 fflush(obuf);
356 tstats[0] = tstats[1] = 0;
357 imap_created_mailbox = 0;
358 srelax_hold();
359 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
360 mp = &message[*ip - 1];
361 if (prot == PROTO_IMAP &&
362 ignoret[0].i_count == 0 &&
363 ignoret[1].i_count == 0
364 #ifdef HAVE_IMAP /* TODO revisit */
365 && imap_thisaccount(file)
366 #endif
368 #ifdef HAVE_IMAP
369 if (imap_copy(mp, *ip, file) == STOP)
370 #endif
371 goto jferr;
372 #ifdef HAVE_IMAP
373 mstats[0] = -1;
374 mstats[1] = mp->m_xsize;
375 #endif
376 } else if (sendmp(mp, obuf, ignoret, NULL,
377 convert, mstats) < 0) {
378 perror(file);
379 goto jferr;
381 srelax();
382 touch(mp);
383 if (domark)
384 mp->m_flag |= MSAVED;
385 if (domove) {
386 mp->m_flag |= MDELETED|MSAVED;
387 last = *ip;
389 tstats[0] += mstats[0];
390 tstats[1] += mstats[1];
392 fflush(obuf);
393 if (ferror(obuf)) {
394 perror(file);
395 jferr:
396 success = 0;
398 if (Fclose(obuf) != 0)
399 success = 0;
400 srelax_rele();
402 if (success) {
403 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
404 disp = (
405 #ifdef HAVE_IMAP
406 ((prot == PROTO_IMAP) && disconnected(file))
407 ? "[Queued]" :
408 #endif
409 (imap_created_mailbox ? "[New file]"
410 : "[Appended]"));
412 printf("\"%s\" %s ", file, disp);
413 if (tstats[0] >= 0)
414 printf("%lu", (long)tstats[0]);
415 else
416 printf(tr(27, "binary"));
417 printf("/%lu\n", (long)tstats[1]);
418 } else if (domark) {
419 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
420 mp = &message[*ip - 1];
421 mp->m_flag &= ~MSAVED;
423 } else if (domove) {
424 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
425 mp = &message[*ip - 1];
426 mp->m_flag &= ~(MSAVED|MDELETED);
429 if (domove && last && success) {
430 setdot(&message[last-1]);
431 last = first(0, MDELETED);
432 setdot(&message[last ? last-1 : 0]);
434 return(success == 0);
438 * Write the indicated messages at the end of the passed
439 * file name, minus header and trailing blank line.
440 * This is the MIME save function.
442 int
443 cwrite(void *v)
445 char *str = v;
447 if (str == NULL || *str == '\0')
448 str = savestr("/dev/null");
449 return (save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0));
453 * Snarf the file from the end of the command line and
454 * return a pointer to it. If there is no file attached,
455 * return the mbox file. Put a null in front of the file
456 * name so that the message list processing won't see it,
457 * unless the file name is the only thing on the line, in
458 * which case, return 0 in the reference flag variable.
460 static char *
461 snarf(char *linebuf, bool_t *flag, bool_t usembox)
463 char *cp;
465 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
466 if (usembox) {
467 *flag = FAL0;
468 cp = expand("&");
469 } else
470 fprintf(stderr, tr(28, "No file specified.\n"));
472 return (cp);
476 * Delete messages.
478 int
479 delete(void *v)
481 int *msgvec = v;
482 delm(msgvec);
483 return 0;
487 * Delete messages, then type the new dot.
489 int
490 deltype(void *v)
492 int *msgvec = v;
493 int list[2];
494 int lastdot;
496 lastdot = dot - &message[0] + 1;
497 if (delm(msgvec) >= 0) {
498 list[0] = dot - &message[0] + 1;
499 if (list[0] > lastdot) {
500 touch(dot);
501 list[1] = 0;
502 return(type(list));
504 printf(tr(29, "At EOF\n"));
505 } else
506 printf(tr(30, "No more messages\n"));
507 return(0);
511 * Delete the indicated messages.
512 * Set dot to some nice place afterwards.
513 * Internal interface.
515 static int
516 delm(int *msgvec)
518 struct message *mp;
519 int *ip;
520 int last;
522 last = 0;
523 for (ip = msgvec; *ip != 0; ip++) {
524 mp = &message[*ip - 1];
525 touch(mp);
526 mp->m_flag |= MDELETED|MTOUCH;
527 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
528 last = *ip;
530 if (last != 0) {
531 setdot(&message[last-1]);
532 last = first(0, MDELETED);
533 if (last != 0) {
534 setdot(&message[last-1]);
535 return(0);
537 else {
538 setdot(&message[0]);
539 return(-1);
544 * Following can't happen -- it keeps lint happy
547 return(-1);
551 * Undelete the indicated messages.
553 int
554 undeletecmd(void *v)
556 int *msgvec = v;
557 struct message *mp;
558 int *ip;
560 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
561 mp = &message[*ip - 1];
562 touch(mp);
563 setdot(mp);
564 if (mp->m_flag & (MDELETED|MSAVED))
565 mp->m_flag &= ~(MDELETED|MSAVED);
566 else
567 mp->m_flag &= ~MDELETED;
568 #ifdef HAVE_IMAP
569 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
570 imap_undelete(mp, *ip);
571 #endif
573 return 0;
576 #ifdef HAVE_ASSERTS
578 * Interactively dump core on "core"
580 /*ARGSUSED*/
581 int
582 core(void *v)
584 int pid;
585 # ifdef WCOREDUMP
586 extern int wait_status;
587 # endif
588 (void)v;
590 switch (pid = fork()) {
591 case -1:
592 perror("fork");
593 return (1);
594 case 0:
595 abort();
596 _exit(1);
598 (void)printf(tr(31, "Okie dokie"));
599 (void)fflush(stdout);
600 (void)wait_child(pid);
601 # ifdef WCOREDUMP
602 if (WCOREDUMP(wait_status))
603 (void)printf(tr(32, " -- Core dumped.\n"));
604 else
605 (void)printf(tr(33, " -- Can't dump core.\n"));
606 # endif
607 return (0);
610 static void
611 clob1(int n)
613 char buf[512], *cp;
615 if (n <= 0)
616 return;
617 for (cp = buf; PTRCMP(cp, <, buf + 512); ++cp)
618 *cp = (char)0xFF;
619 clob1(n - 1);
623 * Clobber as many bytes of stack as the user requests.
625 int
626 clobber(void *v)
628 char **argv = v;
629 int times;
631 if (argv[0] == 0)
632 times = 1;
633 else
634 times = (atoi(argv[0]) + 511) / 512;
635 clob1(times);
636 return (0);
638 #endif /* HAVE_ASSERTS */
641 * Add the given header fields to the retained list.
642 * If no arguments, print the current list of retained fields.
644 int
645 retfield(void *v)
647 char **list = v;
649 return ignore1(list, ignore + 1, "retained");
653 * Add the given header fields to the ignored list.
654 * If no arguments, print the current list of ignored fields.
656 int
657 igfield(void *v)
659 char **list = v;
661 return ignore1(list, ignore, "ignored");
664 int
665 saveretfield(void *v)
667 char **list = v;
669 return ignore1(list, saveignore + 1, "retained");
672 int
673 saveigfield(void *v)
675 char **list = v;
677 return ignore1(list, saveignore, "ignored");
680 int
681 fwdretfield(void *v)
683 char **list = v;
685 return ignore1(list, fwdignore + 1, "retained");
688 int
689 fwdigfield(void *v)
691 char **list = v;
693 return ignore1(list, fwdignore, "ignored");
696 static int
697 ignore1(char **list, struct ignoretab *tab, char const *which)
699 int h;
700 struct ignore *igp;
701 char **ap;
703 if (*list == NULL)
704 return igshow(tab, which);
705 for (ap = list; *ap != 0; ap++) {
706 char *field;
707 size_t sz;
709 sz = strlen(*ap);
710 field = ac_alloc(sz + 1);
711 i_strcpy(field, *ap, sz + 1);
712 field[sz]='\0';
713 if (member(field, tab)) {
714 ac_free(field);
715 continue;
717 h = hash(field);
718 igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
719 sz = strlen(field) + 1;
720 igp->i_field = smalloc(sz);
721 memcpy(igp->i_field, field, sz);
722 igp->i_link = tab->i_head[h];
723 tab->i_head[h] = igp;
724 tab->i_count++;
725 ac_free(field);
727 return 0;
731 * Print out all currently retained fields.
733 static int
734 igshow(struct ignoretab *tab, char const *which)
736 int h;
737 struct ignore *igp;
738 char **ap, **ring;
740 if (tab->i_count == 0) {
741 printf(tr(34, "No fields currently being %s.\n"), which);
742 return 0;
744 /*LINTED*/
745 ring = (char **)salloc((tab->i_count + 1) * sizeof (char *));
746 ap = ring;
747 for (h = 0; h < HSHSIZE; h++)
748 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
749 *ap++ = igp->i_field;
750 *ap = 0;
751 qsort(ring, tab->i_count, sizeof (char *), igcomp);
752 for (ap = ring; *ap != 0; ap++)
753 printf("%s\n", *ap);
754 return 0;
758 * Compare two names for sorting ignored field list.
760 static int
761 igcomp(const void *l, const void *r)
763 return (strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r)));
766 int
767 unignore(void *v)
769 return unignore1((char **)v, ignore, "ignored");
772 int
773 unretain(void *v)
775 return unignore1((char **)v, ignore + 1, "retained");
778 int
779 unsaveignore(void *v)
781 return unignore1((char **)v, saveignore, "ignored");
784 int
785 unsaveretain(void *v)
787 return unignore1((char **)v, saveignore + 1, "retained");
790 int
791 unfwdignore(void *v)
793 return unignore1((char **)v, fwdignore, "ignored");
796 int
797 unfwdretain(void *v)
799 return unignore1((char **)v, fwdignore + 1, "retained");
802 static void
803 unignore_one(const char *name, struct ignoretab *tab)
805 struct ignore *ip, *iq = NULL;
806 int h = hash(name);
808 for (ip = tab->i_head[h]; ip; ip = ip->i_link) {
809 if (asccasecmp(ip->i_field, name) == 0) {
810 free(ip->i_field);
811 if (iq != NULL)
812 iq->i_link = ip->i_link;
813 else
814 tab->i_head[h] = ip->i_link;
815 free(ip);
816 tab->i_count--;
817 break;
819 iq = ip;
823 static int
824 unignore1(char **list, struct ignoretab *tab, char const *which)
826 if (tab->i_count == 0) {
827 printf(tr(34, "No fields currently being %s.\n"), which);
828 return 0;
830 while (*list)
831 unignore_one(*list++, tab);
832 return 0;