[comp.sources.misc] v18i068: mush - Mail User's Shell, Part11/22

argv@zipcode.com (Dan Heller) (04/22/91)

Submitted-by: Dan Heller <argv@zipcode.com>
Posting-number: Volume 18, Issue 68
Archive-name: mush/part11
Supersedes: mush: Volume 12, Issue 28-47

#!/bin/sh
# do not concatenate these parts, unpack them in order with /bin/sh
# file loop.c continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 11; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping loop.c'
else
echo 'x - continuing file loop.c'
sed 's/^X//' << 'SHAR_EOF' >> 'loop.c' &&
X    /* If final is true, do variable expansions first */
X    if (final) {
X	(void) strcpy(buf, str);
X	str = buf;
X	if (!variable_expand(str))
X	    return DUBL_NULL;
X    }
X    *argc = 0;
X    while (*str && *argc < MAXARGS) {
X	while (isspace(*str))
X	    ++str;
X	/* When we have hit an unquoted `;', final must be true,
X	 * so we're finished.  Stuff the rest of the string at the
X	 * end of the argv -- do_command will pass it back later,
X	 * for further processing -- and break out of the loop.
X	 * NOTE: *s is not yet valid the first time through this
X	 * loop, so unq_sep should always be initialized to 0.
X	 */
X	if (unq_sep && s && *s == ';') {
X	    if (*str) { /* Don't bother saving a null string */
X		newargv[*argc] = savestr(str);
X		(*argc)++;
X	    }
X	    break;
X	}
X	if (*str) {		/* found beginning of a word */
X	    unq_sep = 0;	/* innocent until proven guilty */
X	    s = p = str;
X	    do  {
X		if (p - s >= sizeof buf-1) {
X		    print("argument list too long.\n");
X		    return DUBL_NULL;
X		}
X		if (*str == ';' || *str == '|')
X		    unq_sep = final; /* Mark an unquoted separator */
X		if ((*p = *str++) == '\\') {
X		    if (final && (*str == ';' || *str == '|'))
X			--p; /* Back up to overwrite the backslash */
X		    if (*++p = *str) /* assign and compare to NUL */
X			str++;
X		    continue;
X		}
X		if (p2 = index("\"'", *p)) {
X		    register char c2 = *p2;
X		    /* you can't escape quotes inside quotes of the same type */
X		    if (!(p2 = index(str, c2))) {
X			if (final)
X			    print("Unmatched %c.\n", c2);
X			err++;
X			p2 = str;
X		    }
X		    /* This is the intent of the following loop:
X		     *  tmp = (int)(p2 - str) + 1;
X		     *  (void) strncpy(p + !final, str, tmp);
X		     * The strncpy() can't be used directly because
X		     * it may be overlapping, which fails sometimes.
X		     */
X		    /* copy up to and including quote */
X		    for (tmp = 0; tmp < (int)(p2 - str) + 1; tmp++)
X			p[tmp+!final] = str[tmp];
X		    p += tmp - 2 * !!final; /* change final to a boolean */
X		    if (*(str = p2))
X			str++;
X		}
X	    } while (++p, *str && (!isdelimeter(*str) || str[-1] == '\\'));
X	    if (c = *str) /* set c = *str, check for null */
X		str++;
X	    *p = 0;
X	    if (*s) {
X		/* To differentiate real separators from quoted or
X		 * escaped ones, always store 3 chars:
X		 *  1) The separator character
X		 *  2) A nul (string terminator)
X		 *  3) An additional boolean (0 or 1)
X		 * The boolean is checked by do_command.  Note that this
X		 * applies only to "solitary" separators, i.e. those not
X		 * part of a larger word.
X		 */
X		if (final && (!strcmp(s, ";") || !strcmp(s, "|"))) {
X		    char *sep = savestr("xx"); /* get 3 char slots */
X		    sep[0] = *s, sep[1] = '\0', sep[2] = unq_sep;
X		    newargv[*argc] = sep;
X		} else
X		    newargv[*argc] = savestr(s);
X		(*argc)++;
X	    }
X	    *p = c;
X	}
X    }
X    if (!*argc)
X	return DUBL_NULL;
X    /* newargv[*argc] = NULL; */
X    if (!(argv = (char **)calloc((unsigned)((*argc)+1), sizeof(char *)))) {
X	perror("mk_argv: calloc");
X	return DUBL_NULL;
X    }
X    for (tmp = 0; tmp < *argc; tmp++)
X	argv[tmp] = newargv[tmp];
X    if (err)
X	*argc = -1;
X    else if (debug > 3)
X	(void) printf("Made argv: "), print_argv(argv);
X    return argv;
}
X
/*
X * Report a history parsing error.
X * Suppress the message if nonobang is true.
X */
#define hist_error	if (nonobang) {;} else print
X
/*
X * reference previous history from syntax of str and place result into buf
X * We know we've got a history reference -- we're passed the string starting
X * the first char AFTER the '!' (which indicates history reference)
X */
char *
reference_hist(str, buf, hist_ref)
register char *str, **hist_ref;
char buf[];
{
X    int 	   relative; /* relative from current hist_no */
X    int 	   old_hist, argstart = 0, lastarg, argend = 0, n = 0;
X    register char  *p, *rb = NULL, **argv = hist_ref;
X    struct history *hist;
X
X    buf[0] = 0;
X    if (*str == '{')
X	if (!(rb = index(str, '}'))) {   /* { */
X	    hist_error("Unmatched '}'");
X	    return NULL;
X	} else
X	    *rb = 0, ++str;
X    relative = *str == '-';
X    if (index("!:$*", *str)) {
X	old_hist = hist_no;
X	if (*str == '!')
X	    str++;
X    } else if (isdigit(*(str + relative)))
X	str = my_atoi(str + relative, &old_hist);
X    else if (!(p = hist_from_str(str, &old_hist))) {
X	if (rb) /* { */
X	    *rb = '}';
X	return NULL;
X    } else
X	str = p;
X    if (relative)
X	old_hist = (hist_no - old_hist) + 1;
X    if (old_hist == hist_no) {
X	if (!(argv = hist_ref))
X	    hist_error("You haven't done anything yet!\n");
X    } else {
X	if (old_hist <= hist_no-hist_size || old_hist > hist_no ||
X	    old_hist <= 0) {
X	    if (old_hist <= 0)
X		hist_error("You haven't done that many commands, yet.\n");
X	    else
X		hist_error("Event %d %s.\n", old_hist,
X		    (old_hist > hist_no)? "hasn't happened yet": "expired");
X	    if (rb) /* { */
X		*rb = '}';
X	    return NULL;
X	}
X	hist = hist_head;
X	while (hist && hist->histno != old_hist)
X	    hist = hist->prev;
X	if (hist)
X	    argv = hist->argv;
X    }
X    if (!argv) {
X	if (rb) /* { */
X	    *rb = '}';
X	return NULL;
X    }
X    while (argv[argend+1])
X	argend++;
X    lastarg = argend;
X    if (*str && index(":$*-", *str)) {
X	int isrange;
X	if (*str == ':' && isdigit(*++str))
X	    str = my_atoi(str, &argstart);
X	if (isrange = (*str == '-'))
X	    str++;
X	if (!isdigit(*str)) {
X	    if (*str == 'p')
X		str++, print_only = 1;
X	    else if (*str == '*') {
X		str++;
X		if (!isrange) {
X		    if (argv[0]) {
X			if (argv[1])
X			    argstart = 1;
X			else {
X			    if (rb) /* { */
X				*rb = '}';
X			    return (rb ? rb + 1 : str);
X			}
X		    } else
X			argstart = 0;
X		}
X	    } else if (*str == '$') {
X		if (!isrange)
X		    argstart = argend;
X		str++;
X	    } else if (isrange && argend > argstart)
X		argend--; /* unspecified end of range implies last-1 arg */
X	    else 
X		argend = argstart; /* no range specified; use arg given */
X	} else
X	    str = my_atoi(str, &argend);
X    }
X    if (argstart > lastarg || argend > lastarg || argstart > argend) {
X	hist_error("Bad argument selector.\n");
X	if (rb) /* { */
X	    *rb = '}';
X	return (nonobang ? rb ? rb + 1 : str : NULL);
X    }
X    if (debug > 3)
X	print("history expanding from "), print_argv(argv);
X    while (argstart <= argend) {
X	n += Strcpy(&buf[n], argv[argstart++]);
X	buf[n++] = ' ';
X    }
X    buf[--n] = 0;
X    if (rb) /* { */
X	*rb = '}';
X    return (rb ? rb + 1 : str);
}
X
/* find a history command that contains the string "str"
X * place that history number in "hist" and return the end of the string
X * parsed: !?foo (find command with "foo" in it) !?foo?bar (same, but add "bar")
X * in the second example, return the pointer to "bar"
X */
char *
hist_from_str(str, hist_number)
register char *str;
register int *hist_number;
{
X    register char *p = NULL, c = 0;
X    int 	  full_search = 0, len, found;
X    char 	  buf[BUFSIZ];
X    struct history *hist;
#ifndef REGCMP
X    extern char   *re_comp();
#else
X    char *rex = NULL;
X    extern char   *regcmp();
#endif /* REGCMP */
X
X    /* For !{something}, the {} are stripped in reference_hist() */
X    if (*str == '?') {
X	if (p = index(++str, '?'))
X	    c = *p, *p = 0;
X	else
X	    p = str + strlen(str);
X	full_search = 1;
X    } else {
X	p = str;
X	while (*p && *p != ':' && !isspace(*p))
X	    p++;
X	c = *p, *p = 0;
X    }
X    if (*str) {
#ifndef REGCMP
X	if (re_comp(str))
#else
X	if (!(rex = regcmp(str, NULL)))	/* Assign and test */
#endif /* REGCMP */
X	{
X	    if (c)
X		*p = c;
X	    return NULL;
X	}
X    } else {
X	*hist_number = hist_no;
X	if (c)
X	    *p = c;
X	return (*p == '?' ? p + 1 : p);
X    }
X    len = strlen(str);
X    /* move thru the history in reverse searching for a string match. */
X    for (hist = hist_head; hist; hist = hist->prev) {
X	if (full_search) {
X	    (void) argv_to_string(buf, hist->argv);
X	    Debug("Checking for (%s) in (#%d: %s)\n", str, hist->histno, buf);
X	}
X	if (!full_search) {
X	    (void) strcpy(buf, hist->argv[0]);
X	    Debug("Checking for (%s) in (#%d: %*s)\n",
X		str, hist->histno, len, buf);
X	    found = !strncmp(buf, str, len);
X	} else
X	    found =
#ifndef REGCMP
X		re_exec(buf)
#else
X		!!regex(rex, buf, NULL) /* convert to boolean value */
#endif /* REGCMP */
X				== 1;
X	if (found) {
X	    *hist_number = hist->histno;
X	    Debug("Found it in history #%d\n", *hist_number);
X	    *p = c;
X	    return (*p == '?' ? p + 1 : p);
X	}
X    }
X    hist_error("%s: event not found\n", str);
X    *p = c;
X    return NULL;
}
X
disp_hist(n, argv)  /* argc not used -- use space for the variable, "n" */
register int n;
char **argv;
{
X    register int	list_num = TRUE, num_of_hists = hist_size;
X    register int	reverse = FALSE;
X    struct history	*hist = hist_tail;
X
X    while (*++argv && *argv[0] == '-') {
X	n = 1;
X	do  switch(argv[0][n]) {
X		case 'h': list_num = FALSE;
X		when 'r': reverse = TRUE; hist = hist_head;
X		otherwise: return help(0, "history", cmd_help);
X	    }
X	while (argv[0][++n]);
X    }
X
X    if (!hist) {
X	print("No history yet.\n");
X	return -1;
X    }
X    if (*argv)
X	if (!isdigit(**argv)) {
X	    print("history: badly formed number\n");
X	    return -1;
X	} else
X	    num_of_hists = atoi(*argv);
X
X    if (num_of_hists > hist_size || num_of_hists > hist_no)
X	num_of_hists = min(hist_size, hist_no);
X
X    if (!reverse)
X	while (hist_no - hist->histno >= num_of_hists) {
X	    Debug("skipping %d\n", hist->histno);
X	    hist = hist->next;
X	}
X
X    (void) do_pager(NULL, TRUE);
X    for (n = 0; n < num_of_hists && hist; n++) {
X	char buf[256];
X	if (list_num)
X	    (void) do_pager(sprintf(buf, "%4.d  ", hist->histno), FALSE);
X	(void) argv_to_string(buf, hist->argv);
X	(void) do_pager(buf, FALSE);
X	if (do_pager("\n", FALSE) == -1)
X	    break;
X	hist = (reverse)? hist->prev : hist->next;
X    }
X    (void) do_pager(NULL, FALSE);
X    return 0;
}
X
init_history(newsize)
{
X    if ((hist_size = newsize) < 1)
X	hist_size = 1;
}
SHAR_EOF
echo 'File loop.c is complete' &&
chmod 0644 loop.c ||
echo 'restore of loop.c failed'
Wc_c="`wc -c < 'loop.c'`"
test 34934 -eq "$Wc_c" ||
	echo 'loop.c: original size 34934, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= macros.c ==============
