vars.c: use hash() caching, head resorting..
[s-mailx.git] / cmd2.c
blob2b7686b7b6aad90eaaad27d2e12a2b2b462fbc3e
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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"
41 #include "extern.h"
42 #include <sys/wait.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
47 * Mail -- a mail program
49 * More user commands.
52 static int save1(char *str, int mark, char *cmd, struct ignoretab *ignore,
53 int convert, int sender_record, int domove);
54 static char *snarf(char *linebuf, int *flag, int usembox);
55 static int delm(int *msgvec);
56 static int ignore1(char **list, struct ignoretab *tab, char *which);
57 static int igshow(struct ignoretab *tab, char *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, char *which);
63 * If any arguments were given, go to the next applicable argument
64 * following dot, otherwise, go to the next applicable message.
65 * If given as first command with no arguments, print first message.
67 int
68 next(void *v)
70 int *msgvec = v;
71 struct message *mp;
72 int *ip, *ip2;
73 int list[2], mdot;
75 if (*msgvec != 0) {
78 * If some messages were supplied, find the
79 * first applicable one following dot using
80 * wrap around.
83 mdot = dot - &message[0] + 1;
86 * Find the first message in the supplied
87 * message list which follows dot.
90 for (ip = msgvec; *ip != 0; ip++) {
91 #ifdef _CRAY
93 * Work around an optimizer bug in Cray Standard C Version 4.0.3 (057126).
94 * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
96 #pragma _CRI suppress ip
97 #endif /* _CRAY */
98 if (mb.mb_threaded ? message[*ip-1].m_threadpos >
99 dot->m_threadpos :
100 *ip > mdot)
101 break;
103 if (*ip == 0)
104 ip = msgvec;
105 ip2 = ip;
106 do {
107 mp = &message[*ip2 - 1];
108 if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
109 setdot(mp);
110 goto hitit;
112 if (*ip2 != 0)
113 ip2++;
114 if (*ip2 == 0)
115 ip2 = msgvec;
116 } while (ip2 != ip);
117 printf(catgets(catd, CATSET, 21, "No messages applicable\n"));
118 return(1);
122 * If this is the first command, select message 1.
123 * Note that this must exist for us to get here at all.
126 if (!sawcom) {
127 if (msgCount == 0)
128 goto ateof;
129 goto hitit;
133 * Just find the next good message after dot, no
134 * wraparound.
137 if (mb.mb_threaded == 0) {
138 for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
139 if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN|MKILL)) == 0)
140 break;
141 } else {
142 mp = dot;
143 if (did_print_dot)
144 mp = next_in_thread(mp);
145 while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN|MKILL))
146 mp = next_in_thread(mp);
148 if (mp == NULL || mp >= &message[msgCount]) {
149 ateof:
150 printf(catgets(catd, CATSET, 22, "At EOF\n"));
151 return(0);
153 setdot(mp);
154 hitit:
156 * Print dot.
159 list[0] = dot - &message[0] + 1;
160 list[1] = 0;
161 return(type(list));
165 * Save a message in a file. Mark the message as saved
166 * so we can discard when the user quits.
168 int
169 save(void *v)
171 char *str = v;
173 return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
176 int
177 Save(void *v)
179 char *str = v;
181 return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
185 * Copy a message to a file without affected its saved-ness
187 int
188 copycmd(void *v)
190 char *str = v;
192 return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
195 int
196 Copycmd(void *v)
198 char *str = v;
200 return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
204 * Move a message to a file.
206 int
207 cmove(void *v)
209 char *str = v;
211 return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
214 int
215 cMove(void *v)
217 char *str = v;
219 return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
223 * Decrypt and copy a message to a file.
225 int
226 cdecrypt(void *v)
228 char *str = v;
230 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
233 int
234 cDecrypt(void *v)
236 char *str = v;
238 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
242 * Save/copy the indicated messages at the end of the passed file name.
243 * If mark is true, mark the message "saved."
245 static int
246 save1(char *str, int mark, char *cmd, struct ignoretab *ignore,
247 int convert, int sender_record, int domove)
249 struct stat st;
250 int *ip;
251 struct message *mp;
252 char *file = NULL, *disp = "";
253 int f, *msgvec;
254 FILE *obuf;
255 int newfile = 0;
256 char *cp, *cq;
257 off_t mstats[2], tstats[2];
258 int compressed = 0;
259 enum protocol prot;
260 int success = 1, last = 0;
262 /*LINTED*/
263 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
264 if (sender_record) {
265 for (cp = str; *cp && blankchar(*cp & 0377); cp++);
266 f = (*cp != '\0');
267 } else {
268 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
269 return(1);
271 if (!f) {
272 *msgvec = first(0, MMNORM);
273 if (*msgvec == 0) {
274 if (inhook)
275 return 0;
276 printf(catgets(catd, CATSET, 23,
277 "No messages to %s.\n"), cmd);
278 return(1);
280 msgvec[1] = 0;
282 if (f && getmsglist(str, msgvec, 0) < 0)
283 return(1);
284 if (*msgvec == 0) {
285 if (inhook)
286 return 0;
287 printf("No applicable messages.\n");
288 return 1;
290 if (sender_record) {
291 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
292 printf(catgets(catd, CATSET, 24,
293 "Cannot determine message sender to %s.\n"),
294 cmd);
295 return 1;
297 for (cq = cp; *cq && *cq != '@'; cq++);
298 *cq = '\0';
299 if (value("outfolder")) {
300 file = salloc(strlen(cp) + 2);
301 file[0] = '+';
302 strcpy(&file[1], cp);
303 } else
304 file = cp;
306 if ((file = expand(file)) == NULL)
307 return(1);
308 prot = which_protocol(file);
309 if (prot != PROTO_IMAP) {
310 if (access(file, 0) >= 0) {
311 newfile = 0;
312 disp = catgets(catd, CATSET, 25, "[Appended]");
313 } else {
314 newfile = 1;
315 disp = catgets(catd, CATSET, 26, "[New file]");
318 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
319 Zopen(file, "a+", &compressed)) == NULL) {
320 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
321 Zopen(file, "wx", &compressed)) == NULL) {
322 perror(file);
323 return(1);
325 } else {
326 if (compressed) {
327 newfile = 0;
328 disp = catgets(catd, CATSET, 25, "[Appended]");
330 if (!newfile && fstat(fileno(obuf), &st) &&
331 S_ISREG(st.st_mode) &&
332 fseek(obuf, -2L, SEEK_END) == 0) {
333 char buf[2];
334 int prependnl = 0;
336 switch (fread(buf, sizeof *buf, 2, obuf)) {
337 case 2:
338 if (buf[1] != '\n') {
339 prependnl = 1;
340 break;
342 /*FALLTHRU*/
343 case 1:
344 if (buf[0] != '\n')
345 prependnl = 1;
346 break;
347 default:
348 if (ferror(obuf)) {
349 perror(file);
350 return(1);
352 prependnl = 0;
354 fflush(obuf);
355 if (prependnl) {
356 putc('\n', obuf);
357 fflush(obuf);
361 tstats[0] = tstats[1] = 0;
362 imap_created_mailbox = 0;
363 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
364 mp = &message[*ip - 1];
365 if (prot == PROTO_IMAP &&
366 ignore[0].i_count == 0 &&
367 ignore[1].i_count == 0 &&
368 imap_thisaccount(file)) {
369 if (imap_copy(mp, *ip, file) == STOP)
370 goto ferr;
371 mstats[0] = -1;
372 mstats[1] = mp->m_xsize;
373 } else if (send(mp, obuf, ignore, NULL,
374 convert, mstats) < 0) {
375 perror(file);
376 goto ferr;
378 touch(mp);
379 if (mark)
380 mp->m_flag |= MSAVED;
381 if (domove) {
382 mp->m_flag |= MDELETED|MSAVED;
383 last = *ip;
385 tstats[0] += mstats[0];
386 tstats[1] += mstats[1];
388 fflush(obuf);
389 if (ferror(obuf)) {
390 perror(file);
391 ferr: success = 0;
393 if (Fclose(obuf) != 0)
394 success = 0;
395 if (success) {
396 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR)
397 disp = prot == PROTO_IMAP && disconnected(file) ?
398 "[Queued]" : imap_created_mailbox ?
399 "[New file]" : "[Appended]";
400 printf("\"%s\" %s ", file, disp);
401 if (tstats[0] >= 0)
402 printf("%lu", (long)tstats[0]);
403 else
404 printf(catgets(catd, CATSET, 27, "binary"));
405 printf("/%lu\n", (long)tstats[1]);
406 } else if (mark) {
407 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
408 mp = &message[*ip - 1];
409 mp->m_flag &= ~MSAVED;
411 } else if (domove) {
412 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
413 mp = &message[*ip - 1];
414 mp->m_flag &= ~(MSAVED|MDELETED);
417 if (domove && last && success) {
418 setdot(&message[last-1]);
419 last = first(0, MDELETED);
420 setdot(&message[last ? last-1 : 0]);
422 return(success == 0);
426 * Write the indicated messages at the end of the passed
427 * file name, minus header and trailing blank line.
428 * This is the MIME save function.
430 int
431 cwrite(void *v)
433 char *str = v;
435 return save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
439 * Snarf the file from the end of the command line and
440 * return a pointer to it. If there is no file attached,
441 * return the mbox file. Put a null in front of the file
442 * name so that the message list processing won't see it,
443 * unless the file name is the only thing on the line, in
444 * which case, return 0 in the reference flag variable.
447 static char *
448 snarf(char *linebuf, int *flag, int usembox)
450 char *cp;
452 *flag = 1;
453 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
454 if (usembox) {
455 *flag = 0;
456 return expand("&");
457 } else {
458 printf(catgets(catd, CATSET, 28,
459 "No file specified.\n"));
460 return NULL;
463 return(cp);
467 * Delete messages.
469 int
470 delete(void *v)
472 int *msgvec = v;
473 delm(msgvec);
474 return 0;
478 * Delete messages, then type the new dot.
480 int
481 deltype(void *v)
483 int *msgvec = v;
484 int list[2];
485 int lastdot;
487 lastdot = dot - &message[0] + 1;
488 if (delm(msgvec) >= 0) {
489 list[0] = dot - &message[0] + 1;
490 if (list[0] > lastdot) {
491 touch(dot);
492 list[1] = 0;
493 return(type(list));
495 printf(catgets(catd, CATSET, 29, "At EOF\n"));
496 } else
497 printf(catgets(catd, CATSET, 30, "No more messages\n"));
498 return(0);
502 * Delete the indicated messages.
503 * Set dot to some nice place afterwards.
504 * Internal interface.
506 static int
507 delm(int *msgvec)
509 struct message *mp;
510 int *ip;
511 int last;
513 last = 0;
514 for (ip = msgvec; *ip != 0; ip++) {
515 mp = &message[*ip - 1];
516 touch(mp);
517 mp->m_flag |= MDELETED|MTOUCH;
518 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
519 last = *ip;
521 if (last != 0) {
522 setdot(&message[last-1]);
523 last = first(0, MDELETED);
524 if (last != 0) {
525 setdot(&message[last-1]);
526 return(0);
528 else {
529 setdot(&message[0]);
530 return(-1);
535 * Following can't happen -- it keeps lint happy
538 return(-1);
542 * Undelete the indicated messages.
544 int
545 undeletecmd(void *v)
547 int *msgvec = v;
548 struct message *mp;
549 int *ip;
551 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
552 mp = &message[*ip - 1];
553 touch(mp);
554 setdot(mp);
555 if (mp->m_flag & (MDELETED|MSAVED))
556 mp->m_flag &= ~(MDELETED|MSAVED);
557 else
558 mp->m_flag &= ~MDELETED;
559 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
560 imap_undelete(mp, *ip);
562 return 0;
565 #ifdef DEBUG_COMMANDS
567 * Interactively dump core on "core"
569 /*ARGSUSED*/
570 int
571 core(void *v)
573 int pid;
574 #ifdef WCOREDUMP
575 extern int wait_status;
576 #endif
578 switch (pid = fork()) {
579 case -1:
580 perror("fork");
581 return(1);
582 case 0:
583 abort();
584 _exit(1);
586 printf(catgets(catd, CATSET, 31, "Okie dokie"));
587 fflush(stdout);
588 wait_child(pid);
589 #ifdef WCOREDUMP
590 if (WCOREDUMP(wait_status))
591 printf(catgets(catd, CATSET, 32, " -- Core dumped.\n"));
592 else
593 printf(catgets(catd, CATSET, 33, " -- Can't dump core.\n"));
594 #endif
595 return 0;
599 * Clobber as many bytes of stack as the user requests.
601 int
602 clobber(void *v)
604 char **argv = v;
605 int times;
607 if (argv[0] == 0)
608 times = 1;
609 else
610 times = (atoi(argv[0]) + 511) / 512;
611 clob1(times);
612 return 0;
616 * Clobber the stack.
618 static void
619 clob1(int n)
621 char buf[512];
622 char *cp;
624 if (n <= 0)
625 return;
626 for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF)
628 clob1(n - 1);
630 #endif /* DEBUG_COMMANDS */
633 * Add the given header fields to the retained list.
634 * If no arguments, print the current list of retained fields.
636 int
637 retfield(void *v)
639 char **list = v;
641 return ignore1(list, ignore + 1, "retained");
645 * Add the given header fields to the ignored list.
646 * If no arguments, print the current list of ignored fields.
648 int
649 igfield(void *v)
651 char **list = v;
653 return ignore1(list, ignore, "ignored");
656 int
657 saveretfield(void *v)
659 char **list = v;
661 return ignore1(list, saveignore + 1, "retained");
664 int
665 saveigfield(void *v)
667 char **list = v;
669 return ignore1(list, saveignore, "ignored");
672 int
673 fwdretfield(void *v)
675 char **list = v;
677 return ignore1(list, fwdignore + 1, "retained");
680 int
681 fwdigfield(void *v)
683 char **list = v;
685 return ignore1(list, fwdignore, "ignored");
688 static int
689 ignore1(char **list, struct ignoretab *tab, char *which)
691 int h;
692 struct ignore *igp;
693 char **ap;
695 if (*list == NULL)
696 return igshow(tab, which);
697 for (ap = list; *ap != 0; ap++) {
698 char *field;
699 size_t sz;
701 sz = strlen(*ap);
702 field = ac_alloc(sz + 1);
703 i_strcpy(field, *ap, sz + 1);
704 field[sz]='\0';
705 if (member(field, tab)) {
706 ac_free(field);
707 continue;
709 h = hash(field);
710 igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
711 igp->i_field = smalloc(strlen(field) + 1);
712 strcpy(igp->i_field, field);
713 igp->i_link = tab->i_head[h];
714 tab->i_head[h] = igp;
715 tab->i_count++;
716 ac_free(field);
718 return 0;
722 * Print out all currently retained fields.
724 static int
725 igshow(struct ignoretab *tab, char *which)
727 int h;
728 struct ignore *igp;
729 char **ap, **ring;
731 if (tab->i_count == 0) {
732 printf(catgets(catd, CATSET, 34,
733 "No fields currently being %s.\n"), which);
734 return 0;
736 /*LINTED*/
737 ring = (char **)salloc((tab->i_count + 1) * sizeof (char *));
738 ap = ring;
739 for (h = 0; h < HSHSIZE; h++)
740 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
741 *ap++ = igp->i_field;
742 *ap = 0;
743 qsort(ring, tab->i_count, sizeof (char *), igcomp);
744 for (ap = ring; *ap != 0; ap++)
745 printf("%s\n", *ap);
746 return 0;
750 * Compare two names for sorting ignored field list.
752 static int
753 igcomp(const void *l, const void *r)
755 return (strcmp(*(char **)l, *(char **)r));
758 int
759 unignore(void *v)
761 return unignore1((char **)v, ignore, "ignored");
764 int
765 unretain(void *v)
767 return unignore1((char **)v, ignore + 1, "retained");
770 int
771 unsaveignore(void *v)
773 return unignore1((char **)v, saveignore, "ignored");
776 int
777 unsaveretain(void *v)
779 return unignore1((char **)v, saveignore + 1, "retained");
782 int
783 unfwdignore(void *v)
785 return unignore1((char **)v, fwdignore, "ignored");
788 int
789 unfwdretain(void *v)
791 return unignore1((char **)v, fwdignore + 1, "retained");
794 static void
795 unignore_one(const char *name, struct ignoretab *tab)
797 struct ignore *ip, *iq = NULL;
798 int h = hash(name);
800 for (ip = tab->i_head[h]; ip; ip = ip->i_link) {
801 if (asccasecmp(ip->i_field, name) == 0) {
802 free(ip->i_field);
803 if (iq != NULL)
804 iq->i_link = ip->i_link;
805 else
806 tab->i_head[h] = ip->i_link;
807 free(ip);
808 tab->i_count--;
809 break;
811 iq = ip;
815 static int
816 unignore1(char **list, struct ignoretab *tab, char *which)
818 if (tab->i_count == 0) {
819 printf(catgets(catd, CATSET, 34,
820 "No fields currently being %s.\n"), which);
821 return 0;
823 while (*list)
824 unignore_one(*list++, tab);
825 return 0;