1 /*
2  * Heirloom mailx - a mail user agent derived from Berkeley Mail.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  */
6 /*
7  * Copyright (c) 1980, 1993
8  *	The Regents of the University of California.  All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the University of
21  *	California, Berkeley and its contributors.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #ifndef lint
40 #ifdef	DOSCCS
41 static char sccsid[] = "@(#)cmd2.c	2.46 (gritter) 3/4/06";
42 #endif
43 #endif /* not lint */
44 
45 #include "rcv.h"
46 #include "extern.h"
47 #include <sys/wait.h>
48 #include <sys/stat.h>
49 #include <unistd.h>
50 
51 /*
52  * Mail -- a mail program
53  *
54  * More user commands.
55  */
56 
57 static int save1(char *str, int mark, char *cmd, struct ignoretab *ignore,
58 		int convert, int sender_record, int domove);
59 static char *snarf(char *linebuf, int *flag, int usembox);
60 static int delm(int *msgvec);
61 static int ignore1(char **list, struct ignoretab *tab, char *which);
62 static int igshow(struct ignoretab *tab, char *which);
63 static int igcomp(const void *l, const void *r);
64 static void unignore_one(const char *name, struct ignoretab *tab);
65 static int unignore1(char **list, struct ignoretab *tab, char *which);
66 
67 /*
68  * If any arguments were given, go to the next applicable argument
69  * following dot, otherwise, go to the next applicable message.
70  * If given as first command with no arguments, print first message.
71  */
72 int
next(void * v)73 next(void *v)
74 {
75 	int *msgvec = v;
76 	struct message *mp;
77 	int *ip, *ip2;
78 	int list[2], mdot;
79 
80 	if (*msgvec != 0) {
81 
82 		/*
83 		 * If some messages were supplied, find the
84 		 * first applicable one following dot using
85 		 * wrap around.
86 		 */
87 
88 		mdot = dot - &message[0] + 1;
89 
90 		/*
91 		 * Find the first message in the supplied
92 		 * message list which follows dot.
93 		 */
94 
95 		for (ip = msgvec; *ip != 0; ip++) {
96 #ifdef	_CRAY
97 /*
98  * Work around an optimizer bug in Cray Standard C Version 4.0.3  (057126).
99  * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
100  */
101 #pragma _CRI suppress ip
102 #endif	/* _CRAY */
103 			if (mb.mb_threaded ? message[*ip-1].m_threadpos >
104 						dot->m_threadpos :
105 					*ip > mdot)
106 				break;
107 		}
108 		if (*ip == 0)
109 			ip = msgvec;
110 		ip2 = ip;
111 		do {
112 			mp = &message[*ip2 - 1];
113 			if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
114 				setdot(mp);
115 				goto hitit;
116 			}
117 			if (*ip2 != 0)
118 				ip2++;
119 			if (*ip2 == 0)
120 				ip2 = msgvec;
121 		} while (ip2 != ip);
122 		printf(catgets(catd, CATSET, 21, "No messages applicable\n"));
123 		return(1);
124 	}
125 
126 	/*
127 	 * If this is the first command, select message 1.
128 	 * Note that this must exist for us to get here at all.
129 	 */
130 
131 	if (!sawcom) {
132 		if (msgCount == 0)
133 			goto ateof;
134 		goto hitit;
135 	}
136 
137 	/*
138 	 * Just find the next good message after dot, no
139 	 * wraparound.
140 	 */
141 
142 	if (mb.mb_threaded == 0) {
143 		for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
144 			if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN|MKILL)) == 0)
145 				break;
146 	} else {
147 		mp = dot;
148 		if (did_print_dot)
149 			mp = next_in_thread(mp);
150 		while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN|MKILL))
151 			mp = next_in_thread(mp);
152 	}
153 	if (mp == NULL || mp >= &message[msgCount]) {
154 ateof:
155 		printf(catgets(catd, CATSET, 22, "At EOF\n"));
156 		return(0);
157 	}
158 	setdot(mp);
159 hitit:
160 	/*
161 	 * Print dot.
162 	 */
163 
164 	list[0] = dot - &message[0] + 1;
165 	list[1] = 0;
166 	return(type(list));
167 }
168 
169 /*
170  * Save a message in a file.  Mark the message as saved
171  * so we can discard when the user quits.
172  */
173 int
save(void * v)174 save(void *v)
175 {
176 	char *str = v;
177 
178 	return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
179 }
180 
181 int
Save(void * v)182 Save(void *v)
183 {
184 	char *str = v;
185 
186 	return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
187 }
188 
189 /*
190  * Copy a message to a file without affected its saved-ness
191  */
192 int
copycmd(void * v)193 copycmd(void *v)
194 {
195 	char *str = v;
196 
197 	return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
198 }
199 
200 int
Copycmd(void * v)201 Copycmd(void *v)
202 {
203 	char *str = v;
204 
205 	return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
206 }
207 
208 /*
209  * Move a message to a file.
210  */
211 int
cmove(void * v)212 cmove(void *v)
213 {
214 	char *str = v;
215 
216 	return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
217 }
218 
219 int
cMove(void * v)220 cMove(void *v)
221 {
222 	char *str = v;
223 
224 	return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
225 }
226 
227 /*
228  * Decrypt and copy a message to a file.
229  */
230 int
cdecrypt(void * v)231 cdecrypt(void *v)
232 {
233 	char *str = v;
234 
235 	return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
236 }
237 
238 int
cDecrypt(void * v)239 cDecrypt(void *v)
240 {
241 	char *str = v;
242 
243 	return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
244 }
245 
246 /*
247  * Save/copy the indicated messages at the end of the passed file name.
248  * If mark is true, mark the message "saved."
249  */
250 static int
save1(char * str,int mark,char * cmd,struct ignoretab * ignore,int convert,int sender_record,int domove)251 save1(char *str, int mark, char *cmd, struct ignoretab *ignore,
252 		int convert, int sender_record, int domove)
253 {
254 	struct stat	st;
255 	int *ip;
256 	struct message *mp;
257 	char *file = NULL, *disp = "";
258 	int f, *msgvec;
259 	FILE *obuf;
260 	int newfile = 0;
261 	char *cp, *cq;
262 	off_t mstats[2], tstats[2];
263 	int compressed = 0;
264 	enum protocol prot;
265 	int success = 1, last = 0;
266 
267 	/*LINTED*/
268 	msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
269 	if (sender_record) {
270 		for (cp = str; *cp && blankchar(*cp & 0377); cp++);
271 		f = (*cp != '\0');
272 	} else {
273 		if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
274 			return(1);
275 	}
276 	if (!f) {
277 		*msgvec = first(0, MMNORM);
278 		if (*msgvec == 0) {
279 			if (inhook)
280 				return 0;
281 			printf(catgets(catd, CATSET, 23,
282 					"No messages to %s.\n"), cmd);
283 			return(1);
284 		}
285 		msgvec[1] = 0;
286 	}
287 	if (f && getmsglist(str, msgvec, 0) < 0)
288 		return(1);
289 	if (*msgvec == 0) {
290 		if (inhook)
291 			return 0;
292 		printf("No applicable messages.\n");
293 		return 1;
294 	}
295 	if (sender_record) {
296 		if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
297 			printf(catgets(catd, CATSET, 24,
298 				"Cannot determine message sender to %s.\n"),
299 				cmd);
300 			return 1;
301 		}
302 		for (cq = cp; *cq && *cq != '@'; cq++);
303 		*cq = '\0';
304 		if (value("outfolder")) {
305 			file = salloc(strlen(cp) + 2);
306 			file[0] = '+';
307 			strcpy(&file[1], cp);
308 		} else
309 			file = cp;
310 	}
311 	if ((file = expand(file)) == NULL)
312 		return(1);
313 	prot = which_protocol(file);
314 	if (prot != PROTO_IMAP) {
315 		if (access(file, 0) >= 0) {
316 			newfile = 0;
317 			disp = catgets(catd, CATSET, 25, "[Appended]");
318 		} else {
319 			newfile = 1;
320 			disp = catgets(catd, CATSET, 26, "[New file]");
321 		}
322 	}
323 	if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
324 			Zopen(file, "a+", &compressed)) == NULL) {
325 		if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
326 				Zopen(file, "wx", &compressed)) == NULL) {
327 			perror(file);
328 			return(1);
329 		}
330 	} else {
331 		if (compressed) {
332 			newfile = 0;
333 			disp = catgets(catd, CATSET, 25, "[Appended]");
334 		}
335 		if (!newfile && fstat(fileno(obuf), &st) &&
336 				S_ISREG(st.st_mode) &&
337 				fseek(obuf, -2L, SEEK_END) == 0) {
338 			char buf[2];
339 			int prependnl = 0;
340 
341 			switch (fread(buf, sizeof *buf, 2, obuf)) {
342 			case 2:
343 				if (buf[1] != '\n') {
344 					prependnl = 1;
345 					break;
346 				}
347 				/*FALLTHRU*/
348 			case 1:
349 				if (buf[0] != '\n')
350 					prependnl = 1;
351 				break;
352 			default:
353 				if (ferror(obuf)) {
354 					perror(file);
355 					return(1);
356 				}
357 				prependnl = 0;
358 			}
359 			fflush(obuf);
360 			if (prependnl) {
361 				putc('\n', obuf);
362 				fflush(obuf);
363 			}
364 		}
365 	}
366 	tstats[0] = tstats[1] = 0;
367 	imap_created_mailbox = 0;
368 	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
369 		mp = &message[*ip - 1];
370 		if (prot == PROTO_IMAP &&
371 				ignore[0].i_count == 0 &&
372 				ignore[1].i_count == 0 &&
373 				imap_thisaccount(file)) {
374 			if (imap_copy(mp, *ip, file) == STOP)
375 				goto ferr;
376 			mstats[0] = -1;
377 			mstats[1] = mp->m_xsize;
378 		} else if (send(mp, obuf, ignore, NULL,
379 					convert, mstats) < 0) {
380 			perror(file);
381 			goto ferr;
382 		}
383 		touch(mp);
384 		if (mark)
385 			mp->m_flag |= MSAVED;
386 		if (domove) {
387 			mp->m_flag |= MDELETED|MSAVED;
388 			last = *ip;
389 		}
390 		tstats[0] += mstats[0];
391 		tstats[1] += mstats[1];
392 	}
393 	fflush(obuf);
394 	if (ferror(obuf)) {
395 		perror(file);
396 	ferr:	success = 0;
397 	}
398 	if (Fclose(obuf) != 0)
399 		success = 0;
400 	if (success) {
401 		if (prot == PROTO_IMAP || prot == PROTO_MAILDIR)
402 			disp = prot == PROTO_IMAP && disconnected(file) ?
403 				"[Queued]" : imap_created_mailbox ?
404 					"[New file]" : "[Appended]";
405 		printf("\"%s\" %s ", file, disp);
406 		if (tstats[0] >= 0)
407 			printf("%lu", (long)tstats[0]);
408 		else
409 			printf(catgets(catd, CATSET, 27, "binary"));
410 		printf("/%lu\n", (long)tstats[1]);
411 	} else if (mark) {
412 		for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
413 			mp = &message[*ip - 1];
414 			mp->m_flag &= ~MSAVED;
415 		}
416 	} else if (domove) {
417 		for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
418 			mp = &message[*ip - 1];
419 			mp->m_flag &= ~(MSAVED|MDELETED);
420 		}
421 	}
422 	if (domove && last && success) {
423 		setdot(&message[last-1]);
424 		last = first(0, MDELETED);
425 		setdot(&message[last ? last-1 : 0]);
426 	}
427 	return(success == 0);
428 }
429 
430 /*
431  * Write the indicated messages at the end of the passed
432  * file name, minus header and trailing blank line.
433  * This is the MIME save function.
434  */
435 int
cwrite(void * v)436 cwrite(void *v)
437 {
438 	char *str = v;
439 
440 	return save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
441 }
442 
443 /*
444  * Snarf the file from the end of the command line and
445  * return a pointer to it.  If there is no file attached,
446  * return the mbox file.  Put a null in front of the file
447  * name so that the message list processing won't see it,
448  * unless the file name is the only thing on the line, in
449  * which case, return 0 in the reference flag variable.
450  */
451 
452 static char *
snarf(char * linebuf,int * flag,int usembox)453 snarf(char *linebuf, int *flag, int usembox)
454 {
455 	char *cp;
456 
457 	*flag = 1;
458 	if ((cp = laststring(linebuf, flag, 0)) == NULL) {
459 		if (usembox) {
460 			*flag = 0;
461 			return expand("&");
462 		} else {
463 			printf(catgets(catd, CATSET, 28,
464 						"No file specified.\n"));
465 			return NULL;
466 		}
467 	}
468 	return(cp);
469 }
470 
471 /*
472  * Delete messages.
473  */
474 int
delete(void * v)475 delete(void *v)
476 {
477 	int *msgvec = v;
478 	delm(msgvec);
479 	return 0;
480 }
481 
482 /*
483  * Delete messages, then type the new dot.
484  */
485 int
deltype(void * v)486 deltype(void *v)
487 {
488 	int *msgvec = v;
489 	int list[2];
490 	int lastdot;
491 
492 	lastdot = dot - &message[0] + 1;
493 	if (delm(msgvec) >= 0) {
494 		list[0] = dot - &message[0] + 1;
495 		if (list[0] > lastdot) {
496 			touch(dot);
497 			list[1] = 0;
498 			return(type(list));
499 		}
500 		printf(catgets(catd, CATSET, 29, "At EOF\n"));
501 	} else
502 		printf(catgets(catd, CATSET, 30, "No more messages\n"));
503 	return(0);
504 }
505 
506 /*
507  * Delete the indicated messages.
508  * Set dot to some nice place afterwards.
509  * Internal interface.
510  */
511 static int
delm(int * msgvec)512 delm(int *msgvec)
513 {
514 	struct message *mp;
515 	int *ip;
516 	int last;
517 
518 	last = 0;
519 	for (ip = msgvec; *ip != 0; ip++) {
520 		mp = &message[*ip - 1];
521 		touch(mp);
522 		mp->m_flag |= MDELETED|MTOUCH;
523 		mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
524 		last = *ip;
525 	}
526 	if (last != 0) {
527 		setdot(&message[last-1]);
528 		last = first(0, MDELETED);
529 		if (last != 0) {
530 			setdot(&message[last-1]);
531 			return(0);
532 		}
533 		else {
534 			setdot(&message[0]);
535 			return(-1);
536 		}
537 	}
538 
539 	/*
540 	 * Following can't happen -- it keeps lint happy
541 	 */
542 
543 	return(-1);
544 }
545 
546 /*
547  * Undelete the indicated messages.
548  */
549 int
undeletecmd(void * v)550 undeletecmd(void *v)
551 {
552 	int *msgvec = v;
553 	struct message *mp;
554 	int *ip;
555 
556 	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
557 		mp = &message[*ip - 1];
558 		touch(mp);
559 		setdot(mp);
560 		if (mp->m_flag & (MDELETED|MSAVED))
561 			mp->m_flag &= ~(MDELETED|MSAVED);
562 		else
563 			mp->m_flag &= ~MDELETED;
564 		if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
565 			imap_undelete(mp, *ip);
566 	}
567 	return 0;
568 }
569 
570 #ifdef	DEBUG_COMMANDS
571 /*
572  * Interactively dump core on "core"
573  */
574 /*ARGSUSED*/
575 int
core(void * v)576 core(void *v)
577 {
578 	int pid;
579 #ifdef	WCOREDUMP
580 	extern int wait_status;
581 #endif
582 
583 	switch (pid = fork()) {
584 	case -1:
585 		perror("fork");
586 		return(1);
587 	case 0:
588 		abort();
589 		_exit(1);
590 	}
591 	printf(catgets(catd, CATSET, 31, "Okie dokie"));
592 	fflush(stdout);
593 	wait_child(pid);
594 #ifdef	WCOREDUMP
595 	if (WCOREDUMP(wait_status))
596 		printf(catgets(catd, CATSET, 32, " -- Core dumped.\n"));
597 	else
598 		printf(catgets(catd, CATSET, 33, " -- Can't dump core.\n"));
599 #endif
600 	return 0;
601 }
602 
603 /*
604  * Clobber as many bytes of stack as the user requests.
605  */
606 int
clobber(void * v)607 clobber(void *v)
608 {
609 	char **argv = v;
610 	int times;
611 
612 	if (argv[0] == 0)
613 		times = 1;
614 	else
615 		times = (atoi(argv[0]) + 511) / 512;
616 	clob1(times);
617 	return 0;
618 }
619 
620 /*
621  * Clobber the stack.
622  */
623 static void
clob1(int n)624 clob1(int n)
625 {
626 	char buf[512];
627 	char *cp;
628 
629 	if (n <= 0)
630 		return;
631 	for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF)
632 		;
633 	clob1(n - 1);
634 }
635 #endif	/* DEBUG_COMMANDS */
636 
637 /*
638  * Add the given header fields to the retained list.
639  * If no arguments, print the current list of retained fields.
640  */
641 int
retfield(void * v)642 retfield(void *v)
643 {
644 	char **list = v;
645 
646 	return ignore1(list, ignore + 1, "retained");
647 }
648 
649 /*
650  * Add the given header fields to the ignored list.
651  * If no arguments, print the current list of ignored fields.
652  */
653 int
igfield(void * v)654 igfield(void *v)
655 {
656 	char **list = v;
657 
658 	return ignore1(list, ignore, "ignored");
659 }
660 
661 int
saveretfield(void * v)662 saveretfield(void *v)
663 {
664 	char **list = v;
665 
666 	return ignore1(list, saveignore + 1, "retained");
667 }
668 
669 int
saveigfield(void * v)670 saveigfield(void *v)
671 {
672 	char **list = v;
673 
674 	return ignore1(list, saveignore, "ignored");
675 }
676 
677 int
fwdretfield(void * v)678 fwdretfield(void *v)
679 {
680 	char **list = v;
681 
682 	return ignore1(list, fwdignore + 1, "retained");
683 }
684 
685 int
fwdigfield(void * v)686 fwdigfield(void *v)
687 {
688 	char **list = v;
689 
690 	return ignore1(list, fwdignore, "ignored");
691 }
692 
693 static int
ignore1(char ** list,struct ignoretab * tab,char * which)694 ignore1(char **list, struct ignoretab *tab, char *which)
695 {
696 	int h;
697 	struct ignore *igp;
698 	char **ap;
699 
700 	if (*list == NULL)
701 		return igshow(tab, which);
702 	for (ap = list; *ap != 0; ap++) {
703 		char *field;
704 		size_t sz;
705 
706 		sz = strlen(*ap);
707 		field = ac_alloc(sz + 1);
708 		i_strcpy(field, *ap, sz + 1);
709 		field[sz]='\0';
710 		if (member(field, tab)) {
711 			ac_free(field);
712 			continue;
713 		}
714 		h = hash(field);
715 		igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
716 		igp->i_field = smalloc(strlen(field) + 1);
717 		strcpy(igp->i_field, field);
718 		igp->i_link = tab->i_head[h];
719 		tab->i_head[h] = igp;
720 		tab->i_count++;
721 		ac_free(field);
722 	}
723 	return 0;
724 }
725 
726 /*
727  * Print out all currently retained fields.
728  */
729 static int
igshow(struct ignoretab * tab,char * which)730 igshow(struct ignoretab *tab, char *which)
731 {
732 	int h;
733 	struct ignore *igp;
734 	char **ap, **ring;
735 
736 	if (tab->i_count == 0) {
737 		printf(catgets(catd, CATSET, 34,
738 				"No fields currently being %s.\n"), which);
739 		return 0;
740 	}
741 	/*LINTED*/
742 	ring = (char **)salloc((tab->i_count + 1) * sizeof (char *));
743 	ap = ring;
744 	for (h = 0; h < HSHSIZE; h++)
745 		for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
746 			*ap++ = igp->i_field;
747 	*ap = 0;
748 	qsort(ring, tab->i_count, sizeof (char *), igcomp);
749 	for (ap = ring; *ap != 0; ap++)
750 		printf("%s\n", *ap);
751 	return 0;
752 }
753 
754 /*
755  * Compare two names for sorting ignored field list.
756  */
757 static int
igcomp(const void * l,const void * r)758 igcomp(const void *l, const void *r)
759 {
760 	return (strcmp(*(char **)l, *(char **)r));
761 }
762 
763 int
unignore(void * v)764 unignore(void *v)
765 {
766 	return unignore1((char **)v, ignore, "ignored");
767 }
768 
769 int
unretain(void * v)770 unretain(void *v)
771 {
772 	return unignore1((char **)v, ignore + 1, "retained");
773 }
774 
775 int
unsaveignore(void * v)776 unsaveignore(void *v)
777 {
778 	return unignore1((char **)v, saveignore, "ignored");
779 }
780 
781 int
unsaveretain(void * v)782 unsaveretain(void *v)
783 {
784 	return unignore1((char **)v, saveignore + 1, "retained");
785 }
786 
787 int
unfwdignore(void * v)788 unfwdignore(void *v)
789 {
790 	return unignore1((char **)v, fwdignore, "ignored");
791 }
792 
793 int
unfwdretain(void * v)794 unfwdretain(void *v)
795 {
796 	return unignore1((char **)v, fwdignore + 1, "retained");
797 }
798 
799 static void
unignore_one(const char * name,struct ignoretab * tab)800 unignore_one(const char *name, struct ignoretab *tab)
801 {
802 	struct ignore *ip, *iq = NULL;
803 	int h = hash(name);
804 
805 	for (ip = tab->i_head[h]; ip; ip = ip->i_link) {
806 		if (asccasecmp(ip->i_field, name) == 0) {
807 			free(ip->i_field);
808 			if (iq != NULL)
809 				iq->i_link = ip->i_link;
810 			else
811 				tab->i_head[h] = ip->i_link;
812 			free(ip);
813 			tab->i_count--;
814 			break;
815 		}
816 		iq = ip;
817 	}
818 }
819 
820 static int
unignore1(char ** list,struct ignoretab * tab,char * which)821 unignore1(char **list, struct ignoretab *tab, char *which)
822 {
823 	if (tab->i_count == 0) {
824 		printf(catgets(catd, CATSET, 34,
825 				"No fields currently being %s.\n"), which);
826 		return 0;
827 	}
828 	while (*list)
829 		unignore_one(*list++, tab);
830 	return 0;
831 }
832