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

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

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

#!/bin/sh
# do not concatenate these parts, unpack them in order with /bin/sh
# file addrs.c continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 3; 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 addrs.c'
else
echo 'x - continuing file addrs.c'
sed 's/^X//' << 'SHAR_EOF' >> 'addrs.c' &&
X    p = sender + c;
X    *p++ = ',', *p++ = ' ';
X    (void) strcpy(p, next);
X    (void) strcpy(to, sender);
}
X
/*
X * pass a string describing header like, "Subject: ", current value, and
X * whether or not to prompt for it or to just post the information.
X * If do_prompt is true, "type in" the current value so user can either
X * modify it, erase it, or add to it.
X */
char *
set_header(str, curstr, do_prompt)
register char *str, *curstr;
{
X    static char	   buf[HDRSIZ];
X    int 	   offset = 0;
X    register char  *p = curstr;
X
X    if (!str)
X	str = "";
X
X    buf[0] = 0;
X    print(str);
X    (void) fflush(stdout);		 /* force str curstr */
X    if (do_prompt) {
X	if (curstr)
X	    if (isoff(glob_flags, ECHO_FLAG)) {
X		Ungetstr(curstr);
X	    } else
#ifdef TIOCSTI
X		for (p = curstr; *p; p++)
X		    if (ioctl(0, TIOCSTI, p) == -1) {
X			error("ioctl: TIOCSTI");
X			print("You must retype the entire line.\n%s", str);
X			break;
X		    }
#else /* !TIOCSTI */
X		print("WARNING: -e flag! Type the line over.\n%s", str);
#endif /* TIOCSTI */
X
X	if (istool)
X	    return NULL;
X	/* simulate the fact that we're getting input for the letter even tho
X	 * we may not be.  set_header is called before IS_GETTING is true,
X	 * but if we set it to true temporarily, then signals will return to
X	 * the right place (stop/continue).
X	 */
X	{
X	    u_long getting = ison(glob_flags, IS_GETTING);
X	    int wrapping = wrapcolumn;
X	    /* Funky trick here.  If the prompt string is empty,
X	     * assume that we are allowed to do line wrap;
X	     * otherwise, temporarily disable line wrap
X	     */
X	    if (*str)
X		wrapcolumn = 0;
X	    if (!getting)
X		turnon(glob_flags, IS_GETTING);
X	    if (Getstr(buf, sizeof(buf), offset) == -1) {
X		putchar('\n');
X		buf[0] = 0;
X	    }
X	    if (!getting)
X		turnoff(glob_flags, IS_GETTING);
X	    wrapcolumn = wrapping;
X	}
X    } else
X	puts(strcpy(buf, curstr));
X    if (debug > 1)
X	print("returning (%s) from set_header\n", buf);
X    return buf;
}
X
/*
X * improve uucp paths by looking at the name of each host listed in the
X * path given.
X *    sun!island!pixar!island!argv
X * It's a legal address, but redundant. Also, if we know we talk to particular
X * hosts via uucp, then we can just start with that host and disregard the path
X * preceding it.  So, first get the known hosts and save them. Then start
X * at the end of the original path (at the last ! found), and move backwards
X * saving each hostname.  If we get to a host that we know about, stop there
X * and use that address.  If the system knows about domains, skip all paths
X * that precede a domain hostname.  If we get to a host we've already seen,
X * then delete it and all the hosts since then until the first occurrence of
X * that hostname.  When we get to the beginning, the address will be complete.
X * The route_path is prepended to each address to check make sure this path
X * is used if no known_hosts precede it in that address.
X *
X * Return all results into the original buffer passed to us.  If route_path
X * adds to the length of all the paths, then the original buffer could be
X * overwritten.  someone should check for this!
X */
improve_uucp_paths(original, size, route_path)
char *original, *route_path;
{
X    char	   name[256], addr[256], buf[2 * HDRSIZ], *end;
X    char	  *hostnames[32], tmp[sizeof addr], *domain_path;
X    register char *p, *p2, *recipient, *start = original, *b = buf;
X    int		   saved_hosts, i, is_domain;
X
X    if (!original || !*original)
X	return;
X
X    /* use domain_path to point to the path for pathnames that have
X     * a fully qualified domain host in them.
X     */
X    domain_path = do_set(set_options, "domain_route");
X    while (end = get_name_n_addr(start, name, tmp)) {
X	/* first copy the route path, then the rest of the address. */
X	p = addr;
X	if (route_path && *route_path) {
X	    p += Strcpy(addr, route_path);
X	    *p++ = '!';
X	}
X	(void) bang_form(p, tmp);
X	saved_hosts = 0;
X	if (p2 = rindex(p, '!')) {
X	    recipient = p2+1;
X	    /* save the uucp-style address *without* route_path in tmp */
X	    (void) strcpy(tmp, p);
X	    for (p = p2; p > addr; p--) {
X		is_domain = 0;
X		/* null the '!' separating the rest of the path from the part
X		 * of the path preceding it and move p back to the previous
X		 * '!' (or beginning to addr) for hostname to point to.
X		 */
X		for (*p-- = 0; p > addr && *p != '!'; p--)
X		    if (!is_domain && domain_path && *p == '.' &&
X			    lcase_strncmp(p, ".uucp", 5))
X			is_domain++;
X		/* if p is not at the addr, move it forward past the '!' */
X		if (p != addr)
X		    ++p; /* now points to a null terminated hostname */
X		/* if host is ourselves, ignore this and preceding hosts */
X		for (i = 0; ourname && ourname[i]; i++)
X		    if (!lcase_strncmp(p, ourname[i], -1))
X			break;
X		if (ourname && ourname[i]) {
X		    is_domain = 0; /* we've eliminated all domains */
X		    break;
X		}
X		/* check already saved hostnames. If host is one of them,
X		 * delete remaining hostnames since there is a redundant path.
X		 */
X		for (i = 0; i < saved_hosts; i++)
X		    if (!lcase_strncmp(hostnames[i], p, -1))
X			saved_hosts = i;
X
X		/* Add the hostname to the path being constructed */
X		hostnames[saved_hosts++] = p;
X
X		/* If the original path or the address is a fully qualified
X		 * hostname (domain info is included), then break here
X		 */
X		if (p == addr || is_domain && domain_path)
X		    break;
X		/* If we know that we call this host, break */
X		for (i = 0; known_hosts && known_hosts[i]; i++)
X		    if (!lcase_strncmp(p, known_hosts[i], -1))
X			break;
X		if (known_hosts && known_hosts[i])
X		    break;
X	    }
X	    /* temporary holder for where we are in buffer (save address) */
X	    p2 = b;
X	    if (is_domain && domain_path && *domain_path)
X		b += Strcpy(b, domain_path), *b++ = '!';
X	    while (saved_hosts-- > 0) {
X		b += Strcpy(b, hostnames[saved_hosts]);
X		*b++ = '!';
X	    }
X	    b += Strcpy(b, recipient);
X	    if (!strcmp(p2, tmp)) { /* if the same, address was unmodified */
X		b = p2; /* reset offset in buf (b) to where we were (p2) */
X		goto unmodified;
X	    }
X	    if (*name)
X		b += strlen(sprintf(b, " (%s)", name));
X	} else {
X	    char c;
unmodified:
X	    c = *end;
X	    *end = 0;
X	    b += Strcpy(b, start); /* copy the entire address with comments */
X	    *end = c;
X	}
X	if (b - buf > size) {
X	    wprint("Warning: address list truncated!\n");
X	    /* Use a very poor heuristic to find the last complete address */
X	    for (b = buf+size - 1; *b != ','; b--)
X		;
X	    wprint("Lost addresses: %s%s\n", b, end); /* end = not yet parsed */
X	    while (isspace(*b) || *b == ',')
X		b--;
X	    break;
X	}
X	for (start = end; *start == ',' || isspace(*start); start++)
X	    ;
X	if (!*start)
X	    break;
X	*b++ = ',', *b++ = ' ', *b = '\0';
X    }
X    (void) strcpy(original, buf);
}
X
/*
X * rm_cmts_in_addr() removes the comment lines in addresses that result from
X * sendmail or other mailers which append the user's "real name" on the
X * from lines.  See get_name_n_addr().
X */
rm_cmts_in_addr(str)
register char *str;
{
X    char addr[BUFSIZ], buf[HDRSIZ], *start = str;
X    register char *b = buf;
X
X    *b = 0;
X    do  {
X	if (!(str = get_name_n_addr(str, NULL, addr)))
X	    break;
X	b += Strcpy(b, addr);
X	while (*str == ',' || isspace(*str))
X	    str++;
X	if (*str)
X	    *b++ = ',', *b++ = ' ', *b = '\0';
X    } while (*str);
X    for (b--; b > start && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    (void) strcpy(start, buf);
}
X
/*
X * take_me_off() is intended to search for the user's login name in an
X * address string and remove it.  If "metoo" is set, return without change.
X * determine which addresses are the "user'"'s addresses by comparing them
X * against the host/path names in alternates.  If the "*" is used, then
X * this matches the address against the user's current login and -any- path.
X *
X * Note that the alternates list is an array of addresses stored *reversed*!
X */
take_me_off(str)
char *str;
{
X    int i = 0, rm_me;
X    char tmp[256], addr[256], buf[HDRSIZ], *start = str;
X    register char *p, *p2, *b = buf;
X
X    if (!str || !*str)
X	return;
X
X    Debug("take_me_off()\n");
X    *b = 0;
X    do  {
X	rm_me = FALSE;
X	/* get the first "address" and advance p to next addr (ignore name) */
X	if (!(p = get_name_n_addr(str, NULL, tmp)))
X	    break; /* we've reached the end of the address list */
X	/* see if user's login is in the address */
X	if (!strcmp(login, tmp))
X	    rm_me = TRUE;
X	else {
X	    int len;
X	    /* put address in !-format and store in "addr" */
X	    (void) bang_form(addr, tmp);
X	    (void) reverse(addr);
X	    for (i = 0; alternates && alternates[i] && !rm_me; i++) {
X		if (alternates[i][0] == '*') {
X		    if (alternates[i][1] == '\0')
X			p2 = reverse(strcpy(tmp, login));
X		    else
X			p2 = reverse(strcpy(tmp, &alternates[i][1]));
X		} else
X		    p2 = alternates[i];
X		if (!lcase_strncmp(p2, addr, (len = strlen(p2))) &&
X			(!addr[len] || addr[len] == '!')) {
X		    Debug("\t%s\n", reverse(addr));
X		    rm_me = TRUE;
X		}
X	    }
X	    for (i = 0; !rm_me && ourname && ourname[i]; i++) {
X		p2 = tmp + Strcpy(tmp, ourname[i]);
X		*p2++ = '!';
X		(void) strcpy(p2, login);
X		(void) reverse(tmp);
X		if (!lcase_strncmp(tmp, addr, (len = strlen(tmp))) &&
X			(!addr[len] || addr[len] == '!')) {
X		    Debug("\t%s\n", reverse(addr));
X		    rm_me = TRUE;
X		}
X	    }
X	}
X	/* The address is not the user's -- put it into the returned list */
X	if (!rm_me) {
X	    char c = *p;
X	    *p = 0;
X	    b += Strcpy(b, str);
X	    *p = c;
X	}
X	while (*p == ',' || isspace(*p))
X	    p++;
X	if (*p && !rm_me)
X	    *b++ = ',', *b++ = ' ', *b = '\0';
X    } while (*(str = p));
X    for (b--; b > buf && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    (void) strcpy(start, buf);
}
X
/*
X * Place commas in between all addresses that don't already have
X * them.  Addresses which use comments which are in parens or _not_
X * within angle brackets *must* already have commas around them or
X * you can't determine what is a comment and what is an address.
X */
fix_up_addr(str)
char *str;
{
X    char buf[HDRSIZ], *start = str;
X    register char c, *p, *b = buf;
X
X    *b = 0;
X    do  {
X	/* get_name returns a pointer to the next address */
X	if (!(p = get_name_n_addr(str, NULL, NULL)))
X	    break;
X	c = *p, *p = 0;
X	if (strlen(str) + (b - buf) >= sizeof(buf) - 2) {
X	    /* wprint("Address too long! Lost address: \"%s\"\n", str); */
X	    *p = c;
X	    break;
X	}
X	for (b += Strcpy(b, str); b > buf && isspace(*(b-1)); b--)
X	    *b = 0;
X	for (*p = c; *p == ',' || isspace(*p); p++)
X	    ;
X	if (*p)
X	    *b++ = ',', *b++ = ' ', *b = '\0';
X    } while (*(str = p));
X    for (b--; b > buf && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    (void) strcpy(start, buf);
}
X
/*
X * Remove redundant addresses.
X * Assume improve_uucp_paths, fix_up_addr or whatever have already been called.
X */
rm_redundant_addrs(to, cc)
char *to, *cc;
{
X    char tmp[256], addr[256], buf[HDRSIZ];
X    char **list; /* a list of addresses for comparison */
X    int list_cnt = 0, l;
X    register char c, *p, *b, *start;
X
X    Debug("rm_redundant_addrs()\n");
X    list = (char **) calloc(256, sizeof(char *));
X    if (!list) {
X	error("out of memory in rm_redundant_addrs");
X	return;
X    }
X    start = to;
X    b = buf, *b = 0;
X    /* first do the To header */
X    do  {
X	/* get_name returns a pointer to the next address */
X	if (!(p = get_name_n_addr(to, NULL, tmp)))
X	    break;
X	c = *p, *p = 0;
X	(void) bang_form(addr, tmp);
X	for (l = 0; l < list_cnt; l++)
X	    if (!lcase_strncmp(addr, list[l], -1))
X		break;
X	/* if l == list_cnt, we got a new address, store it and add to buf */
X	if (l == list_cnt) {
X	    /* Don't overwrite buffer. */
X	    if (list_cnt < 256)
X		list[list_cnt++] = savestr(addr);
X	    if (b > buf)
X		*b++ = ',', *b++ = ' ', *b = '\0';
X	    for (b += Strcpy(b, to); b > buf && isspace(*(b-1)); b--)
X		*b = 0;
X	} else
X	    Debug("\t%s\n", tmp); /* already specified (removed from list) */
X	for (*p = c; *p == ',' || isspace(*p); p++)
X	    ;
X    } while (*(to = p));
X    for (b--; b > buf && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    (void) strcpy(start, buf);
X    b = buf, *b = 0;
X    /* Now do the Cc header.  If addr is listed in the To field, rm it in cc */
X    start = cc;
X    do  {
X	/* get_name returns a pointer to the next address */
X	if (!(p = get_name_n_addr(cc, NULL, tmp)))
X	    break;
X	c = *p, *p = 0;
X	(void) bang_form(addr, tmp);
X	for (l = 0; l < list_cnt; l++)
X	    if (!lcase_strncmp(addr, list[l], -1))
X		break;
X	if (l == list_cnt) {
X	    /* Don't overwrite buffer. */
X	    if (list_cnt < sizeof(list)/sizeof(char *))
X		list[list_cnt++] = savestr(addr);
X	    if (b > buf)
X		*b++ = ',', *b++ = ' ', *b = '\0';
X	    for (b += Strcpy(b, cc); b > buf && isspace(*(b-1)); b--)
X		*b = 0;
X	} else
X	    Debug("\t%s\n", tmp); /* already specified (removed from list) */
X	for (*p = c; *p == ',' || isspace(*p); p++)
X	    ;
X    } while (*(cc = p));
X    list[list_cnt] = NULL; /* for free_vec */
X    free_vec(list);
X    for (b--; b > buf && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    (void) strcpy(start, buf);
}
X
/*
X * Get address and name from a string (str) which came from an address header
X * in a message or typed by the user.  The string may contain one or more
X * well-formed addresses.  Each must be separated by a comma.
X *
X * address, address, address
X * address (comment or name here)
X * comment or name <address>
X * "Comment, even those with comma's!" <address>
X * address (comma, (more parens), etc...)
X *
X * This does *not* handle cases like:
X *    comment <address (comment)>
X *
X * find the *first* address here and return a pointer to the end of the
X * address (usually a comma).  Return NULL on error: non-matching parens,
X * brackets, quotes...
X */
char *
get_name_n_addr(str, name, addr)
register char *str, *name, *addr;
{
X    register char *p, *p2, *beg_addr = addr, *beg_name = name, c;
X
X    if (addr)
X	*addr = 0;
X    if (name)
X	*name = 0;
X    if (!str || !*str)
X	return NULL;
X
X    while (isspace(*str))
X	str++;
X
X    /* first check to see if there's something to look for */
X    if (!(p = any(str, ",(<\""))) {
X	/* no comma or indication of a quote character. Find a space and
X	 * return that.  If nothing, the entire string is a complete address
X	 */
X	if (p = any(str, " \t"))
X	    c = *p, *p = 0;
X	if (addr)
X	    (void) strcpy(addr, str);
X	if (p)
X	    *p = c;
X	return p? p : str + strlen(str);
X    }
X
X    /* comma terminated before any comment stuff.  If so, check for whitespace
X     * before-hand cuz it's possible that strings aren't comma separated yet
X     * and they need to be.
X     *
X     * address address address, address
X     *                        ^p  <- p points here.
X     *        ^p2 <- should point here.
X     */
X    if (*p == ',') {
X	c = *p, *p = 0;
X	if (p2 = any(str, " \t"))
X	    *p = ',', c = *p2, p = p2, *p = 0;
X	if (addr)
X	    (void) strcpy(addr, str);
X	*p = c;
X	return p;
X    }
X
X    /* starting to get hairy -- we found an angle bracket. This means that
X     * everything outside of those brackets are comments until we find that
X     * all important comma.  A comment AFTER the <addr> :
X     *  <address> John Doe
X     * can't call this function recursively or it'll think that "John Doe"
X     * is a string with two legal address on it (each name being an address).
X     */
X    if (*p == '<') { /* note that "str" still points to comment stuff! */
X	if (name && *str) {
X	    *p = 0;
X	    name += Strcpy(name, str);
X	    *p = '<';
X	}
X	if (!(p2 = index(p+1, '>'))) {
X	    wprint("Warning! Malformed address: \"%s\"\n", str);
X	    return NULL;
X	}
X	if (addr) {
X	    /* to support <addr (comment)> style addresses, add code here */
X	    *p2 = 0;
X	    skipspaces(1);
X	    addr += Strcpy(addr, p);
X	    while (addr > beg_addr && isspace(*(addr-1)))
X		*--addr = 0;
X	    *p2 = '>';
X	}
X	/* take care of the case "... <addr> com (ment)" */
X	{
X	    int p_cnt = 0; /* parenthesis counter */
X	    p = p2;
X	    /* don't recurse yet -- scan till null, comma or '<'(add to name) */
X	    for (p = p2; p[1] && (p_cnt || p[1] != ',' && p[1] != '<'); p++) {
X		if (p[1] == '(')
X		    p_cnt++;
X		else if (p[1] == ')')
X		    p_cnt--;
X		if (name)
X		    *name++ = p[1];
X	    }
X	    if (p_cnt) {
X		wprint("Warning! Malformed name: \"%s\"\n", name);
X		return NULL;
X	    }
X	}
X	if (name && name > beg_name) {
X	    while (isspace(*(name-1)))
X		--name;
X	    *name = 0;
X	}
X    }
X
X    /* this is the worst -- now we have parentheses/quotes.  These guys can
X     * recurse pretty badly and contain commas within them.
X     */
X    if (*p == '(' || *p == '"') {
X	char *start = p;
X	int comment = 1;
X	c = *p;
X	/* "str" points to address while p points to comments */
X	if (addr && *str) {
X	    *p = 0;
X	    while (isspace(*str))
X		str++;
X	    addr += Strcpy(addr, str);
X	    while (addr > beg_addr && isspace(*(addr-1)))
X		*--addr = 0;
X	    *p = c;
X	}
X	while (comment) {
X	    if (c == '"' && !(p = index(p+1, '"')) ||
X		c == '(' && !(p = any(p+1, "()"))) {
X		wprint("Warning! Malformed address: \"%s\"\n", str);
X		return NULL;
X	    }
X	    if (*p == '(') /* loop again on parenthesis. quote ends loop */
X		comment++;
X	    else
X		comment--;
X	}
X	/* Something like ``Comment (Comment) <addr>''.  In this case
X	 * the name should include both comment parts with the
X	 * parenthesis.   We have to redo addr.
X	 */
X	if ((p2 = any(p+1, "<,")) && *p2 == '<') {
X	    if (!(p = index(p2, '>'))) {
X		wprint("Warning! Malformed address: \"%s\"\n", str);
X		return NULL;
X	    }
X	    if (addr = beg_addr) { /* reassign addr and compare to null */
X		c = *p; *p = 0;
X		addr += Strcpy(addr, p2+1);
X		while (addr > beg_addr && isspace(*(addr-1)))
X		    *--addr = 0;
X		*p = c;
X	    }
X	    if (name) {
X		c = *p2; *p2 = 0;
X		name += Strcpy(name, str);
X		while (name > beg_name && isspace(*(name-1)))
X		    *--name = 0;
X		*p2 = c;
X	    }
X	} else if (name && start[1]) {
X	    c = *p, *p = 0; /* c may be ')' instead of '(' now */
X	    name += Strcpy(name, start+1);
X	    while (name > beg_name && isspace(*(name-1)))
X		*--name = 0;
X	    *p = c;
X	}
X    }
X    skipspaces(1);
X    /* this is so common, save time by returning now */
X    if (!*p || *p == ',' || *p == '<')
X	return p;
X    return get_name_n_addr(p, name, addr);
}
X
/* takes string 's' which can be a name or list of names separated by
X * commas and checks to see if each is aliased to something else.
X * return address of the static buf.
X */
char *
alias_to_address(s)
register char *s;
{
X    static char buf[HDRSIZ];
X    register char *p, *p2, *tmp;
X    char newbuf[HDRSIZ], c;
X    static int recursive;
X
X    if (!aliases)
X	return strcpy(buf, s);
X    if (!s || !*s)
X	return NULL;
X    if (!recursive) {
X	bzero(buf, sizeof buf);
X	p2 = buf;  /* if we're starting all this, p2 starts at &buf[0] */
X    } else
X	p2 = buf+strlen(buf);   /* else, pick up where we left off */
X
X    if (++recursive == 30) {
X	wprint("alias references too many addresses!\n");
X	recursive = 0;
X	return NULL;
X    }
X    do  {
X	char addr[256];
X	if (!(p = get_name_n_addr(s, NULL, addr)))
X	    break;
X	c = *p, *p = 0;
X
X	/* On recursive calls, compare against the entire
X	 * previous expansion, not just the address part.
X	 */
X	if (recursive > 1)
X	    (void) strcpy(addr, s);
X
X	/* if this is an alias, recurse this routine to expand it out */
X	if ((tmp = do_set(aliases, addr)) && *tmp) {
X	    if (!alias_to_address(strcpy(newbuf, tmp))) {
X		*p = c;
X		return NULL;
X	    } else
X		p2 = buf+strlen(buf);
X	/* Now, make sure the buffer doesn't overflow */
X	} else if (strlen(s) + (p2-buf) + 2 > sizeof buf) {  /* add ", " */
X	    wprint("address length too long.\n");
X	    recursive = 0;
X	    *p = c;
X	    return NULL;
X	} else {
X	    /* append the new alias (or unchanged address) onto the buffer */
X	    p2 += Strcpy(p2, s);
X	    *p2++ = ',', *p2++ = ' ', *p2 = '\0';
X	}
X	for (*p = c; *p == ',' || isspace(*p); p++)
X	    ;
X    } while (*(s = p));
X    if (recursive)
X	recursive--;
X    if (!recursive)
X	*(p2-2) = 0;  /* get rid of last ", " if end of recursion */
X    return buf;
}
X
/*
X * Wrap addresses so that the headers don't exceed n chars (typically 80).
X */
char *
wrap_addrs(str, n)
char *str;
{
X    char buf[HDRSIZ * 2], *start = str;
X    register char *b = buf, *p, c, *line_start = buf;
X
X    *b = 0;
X    do  {
X	/* get_name returns a pointer to the next address */
X	if (!(p = get_name_n_addr(str, NULL, NULL)))
X	    break;
X	c = *p, *p = 0;
X	if (b > buf) {
X	    *b++ = ',', *b++ = ' ', *b = '\0';
X	    if (b - line_start + strlen(str) + 8 /* \t = 8 */ >= n)
X		*b++ = '\n', *b++ = '\t', line_start = b;
X	}
X	for (b += Strcpy(b, str); b > buf && isspace(*(b-1)); b--)
X	    *b = 0;
X	for (*p = c; *p == ',' || isspace(*p); p++)
X	    ;
X    } while (*(str = p));
X    for (b--; b > buf && (*b == ',' || isspace(*b)); b--)
X	*b = 0;
X    return strcpy(start, buf);
}
SHAR_EOF
echo 'File addrs.c is complete' &&
chmod 0644 addrs.c ||
echo 'restore of addrs.c failed'
Wc_c="`wc -c < 'addrs.c'`"
test 33326 -eq "$Wc_c" ||
	echo 'addrs.c: original size 33326, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= advanced.mushrc ==============
if test -f 'advanced.mushrc' -a X"$1" != X"-c"; then
	echo 'x - skipping advanced.mushrc (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting advanced.mushrc (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'advanced.mushrc' &&
# advanced.mushrc
# by Bart Schaefer
# with special thanks to Phil Lapsley <phil@east.Berkeley.EDU>, who
# provided the original files on which this example is based.  Most of
# Phil's stuff is still here -- just reorganized and updated to use
# mush 6.4 features that were unavailable when Phil did the originals.
#
# This file is intended to demonstrate helpful ways to use the
# .mushrc, not advanced mush commands.
X
# The variable $thisfolder is always set EXCEPT when the .mushrc file
# is read the first time.  A test for non-existance of $thisfolder
# allows the same .mushrc file to be sourced repeatedly without
# redundant execution of the initialization commands.
#
if ! $?thisfolder
X    # Ignore the usual stuff
X    ignore	received via message-id status priority
X    # Hide folders in ~/.mail and save read mail in spool
X    set		folder=~/.mail hold
X    # Remember a few commands, set up editors, act like a shell
X    set		history=20 editor=ex visual=vi unix
X    # Prompt has folder name, message number, history number
X    set		prompt="%f %m (!) & "
X    # Header summaries show name, date, and subject
X    set		hdr_format="%25n %-15d  %27s"
X    # Initialize the cmds below (see later comments)
X    set		first_time=1
X
X    # These two commands are used for automated "bursting" of the spool
X    # mailbox.  This means that the messages are reorganized into new
X    # folders to be read in a prearranged order.  See comments below.
X    #
X    # n brings up the next folder, not the next message
X    cmd		n	'source ~/.mushrc'
X    # N gets the next folder without "bursting"
X    cmd		N	'set first_time=0; source ~/.mushrc'
X
X    # Delete messages by pattern-matching.  Examples:
X    #  del f mailer-daemon		Delete mail from mailer-daemon
X    #  del t mush-users			Delete mail to mush-users
X    cmd		del	'pick -i -\!* | delete'
X    # Forwarding
X    cmd		for	'mail -f'
X    # Quick folder change
X    cmd		F	'folder'
X
X    # Some useful aliases
X    alias	dheller	'The Mush God <argv@sun.com>'
X    alias	barts	'Archangel Mushael <schaefer@cse.ogi.edu>'
X
X    # On init, don't source beyond this point
X    exit
endif	# End of init section -- read on startup only
X
# This part of the file handles "bursting".  A burst is done when the
# n cmd is used the first time.  This is most useful if you habitually
# have lots of mail when you first log in each morning; unwanted mail
# can be deleted, and other mail organized for you.
#
# The folders in this example bursting scheme are:
#	mush-users	anything to or cc'ed to mush-users
#	stats		daily stats
#	root		root mail other than daily stats
# Mail not falling into one of these categories is left in the system
# mailbox to be dealt with first.
#
if $first_time
X    # Kill off some uucp garbage
X    pick -i -s "file c.* delete" | delete
X    pick -i -s "file .* can.t access" | delete
X    pick -i -s "remote access to path/file denied" | delete
X    # Nuke the boring usenet stuff
X    pick -i -f usenet | pick -i -s "uucp map for" | delete
X    pick -i -t usenet | pick -i -s "returned mail" | delete
X    pick -i -t usenet | pick -i -s "automatic test echo" | delete
X    pick -i -t "owner-post" | pick -i -s "unknown mailer" | delete
X    pick -i -s "usenet disk space report" | delete
X    pick -i -s "very old news articles" | delete
X    pick -i -s "uucp map for" | delete
X    # Wipe out some uninteresting daily stats
X    pick -i -s "the maid was here." | delete
X    pick -i -s "daily accounting" | delete
X    pick -i -t netsurvey | delete
X    # Get rid of these things for good.  This isn't essential, but
X    # avoids complexity in the later "pick" commands.
X    update
X    # Save anything "to" or "cc" to mush-users in that folder.
X    pick -i -t mush-users | save +mush-users
X    pick -i -h cc mush-users | save +mush-users
X    # Also save interesting daily stat mail and generic root mail
X    pick -i -f root | pick -i -s stats | save +stats
X    pick -i -f root | pick -i -s report | save +stats
X    pick -i -f uucp | pick -i -s report | save +stats
X    pick -i -f root | pick -i -s summary | save +stats
X    pick -i -f root | pick -i -s munge | save +stats
X    pick -i -t root | save +root
X    # Again, make the changes permanent.  Saved mail gets deleted.
X    # This won't work if you have $keepsave set.
X    update
X
X    # Make sure we don't burst again needlessly.
X    set first_time=0
X
X    # Stop sourcing here.  Otherwise, we'd change folders without
X    # handling the mail left in the system mailbox.
X    exit
endif
X
# Finally, handle stepping through the folders one by one.  This has been
# set up for sendmail, where the system mailbox is /usr/spool/mail/$USER,
# but could easily be modified for other mailers.
#
# $thisfolder:t returns the tail only of the folder name.
X
if $thisfolder:t == $USER
X	folder +stats
X	exit
endif
X
if $thisfolder:t == stats
X	folder +mush-users
X	exit
endif
X
if $thisfolder:t == mush-users
X	folder +root
X	exit
endif
X
# Default back to the system mailbox
folder %
X
# End of advanced.mushrc
SHAR_EOF
chmod 0644 advanced.mushrc ||
echo 'restore of advanced.mushrc failed'
Wc_c="`wc -c < 'advanced.mushrc'`"
test 4976 -eq "$Wc_c" ||
	echo 'advanced.mushrc: original size 4976, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= bind.c ==============
if test -f 'bind.c' -a X"$1" != X"-c"; then
	echo 'x - skipping bind.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting bind.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'bind.c' &&
/* bind.c */
X
#include "bindings.h"
#include "mush.h"
X
extern char *c_macro();
static un_bind();
X
struct cmd_map *cmd_map, *line_map, *bang_map;
X
/*
X * Bindings are added here in REVERSE of the order that
X * they will be displayed!  Display order is based on a
X * guess about the frequency of use and (to a lesser
X * extent) how hard they are to remember.
X *
X * The user's own new bindings, if any, will be displayed
X * before any of these default bindings.
X */
init_bindings()
{
#ifdef CURSES
X    /* Help gets displayed last */
X    add_bind("?", C_HELP, NULL, &cmd_map);
X    add_bind("V", C_VERSION, NULL, &cmd_map);
X
X    /* Miscellaneous shell commands */
X    add_bind("%", C_CHDIR, NULL, &cmd_map);
X    add_bind("|", C_PRINT_MSG, NULL, &cmd_map);
X    add_bind("!", C_SHELL_ESC, NULL, &cmd_map);
X    add_bind(":", C_CURSES_ESC, NULL, &cmd_map);
X
X    /* Mush customization commands */
X    /* NOTE: No default C_MACRO bindings */
X    add_bind(")", C_SAVEOPTS, NULL, &cmd_map);
X    add_bind("(", C_SOURCE, NULL, &cmd_map);
X    add_bind("&!", C_MAP_BANG, NULL, &cmd_map);
X    add_bind("&:", C_MAP, NULL, &cmd_map);
X    add_bind("&&", C_BIND_MACRO, NULL, &cmd_map);
X    add_bind("v", C_VAR_SET, NULL, &cmd_map);
X    add_bind("i", C_IGNORE, NULL, &cmd_map);
X    add_bind("h", C_OWN_HDR, NULL, &cmd_map);
X    add_bind("B", C_UNBIND, NULL, &cmd_map);
X    add_bind("b", C_BIND, NULL, &cmd_map);
X    add_bind("a", C_ALIAS, NULL, &cmd_map);
X
X    /* Display modification commands */
X    add_bind("\022", C_REVERSE, NULL, &cmd_map);	/* ^R */
X    add_bind("\014", C_REDRAW, NULL, &cmd_map);		/* ^L */
X    add_bind("Z", C_PREV_SCREEN, NULL, &cmd_map);
X    add_bind("z", C_NEXT_SCREEN, NULL, &cmd_map);
X
X    /* Searching and sorting commands */
X    add_bind("\016", C_CONT_SEARCH, NULL, &cmd_map);	/* ^N */
X    add_bind("\037", C_PREV_SEARCH, NULL, &cmd_map);	/* ^/ */
X    add_bind("/", C_NEXT_SEARCH, NULL, &cmd_map);
X    add_bind("O", C_REV_SORT, NULL, &cmd_map);
X    add_bind("o", C_SORT, NULL, &cmd_map);
X
X    /* Ways to get out */
X    add_bind("X", C_EXIT_HARD, NULL, &cmd_map);
X    add_bind("x", C_EXIT, NULL, &cmd_map);
X    add_bind("Q", C_QUIT_HARD, NULL, &cmd_map);
X    add_bind("q", C_QUIT, NULL, &cmd_map);
X
X    /* Folder modification commands */
X    add_bind("\025", C_UPDATE, NULL, &cmd_map);		/* ^U */
X    add_bind("\020", C_PRESERVE, NULL, &cmd_map);	/* ^P */
X    add_bind("*", C_MARK_MSG, NULL, &cmd_map);
X    add_bind("W", C_WRITE_LIST, NULL, &cmd_map);
X    add_bind("w", C_WRITE_MSG, NULL, &cmd_map);
X    add_bind("U", C_UNDEL_LIST, NULL, &cmd_map);
X    add_bind("u", C_UNDEL_MSG, NULL, &cmd_map);
X    add_bind("S", C_SAVE_LIST, NULL, &cmd_map);
X    add_bind("s", C_SAVE_MSG, NULL, &cmd_map);
X    add_bind("f", C_FOLDER, NULL, &cmd_map);
X    add_bind("D", C_DELETE_LIST, NULL, &cmd_map);
X    add_bind("d", C_DELETE_MSG, NULL, &cmd_map);
X    add_bind("C", C_COPY_LIST, NULL, &cmd_map);
X    add_bind("c", C_COPY_MSG, NULL, &cmd_map);
X
X    /* Cursor movement and message selection */
X    add_bind("g", C_GOTO_MSG, NULL, &cmd_map);
X    add_bind("}", C_BOTTOM_PAGE, NULL, &cmd_map);
X    add_bind("{", C_TOP_PAGE, NULL, &cmd_map);
X    add_bind("$", C_LAST_MSG, NULL, &cmd_map);
X    add_bind("^", C_FIRST_MSG, NULL, &cmd_map);
X    add_bind("\013",C_PREV_MSG, NULL, &cmd_map);	/* ^K */
X    add_bind("\012", C_NEXT_MSG, NULL, &cmd_map);	/* ^J */
X    add_bind("-",C_PREV_MSG, NULL, &cmd_map);
X    add_bind("+",C_NEXT_MSG, NULL, &cmd_map);
X    add_bind("K", C_PREV_MSG, NULL, &cmd_map);
X    add_bind("k", C_PREV_MSG, NULL, &cmd_map);
X    add_bind("J", C_NEXT_MSG, NULL, &cmd_map);
X    add_bind("j", C_NEXT_MSG, NULL, &cmd_map);
X
X    /* Mail-sending commands */
X    add_bind("R", C_REPLY_ALL, NULL, &cmd_map);
X    add_bind("r", C_REPLY_SENDER, NULL, &cmd_map);
X    add_bind("M", C_MAIL_FLAGS, NULL, &cmd_map);
X    add_bind("m", C_MAIL, NULL, &cmd_map);
X
X    /* Mail-reading commands */
X    add_bind(".", C_DISPLAY_MSG, NULL, &cmd_map);
X    add_bind("T", C_TOP_MSG, NULL, &cmd_map);
X    add_bind("t", C_DISPLAY_MSG, NULL, &cmd_map);
X    add_bind("p", C_DISPLAY_MSG, NULL, &cmd_map);
X    add_bind("n", C_DISPLAY_NEXT, NULL, &cmd_map);
X
#endif /* CURSES */
}
X
/* Bindable function names.
X *  Most of these can't be used if CURSES is not defined,
X *  but help and lookups get confused if they aren't all here.
X */
struct cmd_map map_func_names[] = {
X    /* These MUST be in numerical order; see bindings.h */
X    { C_NULL,		"no-op",		NULL, NULL_MAP },
X    { C_GOTO_MSG,	"goto-msg",		NULL, NULL_MAP },
X    { C_WRITE_LIST,	"write-list",		NULL, NULL_MAP },
X    { C_WRITE_MSG,	"write",		NULL, NULL_MAP },
X    { C_SAVE_LIST,	"save-list",		NULL, NULL_MAP },
X    { C_SAVE_MSG,	"save",			NULL, NULL_MAP },
X    { C_COPY_LIST,	"copy-list",		NULL, NULL_MAP },
X    { C_COPY_MSG,	"copy",			NULL, NULL_MAP },
X    { C_DELETE_LIST,	"delete-list",		NULL, NULL_MAP },
X    { C_DELETE_MSG,	"delete",		NULL, NULL_MAP },
X    { C_UNDEL_LIST,	"undelete-list",	NULL, NULL_MAP },
X    { C_UNDEL_MSG,	"undelete",		NULL, NULL_MAP },
X    { C_REDRAW,		"redraw",		NULL, NULL_MAP },
X    { C_REVERSE,	"reverse-video",	NULL, NULL_MAP },
X    { C_NEXT_MSG,	"next-msg",		NULL, NULL_MAP },
X    { C_PREV_MSG,	"back-msg",		NULL, NULL_MAP },
X    { C_FIRST_MSG,	"first-msg",		NULL, NULL_MAP },
X    { C_LAST_MSG,	"last-msg",		NULL, NULL_MAP },
X    { C_TOP_PAGE,	"top-page",		NULL, NULL_MAP },
X    { C_BOTTOM_PAGE,	"bottom-page",		NULL, NULL_MAP },
X    { C_NEXT_SCREEN,	"screen-next",		NULL, NULL_MAP },
X    { C_PREV_SCREEN,	"screen-back",		NULL, NULL_MAP },
X    { C_SOURCE,		"source",		NULL, NULL_MAP },
X    { C_SAVEOPTS,	"saveopts",		NULL, NULL_MAP },
X    { C_NEXT_SEARCH,	"search-next",		NULL, NULL_MAP },
X    { C_PREV_SEARCH,	"search-back",		NULL, NULL_MAP },
X    { C_CONT_SEARCH,	"search-again",		NULL, NULL_MAP },
X    { C_PRESERVE,	"preserve",		NULL, NULL_MAP },
X    { C_REV_SORT,	"sort-reverse",		NULL, NULL_MAP },
X    { C_SORT,		"sort",			NULL, NULL_MAP },
X    { C_QUIT_HARD,	"quit!",		NULL, NULL_MAP },
X    { C_QUIT,		"quit",			NULL, NULL_MAP },
X    { C_EXIT_HARD,	"exit!",		NULL, NULL_MAP },
X    { C_EXIT,		"exit",			NULL, NULL_MAP },
X    { C_UPDATE,		"update",		NULL, NULL_MAP },
X    { C_FOLDER,		"folder",		NULL, NULL_MAP },
X    { C_SHELL_ESC,	"shell-escape",		NULL, NULL_MAP },
X    { C_CURSES_ESC,	"line-mode",		NULL, NULL_MAP },
X    { C_PRINT_MSG,	"lpr",			NULL, NULL_MAP },
X    { C_CHDIR,		"chdir",		NULL, NULL_MAP },
X    { C_VAR_SET,	"variable",		NULL, NULL_MAP },
X    { C_IGNORE,		"ignore",		NULL, NULL_MAP },
X    { C_ALIAS,		"alias",		NULL, NULL_MAP },
X    { C_OWN_HDR,	"my-hdrs",		NULL, NULL_MAP },
X    { C_VERSION,	"version",		NULL, NULL_MAP },
X    { C_MAIL_FLAGS,	"mail-flags",		NULL, NULL_MAP },
X    { C_MAIL,		"mail",			NULL, NULL_MAP },
X    { C_REPLY_ALL,	"reply-all",		NULL, NULL_MAP },
X    { C_REPLY_SENDER,	"reply",		NULL, NULL_MAP },
X    { C_DISPLAY_NEXT,	"display-next",		NULL, NULL_MAP },
X    { C_DISPLAY_MSG,	"display",		NULL, NULL_MAP },
X    { C_TOP_MSG,	"top",			NULL, NULL_MAP },
X    { C_BIND_MACRO,	"bind-macro",		NULL, NULL_MAP },
X    { C_BIND,		"bind",			NULL, NULL_MAP },
X    { C_UNBIND,		"unbind",		NULL, NULL_MAP },
X    { C_MAP_BANG,	"map!",			NULL, NULL_MAP },
X    { C_MAP,		"map",			NULL, NULL_MAP },
X    { C_MACRO,		"macro",		NULL, NULL_MAP },
X    { C_MARK_MSG,	"mark",			NULL, NULL_MAP },
X    /* C_HELP Must be the last one! */
X    { C_HELP,		"help",			NULL, NULL_MAP }
};
X
#ifdef CURSES
X
/*
X * getcmd() is called from curses mode only.  It waits for char input from
X * the user via m_getchar() (which means that a macro could provide input)
X * and then compares the chars input against the "bind"ings set up by the
X * user (or the defaults).  For example, 'j' could bind to "next msg" which
X * is interpreted by the big switch statement in curses_command() (curses.c).
X * getcmd() returns the int-value of the curses command the input is "bound"
X * to.  If the input is unrecognized, C_NULL is returned (curses_command()
X * might require some cleanup, so this is valid, too).
X *
X * Since the input could originate from a macro rather than the terminal,
X * check to see if this is the case and search for a '[' char which indicates
X * that there is a curses command or other "long" command to be executed.
X */
getcmd()
{
X    char 		buf[MAX_BIND_LEN * 3];
X    register int 	c, m, match;
X    register char	*p = buf;
X    register struct cmd_map *list;
X
X    bzero(buf, MAX_BIND_LEN);
X    active_cmd = NULL_MAP;
X    c = m_getchar();
X    /* If user did job control (^Z), then the interrupt flag will be
X     * set.  Be sure it's unset before continuing.
X     */
X    turnoff(glob_flags, WAS_INTR);
X    if (isdigit(c)) {
X	buf[0] = c;
X	buf[1] = '\0';
X	Ungetstr(buf); /* So mac_flush can clear on error */
X	return C_GOTO_MSG;
X    }
X    for (;;) {
X	if (ison(glob_flags, IN_MACRO) && c == MAC_LONG_CMD)
X	    return long_mac_cmd(c, TRUE);
X	else
X	    *p++ = c;
X	m = 0;
X	for (list = cmd_map; list; list = list->m_next) {
X	    if ((match = prefix(buf, list->m_str)) == MATCH) {
X		if (debug)
X		    print("\"%s\" ",
X			ctrl_strcpy(buf,
X				    map_func_names[list->m_cmd].m_str,
X				    TRUE));
X		if (list->m_cmd == C_MACRO) {
X		    curs_macro(list->x_str);
X		    return getcmd();
X		}
X		active_cmd = list;
X		return (int)list->m_cmd;
X	    } else if (match != NO_MATCH)
X		m++;
X	}
X	if (m == 0) {
X	    if (debug) {
X		char tmp[sizeof buf];
X		print("No binding for \"%s\" found.",
X		    ctrl_strcpy(tmp, buf, TRUE));
X	    }
X	    return C_NULL;
X	}
X	c = m_getchar();
X    }
}
X
#endif /* CURSES */
X
/*
X * bind_it() is used to set or unset bind, map and map! settings.
X * bind is used to accelerate curses commands by mapping key sequences
X * to curses commands.  map is used to accelerate command mode keysequences
X * by simulating stdin.  map! is the same, but used when in compose mode.
X *
X * bind_it() doesn't touch messages; return -1 for curses mode.
X * return -2 to have curses command set CNTD_CMD to prevent screen refresh
X * to allow user to read output in case of multiple lines.
X *
X * Since this routine deals with a lot of binding and unbinding of things
X * like line-mode "map"s and is interactive (calls Getstr()), be very careful
X * not to allow expansions during interaction.
X */
bind_it(len, argv)
char **argv;
{
X    char string[MAX_BIND_LEN], buf[256], *name = NULL;
X    char *rawstr; /* raw format of string (ptr to string if no argv avail) */
X    char ascii[MAX_BIND_LEN*2]; /* printable ascii version of string */
X    register int x;
X    SIGRET (*oldint)(), (*oldquit)();
X    struct cmd_map **map_list;
X    int unbind = (argv && **argv == 'u');
X    int map = 0, is_bind_macro = 0;
X    int ret = 0 - iscurses; /* return value */
X
X    if (argv && !strcmp(name = *argv, "bind-macro"))
X	is_bind_macro++;
X
X    if (map = (argv && (!strcmp(name, "map!") || !strcmp(name, "unmap!"))))
X	map_list = &bang_map;
X    else if (map = (argv && (!strcmp(name, "map") || !strcmp(name, "unmap"))))
X	map_list = &line_map;
X    else
X	map_list = &cmd_map;
X
X    if (argv && *++argv && !strcmp(*argv, "-?"))
X	/* Subtract ret and iscurses to signal output */
X	return help(0, unbind? name+2 : name, cmd_help) - ret - iscurses;
X
X    if (iscurses)
X	on_intr();
X
X    if (unbind) {
X	if (!*argv) {
X	    char savec = complete;
X	    complete = 0;
X	    print("%s what? ", name);
X	    len = Getstr(buf, sizeof buf, 0);
X	    complete = savec;
X	    if (len <= 0) {
X		if (iscurses)
X		    off_intr();
X		return -1;
X	    }
X	    rawstr = m_xlate(buf);
X	} else
X	    rawstr = m_xlate(*argv);
X	if (!un_bind(rawstr, map_list)) {
X	    (void) ctrl_strcpy(ascii, rawstr, TRUE);
X	    print("\"%s\" isn't bound to a command.\n", ascii);
X	}
X	if (iscurses)
X	    off_intr();
X	return ret;
X    }
X    if (argv && *argv) {
X	rawstr = m_xlate(*argv);
X	(void) ctrl_strcpy(ascii, rawstr, TRUE);
X	if (!*++argv) {
X	    /*
X	     * determine whether "argv" references a "map" or a "bind"
X	     */
X	    int binding = c_bind(rawstr, *map_list);
X	    if (binding == C_MACRO) {
X		char *mapping = c_macro(NULL, rawstr, *map_list);
X		if (mapping) {
X		    print("\"%s\" is mapped to ", ascii);
X		    print_more("\"%s\".\n",
X			ctrl_strcpy(buf, mapping, FALSE));
X		} else
X		    print("\"%s\" isn't mapped.\n", ascii);
X	    } else if (binding)
X		print("\"%s\" is %s to \"%s\".\n", ascii,
X		    map? "mapped" : "bound", map_func_names[binding].m_str);
X	    else if (map)
X		print("\"%s\" isn't mapped.\n", ascii);
X	    else
X		print("\"%s\" isn't bound to a command.\n", ascii);
X	    if (iscurses)
X		off_intr();
X	    return ret;
X	}
X    } else {
X	char savec = complete;
X	complete = 0;
X	print("%s [<CR>=all, -?=help]: ", name);
X	len = Getstr(string, MAX_BIND_LEN-1, 0);
X	complete = savec;
X	if (len == 0) {
X	    int add_to_ret = iscurses;
#ifdef CURSES
X	    if (iscurses)
X		move(LINES-1, 0), refresh();
#endif
X	    if (map || is_bind_macro)
X		add_to_ret = !c_macro(name, NULL, *map_list);
X	    else
X		add_to_ret = !c_bind(NULL, *map_list);
X	    if (iscurses)
X		off_intr();
X	    /* signal CTND_CMD if there was output */
X	    return ret - add_to_ret;
X	}
X	if (len < 0) {
X	    if (iscurses)
X		off_intr();
X	    return ret;
X	}
X	rawstr = m_xlate(string);
X	(void) ctrl_strcpy(ascii, rawstr, TRUE);
X    }
X    /* if a binding was given on the command line */
X    if (argv && *argv && !map)
X	if (is_bind_macro)
X	    (void) strcpy(buf, "macro");
X	else
X	    (void) strcpy(buf, *argv++);
X    else {
X	/* at this point, "rawstr" and "ascii" should both be set */
X	int binding;
X
X	if (!strcmp(ascii, "-?")) {
X	    if (iscurses)
X		clr_bot_line();
X	    ret -= help(0, name, cmd_help);
X	    if (iscurses)
X		off_intr();
X	    /* Subtract iscurses to signal CNTD_CMD */
X	    return ret - iscurses;
X	}
X
X	if (!map && !is_bind_macro) {
X	    binding = c_bind(rawstr, *map_list);
X
X	    for (len = 0; len == 0; ) {
X		print("\"%s\" = <%s>: New binding [<CR> for list]: ",
X		    ascii, (binding? map_func_names[binding].m_str : "unset"));
X		len = Getstr(buf, sizeof buf, 0);
X		if (iscurses)
X		    clr_bot_line();
X		/* strip any trailing whitespace */
X		if (len > 0)
X		    len = no_newln(buf) - buf;
X		if (len == 0) {
X		    (void) do_pager(NULL, TRUE);
X		    if (iscurses)
X			putchar('\n');
X		    for (x = 1; x <= C_HELP; x++) {
X			if (!(x % 4))
X			    if (do_pager("\n", FALSE) == EOF)
X				break;
X			(void) do_pager(sprintf(buf, "%-15.15s  ",
X					    map_func_names[x].m_str), FALSE);
X		    }
X		    (void) do_pager("\n", FALSE);
X		    (void) do_pager(NULL, FALSE);
X		    ret -= iscurses;
X		}
X	    }
X	} else /* map */
X	    (void) strcpy(buf, "macro"), len = 5;
X	/* if list was printed, ret < -1 -- tells CNTD_CMD to be set and
X	 * prevents screen from being refreshed (lets user read output
X	 */
X	if (len == -1) {
X	    if (iscurses)
X		off_intr();
X	    return ret;
X	}
X    }
X    for (x = 1; x <= C_HELP; x++) {
X	if (prefix(buf, map_func_names[x].m_str) == MATCH) {
X	    int add_to_ret;
X	    if (debug)
X		print("\"%s\" will execute \"%s\".\n", ascii, buf);
X	    if (map_func_names[x].m_cmd == C_MACRO) {
X		if (argv && *argv) {
X		    (void) argv_to_string(buf, argv);
X		    (void) m_xlate(buf); /* Convert buf to raw chars */
X		    add_to_ret =
X			do_bind(rawstr, map_func_names[x].m_cmd, buf, map_list);
X		} else {
X		    char exp[MAX_MACRO_LEN*2]; /* printable expansion */
X		    char *mapping = c_macro(NULL, rawstr, *map_list);
X
X		    if (mapping)
X			(void) ctrl_strcpy(exp, mapping, TRUE);
X		    print("\"%s\" = <%s>", ascii, mapping ? exp : "unset");
X		    putchar('\n'), print("New macro: ");
X		    ret -= iscurses; /* To signal screen messed up */
X		    /* we are done with buf, so we can trash over it */
X		    len = Getstr(buf, MAX_MACRO_LEN, 0);
X		    if (len > 0) {
X			if (iscurses)
X			    clr_bot_line();
X			(void) m_xlate(buf); /* Convert buf to raw chars */
X			add_to_ret =
X			    do_bind(rawstr, C_MACRO, buf, map_list);
X			if (debug) {
X			    (void) ctrl_strcpy(exp, buf, TRUE);
X			    print("\"%s\" will execute \"%s\".\n", ascii, exp);
X			}
X		    } else if (len < 0) {
X			if (iscurses)
X			    off_intr();
X			return ret;
X		    } else
X			print("Can't bind to null macro"), putchar('\n');
X		}
X	    } else /* not a macro */ {
X		(void) argv_to_string(buf, argv);
X		add_to_ret =
X		    do_bind(rawstr, map_func_names[x].m_cmd, buf, map_list);
X	    }
X	    /* if do_bind had no errors, it returned -1.  If we already
X	     * messed up the screen, then ret is less than -1.  return the
X	     * lesser of the two to make sure that CNTD_CMD gets set right
X	     */
X	    if (iscurses)
X		off_intr();
X	    return min(add_to_ret, ret);
X	}
X    }
X    print("\"%s\": Unknown function.\n", buf);
X    if (iscurses)
X	off_intr();
X    return ret;
}
X
/*
X * print current key to command bindings if "str" is NULL.
X * else return the integer "m_cmd" which the str is bound to.
X */
c_bind(str, opts)
register char *str;
register struct cmd_map *opts;
{
X    register int    incurses = iscurses;
X
X    if (!str) {
X	if (!opts) {
X	    print("No command bindings.\n");
X	    return C_ERROR;
X	}
X	if (incurses)
X	    clr_bot_line(), iscurses = FALSE;
X	(void) do_pager(NULL, TRUE);
X	(void) do_pager("Current key to command bindings:\n", FALSE);
X	(void) do_pager("\n", FALSE);
X    }
X
X    for (; opts; opts = opts->m_next) {
X	char buf[BUFSIZ], buf2[MAX_BIND_LEN], exp[MAX_MACRO_LEN*2], *xp;
X	if (!str) {
X	    (void) ctrl_strcpy(buf2, opts->m_str, FALSE);
X	    if ((xp = opts->x_str) && opts->m_cmd == C_MACRO)
X		xp = ctrl_strcpy(exp, opts->x_str, TRUE);
X	    if (do_pager(sprintf(buf, "%s\t%-15.15s %s\n",
X			 buf2, map_func_names[opts->m_cmd].m_str,
X			 xp? xp : ""),
X			 FALSE) == EOF)
X		break;
X	} else
X	    if (strcmp(str, opts->m_str))
X		continue;
X	    else
X		return opts->m_cmd;
X    }
X
X    iscurses = incurses;
X    if (!str)
X	(void) do_pager(NULL, FALSE);
X    return C_NULL;
}
X
/*
X * Doesn't touch messages, but changes macros: return -1.
X * Error output causes return < -1.
X *  args is currently the execute string of a macro mapping, but may be
X *  used in the future as an argument string for any curses command.
X */
do_bind(str, func, args, map_list)
register char *str, *args;
struct cmd_map **map_list;
long func;
{
X    register int ret = -1;
X    register struct cmd_map *list;
X    int match;
X
X    if (func == C_MACRO && !check_mac_bindings(args))
X	--ret;
X    (void) un_bind(str, map_list);
X    for (list = *map_list; list; list = list->m_next)
X	if ((match = prefix(str, list->m_str)) != NO_MATCH) {
X	    ret--;
X	    switch (match) {
X		case MATCH:
X		    puts("Something impossible just happened.");
X		when A_PREFIX_B:
X		    wprint("Warning: \"%s\" prefixes \"%s\" (%s)\n", str,
X			list->m_str, map_func_names[list->m_cmd].m_str);
X		when B_PREFIX_A:
X		    wprint("Warning: \"%s\" (%s) prefixes: \"%s\"\n",
X			list->m_str, map_func_names[list->m_cmd].m_str, str);
X	    }
X	}
X    add_bind(str, func, args, map_list);
X    /* errors decrement ret.  If ret returns less than -1, CNTD_CMD is set
X     * and no redrawing is done so user can see the warning signs
X     */
X    return ret;
}
X
/*
X * add a binding to a list.  This may include "map"s or other mappings since
X * the map_list argument can control that.  The "func" is an int defined in
X * bindings.h ... the "str" passed is the string the user would have to type
X * to get the macro/map/binding expanded.  This must in in raw format: no
X * \n's to mean \015.  Convert first using m_xlate().
X */
add_bind(str, func, args, map_list)
register char *str, *args;
struct cmd_map **map_list;
long func;
{
X    register struct cmd_map *tmp;
X
X    if (!str || !*str)
X	return;
X
X    /* now make a new option struct and set fields */
X    if (!(tmp = (struct cmd_map *)calloc((unsigned)1,sizeof(struct cmd_map)))) {
X	error("calloc");
X	return;
X    }
X    tmp->m_next = *map_list;
X    *map_list = tmp;
X
X    tmp->m_str = savestr(str);
X    tmp->m_cmd = func; /* strdup handles the NULL case */
X    if (args && *args)
X	tmp->x_str = savestr(args);
X    else
X	tmp->x_str = NULL;
}
X
static
un_bind(p, map_list)
register char *p;
struct cmd_map **map_list;
{
X    register struct cmd_map *list = *map_list, *tmp;
X
X    if (!list || !*list->m_str || !p || !*p)
X	return 0;
X
X    if (!strcmp(p, (*map_list)->m_str)) {
X	*map_list = (*map_list)->m_next;
X	xfree (list->m_str);
X	if (list->x_str)
X	    xfree (list->x_str);
X	xfree((char *)list);
X	return 1;
X    }
X    for ( ; list->m_next; list = list->m_next)
X	if (!strcmp(p, list->m_next->m_str)) {
X	    tmp = list->m_next;
X	    list->m_next = list->m_next->m_next;
X	    xfree (tmp->m_str);
X	    if (tmp->x_str)
X		xfree (tmp->x_str);
X	    xfree ((char *)tmp);
X	    return 1;
X	}
X    return 0;
}
X
prefix(a, b)
register char *a, *b;
{
X    if (!a || !b)
X	return NO_MATCH;
X
X    while (*a && *b && *a == *b)
X	a++, b++;
X    if (!*a && !*b)
X	return MATCH;
X    if (!*a && *b)
X	return A_PREFIX_B;
X    if (*a && !*b)
X	return B_PREFIX_A;
X    return NO_MATCH;
}
SHAR_EOF
chmod 0644 bind.c ||
echo 'restore of bind.c failed'
Wc_c="`wc -c < 'bind.c'`"
test 20663 -eq "$Wc_c" ||
	echo 'bind.c: original size 20663, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= bindings.h ==============
if test -f 'bindings.h' -a X"$1" != X"-c"; then
	echo 'x - skipping bindings.h (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting bindings.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'bindings.h' &&
/* bindings.h  -- command bindings */
X
#define MAX_BIND_LEN 20   /* max length a string can be to bind to a command */
#define MAX_MACRO_LEN 256 /* max length of a macro bound to a command */
X
/* to see if a key sequence matches, prefixes or misses a set binding */
#define NO_MATCH	0
#define MATCH		1
#define A_PREFIX_B	2
#define B_PREFIX_A	3
X
/*
X * Constants to define curses mode functions.
X */
#ifdef NULL_MAP
#undef NULL_MAP
#endif /* NULL_MAP */
#define NULL_MAP	(struct cmd_map *)0
X
#define C_ERROR		(-1L)
#define C_NULL		0L
#define C_GOTO_MSG	1L
#define C_WRITE_LIST	2L
#define C_WRITE_MSG	3L
#define C_SAVE_LIST	4L
#define C_SAVE_MSG	5L
#define C_COPY_LIST	6L
#define C_COPY_MSG	7L
#define C_DELETE_LIST	8L
#define C_DELETE_MSG	9L
#define C_UNDEL_LIST	10L
#define C_UNDEL_MSG	11L
#define C_REDRAW	12L
#define C_REVERSE	13L
#define C_NEXT_MSG	14L
SHAR_EOF
true || echo 'restore of bindings.h failed'
fi
echo 'End of  part 3'
echo 'File bindings.h is continued in part 4'
echo 4 > _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.