if test -f 'macros.c' -a X"$1" != X"-c"; then
	echo 'x - skipping macros.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting macros.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'macros.c' &&
/* (@)# macros.c	(c) copyright 9/19/88 (Bart Schaefer, Dan Heller) */
X
#include "bindings.h"
#include "mush.h"
X
extern struct cmd_map map_func_names[];
X
struct cmd_map	*mac_stack, *mac_hide;
X
/*
X * print current binding to macro mappings if "str" is NULL.
X * else return the string "x_str" which the str is bound to.
X */
char *
c_macro(name, str, opts)
char *name;
register char *str;
register struct cmd_map *opts;
{
X    register int    incurses = iscurses;
X    char buf[MAX_MACRO_LEN], buf2[sizeof buf * 3];
X
X    if (!str) {
X	for (; opts; opts = opts->m_next)
X	    if (opts->m_cmd == C_MACRO)
X		break;
X	if (!opts) {
X	    print("No %s settings.\n", name);
X	    return (char *)(-1);
X	}
X	if (incurses)
X	    clr_bot_line(), iscurses = FALSE;
X	(void) do_pager(NULL, TRUE);
X	(void) do_pager(sprintf(buf, "\nCurrent %s settings:\n\n",name), FALSE);
X    }
X
X    if (!opts)
X	return NULL;
X
X    for (; opts; opts = opts->m_next) {
X	if (opts->m_cmd != C_MACRO)
X	    continue;
X	if (!str) {
X	    (void) do_pager(sprintf(buf, "%-20.20s  ",
X		ctrl_strcpy(buf2, opts->m_str, FALSE)), FALSE);
X	    if (do_pager(sprintf(buf, "%s\n",
X		ctrl_strcpy(buf2, opts->x_str, TRUE)), FALSE) == EOF)
X		break;
X	} else {
X	    if (strcmp(str, opts->m_str))
X		continue;
X	    else
X		return opts->x_str;
X	}
X    }
X    iscurses = incurses;
X    if (str)
X	(void) do_pager(NULL, FALSE);
X    return NULL;
}
X
mac_push(str)
register char *str;
{
X    register struct cmd_map *tmp;
X
X    /* now make a new macro struct and set fields */
X    if (!(tmp = (struct cmd_map *)calloc((unsigned)1,sizeof(struct cmd_map)))) {
X	error("calloc");
X	return -1;
X    }
X    tmp->m_next = mac_stack;
X    mac_stack = tmp;
X    tmp->x_str = savestr(str);	/* x_str is the text of the expansion */
X    tmp->m_str = tmp->x_str;	/* m_str is the current read position */
X
X    /*
X     * Save the current state of the glob_flags so
X     * mac_push() can also serve as unget of stdin
X     */
X    tmp->m_cmd = glob_flags;
X    return 0;
}
X
mac_queue(str)
register char *str;
{
X    register struct cmd_map **tmp; /* NOTE pointer to pointer! */
X
X    /* Find the bottom of the macro stack */
X    for (tmp = &mac_stack; *tmp; tmp = &((*tmp)->m_next))
X	;
X    /* now make a new macro struct and set fields */
X    if (!(*tmp =
X	    (struct cmd_map *)calloc((unsigned)1,sizeof(struct cmd_map)))) {
X	error("calloc");
X	return -1;
X    }
X    (*tmp)->m_next = (struct cmd_map *)0; /* calloc should do this .... */
X    (*tmp)->x_str = savestr(str); /* x_str is the text of the expansion */
X    (*tmp)->m_str = (*tmp)->x_str; /* m_str is the current read position */
X
X    /*
X     * Save the current state of the glob_flags
X     */
X    (*tmp)->m_cmd = glob_flags;
X    return 0;
}
X
void
mac_pop()
{
X    register struct cmd_map *tmp;
X
X    if (mac_stack) {
X	tmp = mac_stack;
X	mac_stack = tmp->m_next;
X	xfree(tmp->x_str);
X	xfree((char *) tmp);
X    }
X    /*
X     * Restore saved MACRO glob_flags only (see mac_push())
X     */
X    if (mac_stack) {
X	if (ison(mac_stack->m_cmd, IN_MACRO))
X	    turnon(glob_flags, IN_MACRO);
X	else
X	    turnoff(glob_flags, IN_MACRO);
X	if (ison(mac_stack->m_cmd, LINE_MACRO))
X	    turnon(glob_flags, LINE_MACRO);
X	else
X	    turnoff(glob_flags, LINE_MACRO);
X	if (ison(mac_stack->m_cmd, QUOTE_MACRO))
X	    turnon(glob_flags, QUOTE_MACRO);
X	else
X	    turnoff(glob_flags, QUOTE_MACRO);
X    }
}
X
/* Abandon macro processing */
void
mac_flush()
{
X    while (mac_stack)
X	mac_pop();
X    if (mac_hide) {
X	mac_stack = mac_hide;
X	mac_hide = NULL_MAP;
X	while (mac_stack)
X	    mac_pop();
X    }
X    turnoff(glob_flags, IN_MACRO);
X    turnoff(glob_flags, LINE_MACRO);
X    turnoff(glob_flags, QUOTE_MACRO);
}
X
/* Check for pending input from a macro. */
mac_pending()
{
X    register struct cmd_map *msp;
X
X    for (msp = mac_stack; msp && !*(msp->m_str); msp = msp->m_next)
X	;
X
X    return !!msp;
}
X
/* Get input and treat it as a macro.  */
get_mac_input(newline)
int newline;	/* 1 if newline to be appended, 0 otherwise */
{
X    register int len;
X    char buf[MAX_MACRO_LEN];
X
X    /* This call cannot be nested */
X    if (mac_hide)
X	return -1;
X
X    /* Hide the mac_stack so input comes from stdin */
X    mac_hide = mac_stack; mac_stack = NULL_MAP;
X
X    if ((len = Getstr(buf, MAX_MACRO_LEN - 1, 0)) < 0)
X	return len;
X    if (newline) {
X	buf[len++] = '\n';
X	buf[len] = 0;
X    }
X
X    /* Restore the mac_stack */
X    if (mac_stack) {
X	/*
X	 * Somehow, a push happened even though mac_hide was
X	 * nonzero -- maybe by line wrap?  Fix it as best we can.
X	 */
X	struct cmd_map *msp;
X	for (msp = mac_stack; msp->m_next; msp = msp->m_next)
X	    ;
X	msp->m_next = mac_hide;
X    } else
X	mac_stack = mac_hide;
X    mac_hide = NULL_MAP;
X
X    /* Restore saved flags */
X    if (mac_stack) {
X	if (ison(mac_stack->m_cmd, IN_MACRO))
X	    turnon(glob_flags, IN_MACRO);
X	else
X	    turnoff(glob_flags, IN_MACRO);
X	if (ison(mac_stack->m_cmd, LINE_MACRO))
X	    turnon(glob_flags, LINE_MACRO);
X	else
X	    turnoff(glob_flags, LINE_MACRO);
X    }
X    if (len > 0)
X	Ungetstr(buf);
X
X    return 1;
}
X
/* getchar() substitute -- reads from the current macro if one is active,
X * otherwise does a getchar().
X *
X * NOTE:  In the mac_stack, x_str is the saved text of the current macro,
X *  and m_str is the current read position within the macro.
X */
m_getchar()
{
X    int c;
X
X    while (mac_stack && (! *(mac_stack->m_str)))
X	mac_pop();
X    if (mac_stack) {
X	c = *((mac_stack->m_str)++);
X	return c;
X    } else {
X	turnoff(glob_flags, IN_MACRO);
X	turnoff(glob_flags, LINE_MACRO);
X	turnoff(glob_flags, QUOTE_MACRO);
X	while ((c = getchar()) == 0)	/* Ignore NUL chars from stdin */
X	    ;				/* until better solution found */
X	return c;
X    }
}
X
m_ungetc(c)
char c;
{
X    if (mac_stack && (mac_stack->m_str > mac_stack->x_str))
X	*(--(mac_stack->m_str)) = c;
X    else
X	(void) ungetc(c, stdin);
}
X
/*
X * Try to read a long command; assumes MAC_LONG_CMD already seen.
X *  On immediate failure, return 0.
X *  On failure after reading some input, return less than zero.
X *  On success, return greater than 0.
X * The absolute value of the return is the number of chars placed in buf.
X */
read_long_cmd (buf)
char *buf;
{
X    register char c, *p = buf;
X    register int count = 0;
X
X    /*
X     * Test in_macro() in this loop because the _entire_
X     * long command _must_ be in the macro -- if we run
X     * out of macro in mid-long-command, it is an error.
X     */
X    while (in_macro() && (count < MAX_LONG_CMD - 1)
X	    && ((c = m_getchar()) != MAC_LONG_END)) {
X	*p++ = c; ++count;
X    }
X    *p = '\0';
X    if (c != MAC_LONG_END)
X	return (-count);
X    return count;
}
X
/*
X * Identify and possibly execute a reserved long macro command
X * Executes if do_exec is true.  Otherwise, just parse.
X */
reserved_cmd (buf, do_exec)
char *buf;
{
X    int ret = 1;
X
X    if (!strcmp(buf, MAC_GET_STR)) {
X	if (do_exec)
X	    ret = get_mac_input(0);
X    } else if (!strcmp(buf, MAC_GET_LINE)) {
X	if (do_exec)
X	    ret = get_mac_input(1);
X    } else
X	ret = 0;
X    return ret;
}
X
#ifdef CURSES
X
/*
X * Identify (and possibly execute, if reserved) curses mode commands
X *  that appear in macro strings enclosed by MAC_LONG_CMD and
X *  MAC_LONG_END.  Return the binding of the command.
X */
long_mac_cmd (c, do_exec)
int c;
{
X    char buf[MAX_LONG_CMD];
X    register int count, binding;
X    int y, x;
X
X    if (c != MAC_LONG_CMD)
X	return C_ERROR;
X
X    if ((count = read_long_cmd(buf)) <= 0) {
X	print("Invalid long macro command");
X	if (ison(glob_flags, CNTD_CMD))
X	    putchar('\n');
X	if (do_exec)
X	    mac_flush();
X	return C_ERROR;
X    }
X
X    if (do_exec) {
X	if (ison(glob_flags, CNTD_CMD))
X	    clr_bot_line();
X	getyx(stdscr, y, x);
X	move(LINES - 1, 0);
X    }
X    if (reserved_cmd(buf, do_exec)) {
X	if (do_exec) {
X	    if (isoff(glob_flags, CNTD_CMD))
X		move(y, x);
X	    return getcmd();
X	} else
X	    return C_NULL;
X    } else if (do_exec)
X	move(y, x);
X
X    /* Can start at C_NULL because of "no-op" command */
X    for (count = 0; count <= C_HELP; count++) {
X	if (!strcmp(buf, map_func_names[count].m_str)) {
X	    binding = (int)map_func_names[count].m_cmd;
X	    break;
X	}
X    }
X    /* Don't allow C_MACRO to be called directly */
X    if (count > C_HELP || binding == C_MACRO) {
X	print("Invalid long macro command");
X	if (ison(glob_flags, CNTD_CMD))
X	    putchar('\n');
X	return C_ERROR;
X    } else
X	return binding;
}
X
#endif /* CURSES */
X
/*
X * Check the validity of a macro binding as far as possible
X */
check_mac_bindings(buf)
char *buf;
{
X    int ok = TRUE;
X
X    while (ok && buf && *buf) {
X	if (*buf == MAC_LONG_CMD) {
X	    char *i;
#ifdef CURSES
X	    int count;
#endif /* CURSES */
X
X	    if (ok)
X		ok = ((i = index(++buf, MAC_LONG_END)) != NULL);
X	    if (i)
X	        *i = '\0';      /* Don't worry, we'll fix it */
X	    else
X	        return ok;
#ifdef CURSES
X	    /* OK to start at C_NULL because of "no-op" command */
X	    for (count = 0; count <= C_HELP; count++)
X	        if (! strcmp(buf, map_func_names[count].m_str))
X	            break;
X	    /* Don't allow C_MACRO to be called directly */
X	    if (count == C_MACRO)
X	        ok = FALSE;
X	    else if (count > C_HELP)
#endif /* CURSES */
X	    if (ok && !(ok = reserved_cmd(buf, FALSE)))
X		wprint("Warning: unrecognized curses command: \"%s\"\n", buf);
X	    buf = i;
X	    *buf++ = MAC_LONG_END;
X	} else if (*buf++ == '\\' && *buf)
X	    ++buf;
X    }
X    return ok;
}
SHAR_EOF
chmod 0644 macros.c ||
echo 'restore of macros.c failed'
Wc_c="`wc -c < 'macros.c'`"
test 9279 -eq "$Wc_c" ||
	echo 'macros.c: original size 9279, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= mail.c ==============
