Rename occurrences of USE_ to HAVE_ (again)
[s-mailx.git] / cmd2.c
blob67dbbda06f31e65ea727a65b8b588e01d55bf9f4
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 "rcv.h"
42 #include <sys/stat.h>
43 #include <sys/wait.h>
44 #include <unistd.h>
46 #include "extern.h"
48 static int save1(char *str, int mark, char const *cmd,
49 struct ignoretab *ignore, int convert,
50 int sender_record, int domove);
51 static char * snarf(char *linebuf, int *flag, int usembox);
52 static int delm(int *msgvec);
53 #ifdef HAVE_ASSERTS
54 static void clob1(int n);
55 #endif
56 static int ignore1(char **list, struct ignoretab *tab, char const *which);
57 static int igshow(struct ignoretab *tab, char const *which);
58 static int igcomp(const void *l, const void *r);
59 static void unignore_one(const char *name, struct ignoretab *tab);
60 static int unignore1(char **list, struct ignoretab *tab,
61 char const *which);
64 * If any arguments were given, go to the next applicable argument
65 * following dot, otherwise, go to the next applicable message.
66 * If given as first command with no arguments, print first message.
68 int
69 next(void *v)
71 int *msgvec = v;
72 struct message *mp;
73 int *ip, *ip2;
74 int list[2], mdot;
76 if (*msgvec != 0) {
79 * If some messages were supplied, find the
80 * first applicable one following dot using
81 * wrap around.
84 mdot = dot - &message[0] + 1;
87 * Find the first message in the supplied
88 * message list which follows dot.
91 for (ip = msgvec; *ip != 0; ip++) {
92 #ifdef _CRAY
94 * Work around an optimizer bug in Cray Standard C Version 4.0.3 (057126).
95 * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
97 #pragma _CRI suppress ip
98 #endif /* _CRAY */
99 if (mb.mb_threaded ? message[*ip-1].m_threadpos >
100 dot->m_threadpos :
101 *ip > mdot)
102 break;
104 if (*ip == 0)
105 ip = msgvec;
106 ip2 = ip;
107 do {
108 mp = &message[*ip2 - 1];
109 if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
110 setdot(mp);
111 goto hitit;
113 if (*ip2 != 0)
114 ip2++;
115 if (*ip2 == 0)
116 ip2 = msgvec;
117 } while (ip2 != ip);
118 printf(catgets(catd, CATSET, 21, "No messages applicable\n"));
119 return(1);
123 * If this is the first command, select message 1.
124 * Note that this must exist for us to get here at all.
127 if (!sawcom) {
128 if (msgCount == 0)
129 goto ateof;
130 goto hitit;
134 * Just find the next good message after dot, no
135 * wraparound.
138 if (mb.mb_threaded == 0) {
139 for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
140 if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN)) == 0)
141 break;
142 } else {
143 mp = dot;
144 if (did_print_dot)
145 mp = next_in_thread(mp);
146 while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN))
147 mp = next_in_thread(mp);
149 if (mp == NULL || mp >= &message[msgCount]) {
150 ateof:
151 printf(catgets(catd, CATSET, 22, "At EOF\n"));
152 return(0);
154 setdot(mp);
155 hitit:
157 * Print dot.
160 list[0] = dot - &message[0] + 1;
161 list[1] = 0;
162 return(type(list));
166 * Save a message in a file. Mark the message as saved
167 * so we can discard when the user quits.
169 int
170 save(void *v)
172 char *str = v;
174 return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
177 int
178 Save(void *v)
180 char *str = v;
182 return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
186 * Copy a message to a file without affected its saved-ness
188 int
189 copycmd(void *v)
191 char *str = v;
193 return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
196 int
197 Copycmd(void *v)
199 char *str = v;
201 return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
205 * Move a message to a file.
207 int
208 cmove(void *v)
210 char *str = v;
212 return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
215 int
216 cMove(void *v)
218 char *str = v;
220 return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
224 * Decrypt and copy a message to a file.
226 int
227 cdecrypt(void *v)
229 char *str = v;
231 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
234 int
235 cDecrypt(void *v)
237 char *str = v;
239 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
243 * Save/copy the indicated messages at the end of the passed file name.
244 * If mark is true, mark the message "saved."
246 static int
247 save1(char *str, int mark, char const *cmd, struct ignoretab *ignore,
248 int convert, int sender_record, int domove)
250 off_t mstats[2], tstats[2];
251 struct stat st;
252 int newfile = 0, compressed = 0, success = 1, last = 0, f, *msgvec, *ip;
253 struct message *mp;
254 char *file = NULL, *cp, *cq;
255 char const *disp = "";
256 FILE *obuf;
257 enum protocol prot;
259 /*LINTED*/
260 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
261 if (sender_record) {
262 for (cp = str; *cp && blankchar(*cp & 0377); cp++);
263 f = (*cp != '\0');
264 } else {
265 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
266 return(1);
268 if (!f) {
269 *msgvec = first(0, MMNORM);
270 if (*msgvec == 0) {
271 if (inhook)
272 return 0;
273 printf(catgets(catd, CATSET, 23,
274 "No messages to %s.\n"), cmd);
275 return(1);
277 msgvec[1] = 0;
279 if (f && getmsglist(str, msgvec, 0) < 0)
280 return(1);
281 if (*msgvec == 0) {
282 if (inhook)
283 return 0;
284 printf("No applicable messages.\n");
285 return 1;
287 if (sender_record) {
288 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
289 printf(catgets(catd, CATSET, 24,
290 "Cannot determine message sender to %s.\n"),
291 cmd);
292 return 1;
294 for (cq = cp; *cq && *cq != '@'; cq++);
295 *cq = '\0';
296 if (value("outfolder")) {
297 size_t sz = strlen(cp) + 1;
298 file = salloc(sz + 1);
299 file[0] = '+';
300 memcpy(file + 1, cp, sz);
301 } else
302 file = cp;
304 if ((file = expand(file)) == NULL)
305 return (1);
306 prot = which_protocol(file);
307 if (prot != PROTO_IMAP) {
308 if (access(file, 0) >= 0) {
309 newfile = 0;
310 disp = tr(25, "[Appended]");
311 } else {
312 newfile = 1;
313 disp = tr(26, "[New file]");
316 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
317 Zopen(file, "a+", &compressed)) == NULL) {
318 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
319 Zopen(file, "wx", &compressed)) == NULL) {
320 perror(file);
321 return(1);
323 } else {
324 if (compressed) {
325 newfile = 0;
326 disp = catgets(catd, CATSET, 25, "[Appended]");
328 if (!newfile && fstat(fileno(obuf), &st) &&
329 S_ISREG(st.st_mode) &&
330 fseek(obuf, -2L, SEEK_END) == 0) {
331 char buf[2];
332 int prependnl = 0;
334 switch (fread(buf, sizeof *buf, 2, obuf)) {
335 case 2:
336 if (buf[1] != '\n') {
337 prependnl = 1;
338 break;
340 /*FALLTHRU*/
341 case 1:
342 if (buf[0] != '\n')
343 prependnl = 1;
344 break;
345 default:
346 if (ferror(obuf)) {
347 perror(file);
348 return(1);
350 prependnl = 0;
352 fflush(obuf);
353 if (prependnl) {
354 putc('\n', obuf);
355 fflush(obuf);
359 tstats[0] = tstats[1] = 0;
360 imap_created_mailbox = 0;
361 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
362 mp = &message[*ip - 1];
363 if (prot == PROTO_IMAP &&
364 ignore[0].i_count == 0 &&
365 ignore[1].i_count == 0
366 #ifdef HAVE_IMAP /* TODO revisit */
367 && imap_thisaccount(file)
368 #endif
370 #ifdef HAVE_IMAP
371 if (imap_copy(mp, *ip, file) == STOP)
372 #endif
373 goto ferr;
374 #ifdef HAVE_IMAP
375 mstats[0] = -1;
376 mstats[1] = mp->m_xsize;
377 #endif
378 } else if (send(mp, obuf, ignore, NULL,
379 convert, mstats) < 0) {
380 perror(file);
381 goto ferr;
383 touch(mp);
384 if (mark)
385 mp->m_flag |= MSAVED;
386 if (domove) {
387 mp->m_flag |= MDELETED|MSAVED;
388 last = *ip;
390 tstats[0] += mstats[0];
391 tstats[1] += mstats[1];
393 fflush(obuf);
394 if (ferror(obuf)) {
395 perror(file);
396 ferr: success = 0;
398 if (Fclose(obuf) != 0)
399 success = 0;
400 if (success) {
401 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
402 disp = (
403 #ifdef HAVE_IMAP
404 ((prot == PROTO_IMAP) && disconnected(file))
405 ? "[Queued]" :
406 #endif
407 (imap_created_mailbox ? "[New file]"
408 : "[Appended]"));
410 printf("\"%s\" %s ", file, disp);
411 if (tstats[0] >= 0)
412 printf("%lu", (long)tstats[0]);
413 else
414 printf(tr(27, "binary"));
415 printf("/%lu\n", (long)tstats[1]);
416 } else if (mark) {
417 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
418 mp = &message[*ip - 1];
419 mp->m_flag &= ~MSAVED;
421 } else if (domove) {
422 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
423 mp = &message[*ip - 1];
424 mp->m_flag &= ~(MSAVED|MDELETED);
427 if (domove && last && success) {
428 setdot(&message[last-1]);
429 last = first(0, MDELETED);
430 setdot(&message[last ? last-1 : 0]);
432 return(success == 0);
436 * Write the indicated messages at the end of the passed
437 * file name, minus header and trailing blank line.
438 * This is the MIME save function.
440 int
441 cwrite(void *v)
443 char *str = v;
445 if (str == NULL || *str == '\0')
446 str = savestr("/dev/null");
447 return (save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0));
451 * Snarf the file from the end of the command line and
452 * return a pointer to it. If there is no file attached,
453 * return the mbox file. Put a null in front of the file
454 * name so that the message list processing won't see it,
455 * unless the file name is the only thing on the line, in
456 * which case, return 0 in the reference flag variable.
458 static char *
459 snarf(char *linebuf, int *flag, int usembox)
461 char *cp;
463 *flag = 1;
464 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
465 if (usembox) {
466 *flag = 0;
467 cp = expand("&");
468 } else
469 fprintf(stderr, tr(28, "No file specified.\n"));
471 return (cp);
475 * Delete messages.
477 int
478 delete(void *v)
480 int *msgvec = v;
481 delm(msgvec);
482 return 0;
486 * Delete messages, then type the new dot.
488 int
489 deltype(void *v)
491 int *msgvec = v;
492 int list[2];
493 int lastdot;
495 lastdot = dot - &message[0] + 1;
496 if (delm(msgvec) >= 0) {
497 list[0] = dot - &message[0] + 1;
498 if (list[0] > lastdot) {
499 touch(dot);
500 list[1] = 0;
501 return(type(list));
503 printf(catgets(catd, CATSET, 29, "At EOF\n"));
504 } else
505 printf(catgets(catd, CATSET, 30, "No more messages\n"));
506 return(0);
510 * Delete the indicated messages.
511 * Set dot to some nice place afterwards.
512 * Internal interface.
514 static int
515 delm(int *msgvec)
517 struct message *mp;
518 int *ip;
519 int last;
521 last = 0;
522 for (ip = msgvec; *ip != 0; ip++) {
523 mp = &message[*ip - 1];
524 touch(mp);
525 mp->m_flag |= MDELETED|MTOUCH;
526 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
527 last = *ip;
529 if (last != 0) {
530 setdot(&message[last-1]);
531 last = first(0, MDELETED);
532 if (last != 0) {
533 setdot(&message[last-1]);
534 return(0);
536 else {
537 setdot(&message[0]);
538 return(-1);
543 * Following can't happen -- it keeps lint happy
546 return(-1);
550 * Undelete the indicated messages.
552 int
553 undeletecmd(void *v)
555 int *msgvec = v;
556 struct message *mp;
557 int *ip;
559 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
560 mp = &message[*ip - 1];
561 touch(mp);
562 setdot(mp);
563 if (mp->m_flag & (MDELETED|MSAVED))
564 mp->m_flag &= ~(MDELETED|MSAVED);
565 else
566 mp->m_flag &= ~MDELETED;
567 #ifdef HAVE_IMAP
568 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
569 imap_undelete(mp, *ip);
570 #endif
572 return 0;
575 #ifdef HAVE_ASSERTS
577 * Interactively dump core on "core"
579 /*ARGSUSED*/
580 int
581 core(void *v)
583 int pid;
584 # ifdef WCOREDUMP
585 extern int wait_status;
586 # endif
587 (void)v;
589 switch (pid = fork()) {
590 case -1:
591 perror("fork");
592 return (1);
593 case 0:
594 abort();
595 _exit(1);
597 (void)printf(tr(31, "Okie dokie"));
598 (void)fflush(stdout);
599 (void)wait_child(pid);
600 # ifdef WCOREDUMP
601 if (WCOREDUMP(wait_status))
602 (void)printf(tr(32, " -- Core dumped.\n"));
603 else
604 (void)printf(tr(33, " -- Can't dump core.\n"));
605 # endif
606 return (0);
609 static void
610 clob1(int n)
612 char buf[512], *cp;
614 if (n <= 0)
615 return;
616 for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF)
618 clob1(n - 1);
622 * Clobber as many bytes of stack as the user requests.
624 int
625 clobber(void *v)
627 char **argv = v;
628 int times;
630 if (argv[0] == 0)
631 times = 1;
632 else
633 times = (atoi(argv[0]) + 511) / 512;
634 clob1(times);
635 return (0);
637 #endif /* HAVE_ASSERTS */
640 * Add the given header fields to the retained list.
641 * If no arguments, print the current list of retained fields.
643 int
644 retfield(void *v)
646 char **list = v;
648 return ignore1(list, ignore + 1, "retained");
652 * Add the given header fields to the ignored list.
653 * If no arguments, print the current list of ignored fields.
655 int
656 igfield(void *v)
658 char **list = v;
660 return ignore1(list, ignore, "ignored");
663 int
664 saveretfield(void *v)
666 char **list = v;
668 return ignore1(list, saveignore + 1, "retained");
671 int
672 saveigfield(void *v)
674 char **list = v;
676 return ignore1(list, saveignore, "ignored");
679 int
680 fwdretfield(void *v)
682 char **list = v;
684 return ignore1(list, fwdignore + 1, "retained");
687 int
688 fwdigfield(void *v)
690 char **list = v;
692 return ignore1(list, fwdignore, "ignored");
695 static int
696 ignore1(char **list, struct ignoretab *tab, char const *which)
698 int h;
699 struct ignore *igp;
700 char **ap;
702 if (*list == NULL)
703 return igshow(tab, which);
704 for (ap = list; *ap != 0; ap++) {
705 char *field;
706 size_t sz;
708 sz = strlen(*ap);
709 field = ac_alloc(sz + 1);
710 i_strcpy(field, *ap, sz + 1);
711 field[sz]='\0';
712 if (member(field, tab)) {
713 ac_free(field);
714 continue;
716 h = hash(field);
717 igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
718 sz = strlen(field) + 1;
719 igp->i_field = smalloc(sz);
720 memcpy(igp->i_field, field, sz);
721 igp->i_link = tab->i_head[h];
722 tab->i_head[h] = igp;
723 tab->i_count++;
724 ac_free(field);
726 return 0;
730 * Print out all currently retained fields.
732 static int
733 igshow(struct ignoretab *tab, char const *which)
735 int h;
736 struct ignore *igp;
737 char **ap, **ring;
739 if (tab->i_count == 0) {
740 printf(catgets(catd, CATSET, 34,
741 "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(catgets(catd, CATSET, 34,
828 "No fields currently being %s.\n"), which);
829 return 0;
831 while (*list)
832 unignore_one(*list++, tab);
833 return 0;