if test -f 'mail.c' -a X"$1" != X"-c"; then
	echo 'x - skipping mail.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting mail.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'mail.c' &&
/* @(#)mail.c 	(c) copyright 1986 (Dan Heller) */
X
#include "mush.h"
X
/*
X * mail.c --
X *    do_mail() 	external interface to these functions; parses options.
X *    mail_someone()    called from do_mail() to begin composing the message.
X *    start_file()      creates the editing file and reset signal catching.
X *    add_to_letter()	adds the next line to letter --determine ~ escapes.
X *    finish_up_letter()  prompts for Cc:, verifies send, adds signatures.
X *    send_it() 	expands aliases, invokes mailer, sends to record file.
X *    add_headers()	adds all headers to a FILE *, reads draft files
X *    rm_edfile()	signals are directed here. remove letter, longjmp
X *    dead_letter()     make a dead.letter if mail failed.
X */
X
static char Subject[BUFSIZ],To[HDRSIZ],Cc[HDRSIZ],Bcc[HDRSIZ],in_reply_to[256];
static int killme;
static u_long flags;
static SIGRET (*oldterm)(), (*oldint)(), (*oldquit)();
static int finish_up_letter(), send_it(), start_file();
static long add_headers();
static jmp_buf cntrl_c_buf;
static char *Hfile, *edfile;
FILE *ed_fp;
char *hfile, *mktemp();
X
/* argc and argv could be null if coming from tool mode compose */
do_mail(n, argv, list)
register int n;   /* no need for "argc", so use the space for a variable */
register char **argv, *list;
{
X    char firstchar = (argv)? **argv: 'm';
X    char *to = NULL, *cc = NULL, *addcc = NULL, *bcc = NULL, *subj = NULL;
X    char *route = NULL;
X    char inc_list[MAXMSGS_BITS], buf[HDRSIZ];
X    u_long flgs = 0;
X
X    if (ison(glob_flags, IS_GETTING)) {
X	wprint("You must finish the letter you are editing first.\n");
X	return -1;
X    }
X    if (ison(glob_flags, DO_PIPE)) {
X	wprint("You can't pipe out of the %s command.\n", argv? *argv : "mail");
X	return -1;
X    }
X    clear_msg_list(inc_list);
X    hfile = Hfile = NULL;
X
X    /* If piped to mail, include the messages piped */
X    if (ison(glob_flags, IS_PIPE) ||
X	(lower(firstchar) == 'r' && do_set(set_options, "autoinclude"))) {
X	turnon(flgs, INCLUDE);
X	bitput(list, inc_list, msg_cnt, =);
X    }
X    /* Set SIGN and DO_FORTUNE now so we can turn them off later */
X    if (do_set(set_options, "autosign"))
X	turnon(flgs, SIGN);
X    if (do_set(set_options, "fortune"))
X	turnon(flgs, DO_FORTUNE);
X    while (argv && *argv && *++argv && **argv == '-') {
X	n = 1;
X	while (n && argv[0][n])
X	    switch (argv[0][n]) {
#ifdef VERBOSE_ARG
X		case 'v': turnon(flgs, VERBOSE); n++; break;
#endif /* VERBOSE_ARG */
X		case 'H':
X		    if (argv[1]) {
X			n = 0;
X			Hfile = *++argv;
X		    } else {
X			wprint("Must specify a file\n");
X			return -1;
X		    }
X		when 'h':
X		    if (argv[1]) {
X			n = -1; /* it gets incremented below */
X			hfile = savestr(*++argv);
X			if (ison(glob_flags, REDIRECT)) {
X			    turnoff(glob_flags, REDIRECT);
X			    turnon(flgs, SEND_NOW);
X			}
X		    } else {
X			wprint("Must specify a file containing headers\n");
X			return -1;
X		    }
X		    /* Fall through */
X		case 'E': turnon(flgs, EDIT_HDRS); n++;
X		when 'e': turnon(flgs, EDIT); n++;
X		when 'F': turnon(flgs, DO_FORTUNE); n++;
X		when 'b':
X		    if (argv[1]) {
X			n = 0, bcc = *++argv;
X			fix_up_addr(bcc);
X		    } else {
X			wprint("Must specify blind-carbon list\n");
X			return -1;
X		    }
X		when 'c':
X		    if (argv[1]) {
X			n = 0, addcc = *++argv;
X			fix_up_addr(addcc);
X		    } else {
X			wprint("Must specify carbon-copy list\n");
X			return -1;
X		    }
X		when 's':
X		    if (argv[1])
X			n = 0, subj = *++argv;
X		    else
X			n++, turnon(flgs, NEW_SUBJECT);
X		when 'i': case 'I': case 'f': {
X		    int m;
X		    if (!msg_cnt) {
X			wprint("No message to include!\n");
X			return -1;
X		    }
X		    if (argv[0][n] == 'i') {
X			turnon(flgs, INCLUDE);
X			turnoff(flgs, INCLUDE_H);
X			turnoff(flgs, FORWARD);
X		    } else if (argv[0][n] == 'I') {
X			turnon(flgs, INCLUDE_H);
X			turnoff(flgs, INCLUDE);
X			turnoff(flgs, FORWARD);
X		    } else if (argv[0][n] == 'f') {
X			turnon(flgs, FORWARD);
X			turnon(flgs, SEND_NOW);
X			turnoff(flgs, INCLUDE_H);
X			turnoff(flgs, INCLUDE);
X		    }
X		    /* "-i 3-5" or "-i3-5"  Consider the latter case first */
X		    if (!argv[0][++n])
X			argv++, n = 0;
X		    (*argv) += n;
X		    m = get_msg_list(argv, inc_list);
X		    (*argv) -= n;
X		    if (m == -1)
X			return -1;
X		    /* if there were args, then go back to the first char
X		     * in the next argv
X		     */
X		    if (m)
X			n = 0;
X		    if (!n) /* n may be 0 from above! */
X			argv += (m-1);
X		}
X		when 'U':
X		    turnon(flgs, SEND_NOW);
X		    n++;
X		when 'u':
X		    turnoff(flgs, SIGN);
X		    turnoff(flgs, DO_FORTUNE);
X		    n++;
X		when 'r':
X		    if (lower(firstchar) == 'r') {
X			route = *++argv;
X			n = 0;
X			break;
X		    }
X		    /* fall thru */
X		default:
X		    if (argv[0][n] != '?') {
X			wprint("%c: unknown option\n\n", argv[0][n]);
X			return -1;
X		    } else
X			return help(0, "mail", cmd_help);
X	    }
X    }
X    if (isoff(flgs, FORWARD)) {
X	if (ison(flgs, SEND_NOW)) {
X	    if (!hfile && !Hfile) {
X		wprint("Can't send immediately without draft file.\n");
X		return -1;
X	    }
X	    turnoff(flgs, EDIT); /* -U overrides -e */
X	} else if (do_set(set_options, "autoedit"))
X	    turnon(flgs, EDIT);
X    } else if (ison(flgs, EDIT)) /* -e modifies -f */
X	turnoff(flgs, SEND_NOW);
#ifdef VERBOSE_ARG
X    if (do_set(set_options, "verbose"))
X	turnon(flgs, VERBOSE);
#endif /* VERBOSE_ARG */
X    *in_reply_to = *To = *Subject = *Cc = *Bcc = 0;
X    if (lower(firstchar) == 'r') {
X	char *in_reply_fmt, *pcc = NULL;
X	to = To, cc = Cc;
X	/*
X	 * Generate a reply to all the messages passed to respond().  This
X	 * list is different than the include-msg list above.  Get info about
X	 * whom the messages were sent to for reply-all.
X	 * BUG: currently, redundant addresses aren't pruned from Bcc list!
X	 */
X	for (n = 0; n < msg_cnt; n++)
X	    if (msg_bit(list, n)) {
X		if (to != To)
X		    *to++ = ',', *to++ = ' ';
X		(void) reply_to(n, (firstchar == 'R'), buf);
X		if (strlen(buf) + (to - To) > sizeof(To) - 1) {
X		    wprint("# recipients exceeded at msg %d\n", n);
X		    break;
X		}
X		to += Strcpy(to, buf);
X		if (firstchar == 'R') {
X		    if (pcc = cc_to(n, buf)) {
X			/* if there was a previous cc, append ", " */
X			if (cc != Cc)
X			    *cc++ = ',', *cc++ = ' ';
X			if (strlen(pcc) + (cc - Cc) > sizeof(Cc) - 1)
X			    wprint("# Cc's exceeded at msg %d\n", n);
X			else
X			    cc += Strcpy(cc, pcc);
X		    }
X		}
X		/* remove redundant addresses now, or headers could get too
X		 * long before the list runs out (it still might)
X		 */
X		rm_redundant_addrs(To, Cc);
X		to = To + strlen(To);
X		cc = Cc + strlen(Cc);
X	    }
X	/* clean up end of Cc line for replyall's */
X	while (*cc == ' ' || *cc == ',')
X	    *cc-- = '\0';
X	if (firstchar == 'R' && !do_set(set_options, "metoo")) {
X	    /* Each reply_to() call above will leave at least
X	     * one person in To.  If that one person was us,
X	     * we need to get removed from the complete list.
X	     */
X	    (void) take_me_off(to);
X	}
X	to = To, cc = Cc;
X	if (route || (route = do_set(set_options, "auto_route")))
X	    /* careful! This routine could add lots-o-bytes and lose addresses
X	     * to avoid writing out of segment.
X	     */
X	    route_addresses(To, Cc, route);
X	if (in_reply_fmt = do_set(set_options, "in_reply_to"))
X	    /* "9" here is a magic # --see compose_hdr() */
X	    (void) strcpy(in_reply_to,
X			    format_hdr(current_msg, in_reply_fmt, FALSE)+9);
X    }
X    if (ison(flgs, FORWARD) && ison(flgs, EDIT) ||
X	    lower(firstchar) == 'r' && isoff(flgs, NEW_SUBJECT)) {
X	turnoff(flgs, NEW_SUBJECT);
X	if (subj && *subj && (isoff(flgs, FORWARD) || ison(flgs, EDIT)))
X	    subj = strcpy(Subject, subj);
X	else if (subj = subject_to(current_msg, buf))
X	    subj = strcpy(Subject, buf + 4*(lower(firstchar) != 'r'));
X    } else if (isoff(flgs, NEW_SUBJECT) && isoff(flgs, FORWARD) &&
X	(do_set(set_options, "ask") || do_set(set_options, "asksub")))
X	turnon(flgs, NEW_SUBJECT);
X    if (argv && *argv) {
X	char buf[HDRSIZ];
X	(void) argv_to_string(buf, argv);
X	fix_up_addr(buf);
X	to = &To[strlen(To)];
X	if (*To)
X	    *to++ = ',', *to++ = ' ';
X	(void) strcpy(to, buf);
X	to = To;
X    }
X    if (addcc && *addcc) {
X	cc = &Cc[strlen(Cc)];
X	if (*Cc)
X	    *cc++ = ',', *cc++ = ' ';
X	(void) strcpy(cc, addcc); /* addcc has already been fixed up */
X	cc = Cc;
X    }
X    /* remove any redundant addresses that just got added */
X    rm_redundant_addrs(To, Cc);
X    if (bcc && *bcc)
X	(void) strncpy(Bcc, bcc, sizeof(Bcc)); /* bcc already fixed up */
X    bcc = Bcc;
X
X    return mail_someone(to, subj, cc, bcc, flgs, inc_list);
}
X
static
mail_someone(to, subject, cc, bcc, flgs, list)
register char *to, *subject, *cc, *bcc, *list;
u_long flgs;
{
X    register char *p;
X
X    flags = flgs;
X    if (to && *to) {
X	if (!*To)
X	    (void) strncpy(To, to, sizeof(To));
X    } else
X	to = NO_STRING;
X    if (subject && *subject) {
X	if (!*Subject)
X	    (void) strncpy(Subject, subject, sizeof(Subject));
X    } else
X	subject = NO_STRING;
X    if (cc && *cc) {
X	if (!*Cc)
X	    (void) strncpy(Cc, cc, sizeof(Cc));
X    } else
X	Cc[0] = '\0';
X    if (bcc && *bcc) {
X	if (!*Bcc)
X	    (void) strncpy(Bcc, bcc, sizeof(Bcc));
X    } else
X	Bcc[0] = '\0';
X
X    if (ison(glob_flags, REDIRECT)) {
X	/*
X	 * NOTE: Could change this to finish_up_letter() to allow
X	 * signatures to be appended.  The -U! option to mush would
X	 * be extended to suppress signing when redirection is on.
X	 */
X	int sent = send_it();
X	if (sent == -1) {
X	    wprint("Message not sent!\n");
X	    rm_edfile(-1);
X	}
X	return sent;
X    }
X    /* if (!*to) then prompting will be done */
X    if (!istool && !hfile) {
X	if (p = set_header("To: ", to, !*to)) {
X	    if (!*to) /* if user typed To-line here, fix up the addresses */
X		fix_up_addr(p);
X	    (void) strcpy(To, p);
X	}
X	if (!*To && ison(flags, FORWARD) && ison(flags, SEND_NOW)) {
X	    turnoff(flags, SEND_NOW); /* user must edit To: line or do again */
X	    print("(You must add a To: address.)\n");
X	}
X	/* don't prompt for subject if forwarding mail */
X	if (isoff(flags, FORWARD) && (*subject || ison(flags, NEW_SUBJECT)) &&
X		(p = set_header("Subject: ", subject,
X			!*subject && ison(flags, NEW_SUBJECT))))
X	    (void) strcpy(Subject, p);
X	if (*Cc || ison(flags, EDIT_HDRS) && do_set(set_options, "askcc")) {
X	    if ((p = set_header("Cc: ", cc, !*Cc)) && *p) {
X		fix_up_addr(p);
X		(void) strcpy(Cc, p);
X	    }
X	}
X	if (*Bcc)
X	    print("Bcc: %s\n", Bcc);
X	putchar('\n');
X    }
X
X    /* If forwarding w/o editing, start a new file for each. */
X    if (ison(flags, FORWARD) && ison(flags, SEND_NOW)) {
X	char fwd[MAXMSGS_BITS];
X	register int i;
X	clear_msg_list(fwd);
X	for (i = 0; i < msg_cnt; i++)
X	    if (msg_bit(list, i)) {
X		set_msg_bit(fwd, i);
X		if (start_file(fwd) < 0)
X		    return -1;
X		turnon(msg[i].m_flags, FORWARD);
X		if (isoff(glob_flags, READ_ONLY))
X		    turnon(glob_flags, DO_UPDATE);
X		clear_msg_list(fwd);
X	    }
X    } else
X	return start_file(list);
X    return 0;
}
X
static
start_file(list)
char *list;
{
X    register char  *dir;
X    register int   i;
X    char  	   line[MAXPATHLEN];
X    int		   had_hfile = FALSE;
X
X    /* getdir() uses the home directory if no tmpdir */
X    if (!(dir = getdir(do_set(set_options, "tmpdir"))))
alted:
X	dir = ALTERNATE_HOME;
X    (void) mktemp(sprintf(line, "%s/%s", dir, EDFILE));
X    strdup(edfile, line);
X    if (!(ed_fp = mask_fopen(edfile, "w+"))) {
X	if (strcmp(dir, ALTERNATE_HOME))
X	    goto alted;
X	error("can't create %s", edfile);
X	return -1;
X    }
X    if (!istool) {
X	oldint = signal(SIGINT, rm_edfile);
X	oldquit = signal(SIGQUIT, rm_edfile);
X	oldterm = signal(SIGTERM, rm_edfile);
X    }
X
X    if (istool && isoff(flags, SEND_NOW) ||
X	    (isoff(flags, FORWARD) || isoff(flags, SEND_NOW)) &&
X	    (ison(flags, EDIT_HDRS) || do_set(set_options, "edit_hdrs"))) {
X	turnon(flags, EDIT_HDRS);
X	if (hfile)
X	    had_hfile = TRUE;
X	if (add_headers(NULL_FILE, &ed_fp, 1, flags) == (long) -1)
X	    return -1;
X    }
X    if (Hfile) {
X	(void) file_to_fp(Hfile, ed_fp, "r");
X	Hfile = NULL;
X	had_hfile = TRUE;
X    }
X    if (istool && isoff(flags, SEND_NOW))
X	strdup(hfile, edfile);
X
X    /* if flags call for it, include current message (with header?) */
X    if (ison(flags, INCLUDE|FORWARD|INCLUDE_H)) {
X	long copy_flgs = 0, is_forw = ison(flags, FORWARD);
X	char buf[sizeof(To)];
X	if (!is_forw) {
X	    turnon(copy_flgs, INDENT);
X	    if (ison(flags, INCLUDE_H) &&
X		    !chk_option("alwaysignore", "include"))
X		turnon(copy_flgs, NO_IGNORE);
X	    else if (ison(flags, INCLUDE))
X		turnon(copy_flgs, NO_HEADER);
X	} else if (ison(flags, SEND_NOW) ||
X		!chk_option("alwaysignore", "forward"))
X	    turnon(copy_flgs, FORWARD);	/* FORWARD implies NO_IGNORE */
#ifdef MSG_SEPARATOR
X	turnon(copy_flgs, NO_SEPARATOR);
#endif /* MSG_SEPARATOR */
X	for (i = 0; i < msg_cnt; i++)
X	    if (msg_bit(list, i)) {
X		if (is_forw && isoff(flags, SEND_NOW)) {
X		    (void) reply_to(i, FALSE, buf);
X		    (void) fprintf(ed_fp,"--- Forwarded mail from %s\n\n",buf);
X		}
X		wprint("%sing message %d ...",
X		    is_forw? "forward" : "includ", i+1);
X		wprint("(%d lines)\n",
X		    copy_msg(i, ed_fp, (u_long) copy_flgs, NULL));
X		set_isread(i); /* if we included it, we read it, right? */
X		if (is_forw && isoff(flags, SEND_NOW))
X		    (void) fprintf(ed_fp,
X			"\n--- End of forwarded message from %s\n", buf);
X		if (!is_forw || isoff(flags, SEND_NOW))
X		    (void) fputc('\n', ed_fp);
X	    }
X	(void) fflush(ed_fp);
X    }
X    if (!istool && ison(glob_flags, WARNING)) {
X	if (escape && strncmp(escape, DEF_ESCAPE, 1))
X	    print("(escape character is set to `%c')\n", *escape);
X	if (wrapcolumn && wrapcolumn < 20)
X	    print("(warning: wrapping only %d columns from the left!)\n",
X		    wrapcolumn);
X    }
X
X    /* do an "if" again in case editor not found and EDIT turned off */
X    if (!istool && ison(flags, EDIT)) {
X	char **argv, *edit;
X	int argc;
X	if ((!(edit = do_set(set_options, "visual")) || !*edit) &&
X		(!(edit = do_set(set_options, "editor")) || !*edit))
X	    edit = DEF_EDITOR;
X	(void) sprintf(line, "%s %s", edit, edfile);
X	if ((argv = mk_argv(line, &argc, FALSE)) && argc > 0) {
X	    print("Starting \"%s\"...\n", argv[0]);
X	    (void) fclose(ed_fp);
X	    ed_fp = NULL_FILE;
X	    execute(argv);
X	    free_vec(argv);
X	    turnoff(flags, EDIT);
X	    turnoff(flags, FORWARD); /* forwarded messages must be unedited */
X	    /* upon exit of editor, user must now type eofc or "." to send */
X	    if (!(ed_fp = fopen(edfile, "r+"))) {
X		error("can't reopen %s", edfile);
X		return -1;
X	    }
X	    (void) fseek(ed_fp, 0L, 2);
X	} else
X	    print("Unable to start \"%s\"\n", edit);
X	wprint("(continue editing letter or ^%c to send)\n", eofc + '@');
X    } else if (ison(flags, SEND_NOW)) {
X	/* if finish_up_letter() was successful, file was successfully sent. */
X	if (!setjmp(cntrl_c_buf) && finish_up_letter() == 0) {
X	    rm_edfile(0);
X	    return 0;
X	}
X    } else if (had_hfile) {
X	/* it's not obvious what's going on -- enlighten user */
X	wprint("(continue editing or ^%c to send)\n", eofc + '@');
X    }
X
#ifdef SUNTOOL
X    if (istool) {
X	/* toolmode doesn't care if SEND_NOW -- user continues to edit file.
X	 * if SEND_NOW is not set, then the editor file has just been started,
X	 * so again, just return so user can edit file.
X	 */
X	if (ed_fp)
X	    fclose(ed_fp), ed_fp = NULL_FILE;
X	turnon(glob_flags, IS_GETTING);
X	return 0;
X    }
#endif /* SUNTOOL */
X    if (ison(flags, SEND_NOW)) {
X	/* editing couldn't have been on -- finish_up_letter() failed */
X	rm_edfile(0 - ison(flags, FORWARD));
X	return -1;
X    }
X
X    i = 0;
X    turnon(glob_flags, IS_GETTING);
X    do  {
X	/* If the user hits ^C in cbreak mode, mush will return to
X	 * Getstr and not clear the buffer. whatever is typed next will
X	 * be appended to the line.  jumping here will force the line to
X	 * be cleared cuz it's a new call.
X	 */
X	(void) setjmp(cntrl_c_buf);
X	while (Getstr(line, sizeof(line), 0) > -1) {
X	    if (!istool) /* toolmode checks on a timer -- don't do it here */
X		(void) check_new_mail(); /* if new mail comes in, get it */
X	    if ((i = add_to_letter(line)) <= 0)
X		break;
X	}
X    } while (i >= 0 && finish_up_letter() == -1);
X    turnoff(glob_flags, IS_GETTING);
X    return i; /* return -1 if ~x or ~q to terminate letter */
}
X
char *tilde_commands[] = {
X    "commands: [OPTIONAL argument]",
X    "t [list]\tChange list of recipients",
X    "s [subject]\tModify [set] subject header",
X    "c [cc list]\tModify [set] carbon copy recipients",
X    "b [bcc list]\tModify [set] blind carbon recipients",
X    "h\t\tModify all message headers",
X    "e [editor]\tEnter editor. Editor used: \"set editor\", env EDITOR, vi",
X    "v [editor]\tEnter visual editor. \"set visual\", env VISUAL, vi",
X    "u\t\tEdit previous (last) line in file.",
X    "p [pager]\tPage message; pager used: \"set pager\", env. PAGER, more",
X    "i [msg#'s]\tInclude current msg body [msg#'s] indented by \"indent_str\"",
X    "I [msg#'s]\tSame, but include the message headers from included messages",
X    "f [msg#'s]\tForward mail. Not indented, but marked as \"forwarded mail\"",
X    "S[!]\t\tInclude Signature file [suppress file]",
X    "F[!]\t\tAdd a fortune at end of letter [don't add]",
X    "w file\t\tWrite msg buffer to file name",
X    "a file\t\tAppend msg buffer to file name",
X    "r file\t\tRead filename into message buffer",
X    "q \t\tQuit message; save in dead.letter (unless \"nosave\" is set).",
X    "x \t\tQuit message; don't save in dead.letter.",
X    "$variable\tInsert the string value for \"variable\" into message.",
X    ":cmd\t\tRun the mail command \"cmd\".",
X    "|cmd\t\tPipe the message through the unix command \"cmd\".",
X    "E[!]\t\tClear contents of letter after saving to dead.letter [unless !].",
X    0
};
X
/*
X * TC_EDIT(tc) returns TRUE if tilde_command[tc] involves message
X * editing operations.  Used when EDIT_HDRS is active.
X */
#define TC_EDIT(tc) ((tc) && ((tc) < 6 || !tilde_commands[(tc)+1]))
X
/*
X * Add the line (char *) parameter to the letter.  Determine tilde
X * escapes and determine what to do.  This function returns 0 to
X * indicate user wants to end the letter, -1 if the letter cannot
X * be sent (~q, ~x no buffer after editor, etc...) or 1 to indicate
X * successful addition of the line to the letter.
X * This function may be called by toolmode just to change certain mush
X * internal variables via tilde escapes.  Thus, ed_fp might be null.
X */
add_to_letter(line)
char line[];
{
X    register char *p;
X    char buf[HDRSIZ > MAXPATHLEN ? HDRSIZ : MAXPATHLEN];
X
X    killme = 0;
X    if (ed_fp) /* may be null if istool */
X	(void) fseek(ed_fp, 0L, 2);
X
X    if (!strcmp(line, ".") && do_set(set_options, "dot"))
X	return 0;
X    if (line[0] != *escape || ison(glob_flags, QUOTE_MACRO)) {
X	(void) fputs(line, ed_fp);
X	(void) fputc('\n', ed_fp);
X	(void) fflush(ed_fp);
X	return 1;
X    }
X    /* all commands are "~c" (where 'c' is the command). set p = first
X     * character after 'c' and skip whitespace
X     */
X    p = &line[2];
X    skipspaces(0);
X    switch (line[1]) {
X	case 'v' : case 'p': case 'e' : case '|' : {
X	    if (!*p || *p == 'i')
X		switch (line[1]) {
X		    case 'p' :
X			if (!*p && !(p = do_set(set_options, "pager")))
X			    p = DEF_PAGER;
X			if (!*p || !strcmp(p, "internal"))
X			    p = NULL;
X		    when 'v' :
X			if (*p && p[1] || (p = do_set(set_options, "visual")))
X			    break;
X			/* else fall through */
X		    default :
X			if (!(p = do_set(set_options, "editor")) || !*p)
X			    p = DEF_EDITOR;
X		    when '|' :
X			print("No command for pipe\n");
X			return 1;
X		}
X	    if (line[1] == 'p' || line[1] == '|')
X		rewind(ed_fp);
X	    if (line[1] == 'p') {
X		(void) do_pager(p, TRUE); /* start the pager "p" */
X		if (isoff(flags, EDIT_HDRS)) {
X		    (void) do_pager(sprintf(buf, "To: %s\n", To), FALSE);
X		    if (Subject[0])
X			(void) do_pager(sprintf(buf, "Subject: %s\n", Subject),
X					FALSE);
X		    if (Cc[0])
X			(void) do_pager(sprintf(buf, "Cc: %s\n", Cc), FALSE);
X		    if (Bcc[0])
X			(void) do_pager(sprintf(buf, "Bcc: %s\n", Bcc), FALSE);
X		    (void) do_pager(strcpy(buf,
X					    "--------\nMessage contains:\n"),
X			FALSE);
X		}
X		while (fgets(buf, sizeof(buf), ed_fp))
X		    if (do_pager(buf, FALSE) == EOF)
X			break;
X		(void) do_pager(NULL, FALSE); /* end pager */
X	    } else if (line[1] == '|') {
X		FILE *pipe_fp;
X		(void) sprintf(buf, "( %s ) > %s", p, edfile);
X		if (unlink(edfile) < 0) {
X		    error("Can't unlink %s:", edfile);
X		    break; /* Drop out of switch */
X		}
X		if ((pipe_fp = popen(buf, "w")) == NULL_FILE) {
X		    error("Can't run \"%s\":", p);
X		    (void) file_to_fp(edfile, ed_fp, "w");
X		} else {
X		    while (fgets(buf, sizeof(buf), ed_fp))
X			if (fputs(buf, pipe_fp) == EOF) {
X			    print("Broken pipe\n");
X			    break;
X			}
X		    (void) pclose(pipe_fp);
X		}
X		pipe_fp = ed_fp; /* save ed_fp until we can reopen it */
X		if (!(ed_fp = fopen(edfile, "r+"))) {
X		    error("can't reopen %s", edfile);
X		    (void) rewind(pipe_fp);
X		    if (file_to_fp(edfile, pipe_fp, "w") < 0 ||
X			    !(ed_fp = fopen(edfile, "r+"))) {
X			error("can't restore old contents of %s", edfile);
X			ed_fp = pipe_fp;
X			dead_letter(0);
X			return -1;
X		    }
X		}
X		(void) fclose(pipe_fp);
X	    } else {
X		char **argv;
X		int argc;
X		(void) sprintf(buf, "%s %s", p, edfile);
X		if ((argv = mk_argv(buf, &argc, FALSE)) && argc > 0) {
X		    (void) fclose(ed_fp);
X		    ed_fp = NULL_FILE;
X		    execute(argv);
X		    free_vec(argv);
X		    /* tool will return even tho editor isn't done */
X		    if (!(ed_fp = fopen(edfile, "r+"))) {
X			error("can't reopen %s", edfile);
X			return -1;
X		    }
X		} else
X		    print("Unable to start \"%s\"\n", p);
X	    }
X	}
X	when '$': {
X	    register char *p2;
X	    if (!(p2 = do_set(set_options, p)))
X		print("(%s isn't set)\n", p);
X	    else
X		putstring(p2, ed_fp);
X	}
X	when ':': {
X	    char new[MAXMSGS_BITS];
X	    u_long save_flags = glob_flags;
X
X	    turnon(glob_flags, IGN_SIGS);
X	    turnoff(glob_flags, DO_PIPE);
X	    turnoff(glob_flags, IS_PIPE);
X	    (void) cmd_line(p, new);
X	    glob_flags = save_flags;
X	}
X	when 'i': case 'f': case 'I': case 'm': {
X	    int  n;
X	    u_long copy_flgs = 0;
X	    char list[MAXMSGS_BITS];
X
X	    if (!msg_cnt) {
X		wprint("No messages.\n");
X		break;
X	    }
X	    clear_msg_list(list);
X	    if (line[1] != 'f') {
X		turnon(copy_flgs, INDENT);
X		if (line[1] == 'i')
X		    turnon(copy_flgs, NO_HEADER);
X		else if (!chk_option("alwaysignore", "include"))
X		    turnon(copy_flgs, NO_IGNORE);
X	     } else if (!chk_option("alwaysignore", "forward"))
X		turnon(copy_flgs, NO_IGNORE);
#ifdef MSG_SEPARATOR
X	    turnon(copy_flgs, NO_SEPARATOR);
#endif /* MSG_SEPARATOR */
X	    if (!*p)
X		set_msg_bit(list, current_msg);
X	    else if (!do_range(p, list))
X		return 1;
X	    for (n = 0; n < msg_cnt; n++)
X		if (msg_bit(list, n)) {
X		    if (line[1] == 'f') {
X			(void) reply_to(n, FALSE, buf);
X			(void) fprintf(ed_fp,
X				    "--- Forwarded mail from %s\n\n", buf);
X		    }
X		    wprint("Including message %d ... ", n+1);
X		    wprint("(%d lines)\n", copy_msg(n, ed_fp, copy_flgs, NULL));
X		    set_isread(n);
X		    if (line[1] == 'f')
X			(void) fprintf(ed_fp,
X			    "\n--- End of forwarded message from %s\n\n", buf);
X		    else
X			(void) fputc('\n', ed_fp);
X		}
X	}
X	/* To: Cc: and Bcc: headers */
X	when 'b':
X	case 't':
X	case 'c': {
X	    char *h = (line[1] == 't')? To : (line[1] == 'c')? Cc : Bcc;
X	    char *Prompt = line[1] == 't'? "To: " :
X			   line[1] == 'c'? "Cc: " : "Bcc: ";
X	    if (ison(flags, EDIT_HDRS)) {
X		print("You must use an editor to change your headers.\n");
X		break;
X	    }
X
X	    if (*p) {
X		fix_up_addr(p);
X		if (*h)
X		    (void) sprintf(h+strlen(h), ", %s", p);
X		else
X		    (void) strcpy(h, p);
X	    } else if (!(p = set_header(Prompt, h, TRUE)) || !*p)
X		*h = 0;
X	    else {
X		fix_up_addr(p);
X		(void) strcpy(h, p);
X	    }
X	}
X	when 's':
X	    if (ison(flags, EDIT_HDRS)) {
X		print("You must use an editor to change your headers.\n");
X		break;
X	    }
X	    if (*p || (p = set_header("Subject: ", Subject, 1)))
X		if (!*p)
X		    Subject[0] = 0;
X		else
X		    (void) strcpy(Subject, p);
X	when 'h':
X	    if (ison(flags, EDIT_HDRS)) {
X		print("You must use an editor to change your headers.\n");
X		break;
X	    }
X	    while ((p = set_header("To: ", To, 1)) && !*p)
X		print("(There must be a recipient.)\n");
X	    (void) strcpy(To, p);
X	    if (p = set_header("Subject: ", Subject, 1))
X		if (!*p)
X		    Subject[0] = 0;
X		else
X		    (void) strcpy(Subject, p);
X	    if (p = set_header("Cc: ", Cc, 1))
X		if (!*p)
X		    Cc[0] = 0;
X		else {
X		    fix_up_addr(p);
X		    (void) strcpy(Cc, p);
X		}
X	    if (p = set_header("Bcc: ", Bcc, 1))
X		if (!*p)
X		    Bcc[0] = 0;
X		else {
X		    fix_up_addr(p);
X		    (void) strcpy(Bcc, p);
X		}
X	when 'F': case 'S' : {
X	    if (*p == '!')
X		turnoff(flags, line[1] == 'F'? DO_FORTUNE : SIGN);
X	    else
X		turnon(flags, line[1] == 'F'? DO_FORTUNE : SIGN);
X	    wprint("%sadding %s at end of message.\n", *p == '!'? "not " : "",
X		line[1] == 'F'? "fortune" : "signature");
X	}
X	when 'w': case 'a': case 'r':
X	    if (!*p) {
X		print("(you must specify a filename)\n");
X		return 1;
X	    }
X	    (void) fseek(ed_fp, 0L, 2); /* append */
X	    (void) file_to_fp(p, ed_fp, (line[1] == 'r')? "r":
X			      (line[1] == 'w')? "w": "a");
X	/* go up one line in the message file and allow the user to edit it */
X	when 'u': {
X	    long newpos, pos = ftell(ed_fp);
X	    char oldline[256];
X	    if (pos <= 0L) { /* pos could be -1 if ftell() failed */
X		print("(No previous line in file.)\n");
X		return 1;
X	    }
X	    /* get the last 256 bytes written and read backwards from the
X	     * current place until '\n' is found. Start by moving past the
X	     * first \n which is at the end of the line we want to edit
X	     */
X	    newpos = max(0, pos - 256L);
X	    (void) fseek(ed_fp, newpos, L_SET);
X	    /* don't fgets -- it'll stop at a \n */
X	    (void) fread(line, sizeof(char), (int)(pos-newpos), ed_fp);
X	    pos--;
X	    /* the last char in line should be a \n cuz it was last input */
X	    if (line[(int)(pos-newpos)] != '\n')
X		print("I don't know how, but your last line ended with %c.\n",
X		    line[(int)(pos-newpos)]);
X	    else
X		line[(int)(pos-newpos)] = 0; /* null terminate \n for ^H-ing */
X	    for (pos--; pos > newpos && line[(int)(pos-newpos)] != '\n'; pos--)
X		;
X	    /* we've gone back to the end of the second previous line. Check
X	     * to see if the char we're pointing to is a \n.  It should be, but
X	     * if it's not, we moved back to the first line of the file.
X	     */
X	    if (line[(int)(pos-newpos)] == '\n')
X		++pos;
X	    /* save the old line that's there in case the user boo-boos */
X	    (void) strcpy(oldline, &line[(int)(pos-newpos)]);
X	    /* let set header print out the line and get the input */
X	    if (!(p = set_header("", &line[(int)(pos-newpos)], TRUE))) {
X		print("Something bad happened and I don't know what it is.\n");
X		p = oldline;
X	    } else if (*p == *escape)
X		print("(Warning: %c escapes ignored on %cu lines.)\n",
X				*escape, *escape);
X	    /* seek to to the position where the new line will go */
X	    (void) fseek(ed_fp, pos, L_SET);
X	    /* put the newly typed line */
X	    (void) fputs(p, ed_fp); /* don't add \n. padding may be necessary */
X	    /* if the new line is less than the old line, we're going to do
X	     * one of two things.  The best thing to do is to truncate the
X	     * file to the end of the new line.  Sys-v can't do that, so we
X	     * pad the line with blanks.  May be messy in some cases, but...
X	     */
X	    if ((pos = strlen(p) - strlen(oldline)) < 0) {
#ifndef SYSV
X		/* add the \n, flush the file, truncate to the current pos */
X		(void) fputc('\n', ed_fp);
X		(void) fflush(ed_fp);
X		(void) ftruncate(fileno(ed_fp), (off_t) ftell(ed_fp));
#else /* SYSV */
X		/* pad with blanks to the length of the old line. add \n */
X		while (pos++ < 0)
X		    (void) fputc(' ', ed_fp);
X		(void) fputc('\n', ed_fp), (void) fflush(ed_fp);
#endif /* SYSV */
X	    } else {
X		/* the new line is >= the old line, add \n -- no trunc req. */
X	        (void) fputc('\n', ed_fp);
X		(void) fflush(ed_fp);
X	    }
X	    return 1;
X	 }
X	/* break;  not here cuz of "return" (lint). */
X	case 'E':
X	    if (ison(flags, EDIT_HDRS)) {
X		print("You must use an editor to empty the message buffer.\n");
X		break;
X	    }
X	    if (*p != '!' && !do_set(set_options, "nosave"))
X		dead_letter(0);
X	    if (emptyfile(&ed_fp, edfile) == -1) {
X		error(edfile);
X		return -1;
X	    } else
X		print("Message buffer empty\n");
X	when 'q':
X	    /* save in dead.letter if nosave not set -- rm_edfile(-2). */
X	    rm_edfile(-2); /* doesn't return out of tool mode */
X	    return -1;
X	    /* break; not stated cuz of "return" (lint) */
SHAR_EOF
true || echo 'restore of mail.c failed'
fi
echo 'End of  part 11'
echo 'File mail.c is continued in part 12'
echo 12 > _shar_seq_.tmp
exit 0
exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.