Blob Blame History Raw
/*
 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 */
/*
 * Copyright (c) 1980, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
#ifdef	DOSCCS
static char sccsid[] = "@(#)aux.c	2.83 (gritter) 3/4/06";
#endif
#endif /* not lint */

#include "rcv.h"
#include "extern.h"
#include <sys/stat.h>
#include <utime.h>
#include <time.h>
#include <termios.h>
#include <ctype.h>
#ifdef	HAVE_WCTYPE_H
#include <wctype.h>
#endif	/* HAVE_WCTYPE_H */
#ifdef	HAVE_WCWIDTH
#include <wchar.h>
#endif	/* HAVE_WCWIDTH */
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>

#include "md5.h"

/*
 * Mail -- a mail program
 *
 * Auxiliary functions.
 */

/*
 * Return a pointer to a dynamic copy of the argument.
 */
char *
savestr(const char *str)
{
	char *new;
	int size = strlen(str) + 1;

	if ((new = salloc(size)) != NULL)
		memcpy(new, str, size);
	return new;
}

/*
 * Make a copy of new argument incorporating old one.
 */
char *
save2str(const char *str, const char *old)
{
	char *new;
	int newsize = strlen(str) + 1;
	int oldsize = old ? strlen(old) + 1 : 0;

	if ((new = salloc(newsize + oldsize)) != NULL) {
		if (oldsize) {
			memcpy(new, old, oldsize);
			new[oldsize - 1] = ' ';
		}
		memcpy(new + oldsize, str, newsize);
	}
	return new;
}

char *
savecat(const char *s1, const char *s2)
{
	const char	*cp;
	char	*ns, *np;

	np = ns = salloc(strlen(s1) + strlen(s2) + 1);
	for (cp = s1; *cp; cp++)
		*np++ = *cp;
	for (cp = s2; *cp; cp++)
		*np++ = *cp;
	*np = '\0';
	return ns;
}

#include <stdarg.h>

#ifndef	HAVE_SNPRINTF
/*
 * Lazy vsprintf wrapper.
 */
int
snprintf(char *str, size_t size, const char *format, ...)
{
	va_list ap;
	int ret;

	va_start(ap, format);
	ret = vsprintf(str, format, ap);
	va_end(ap);
	return ret;
}
#endif	/* !HAVE_SNPRINTF */

/*
 * Announce a fatal error and die.
 */
void
panic(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	fprintf(stderr, catgets(catd, CATSET, 1, "panic: "));
	vfprintf(stderr, format, ap);
	va_end(ap);
	fprintf(stderr, catgets(catd, CATSET, 2, "\n"));
	fflush(stderr);
	abort();
}

void
holdint(void)
{
	sigset_t	set;

	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	sigprocmask(SIG_BLOCK, &set, NULL);
}

void
relseint(void)
{
	sigset_t	set;

	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	sigprocmask(SIG_UNBLOCK, &set, NULL);
}

/*
 * Touch the named message by setting its MTOUCH flag.
 * Touched messages have the effect of not being sent
 * back to the system mailbox on exit.
 */
void 
touch(struct message *mp)
{

	mp->m_flag |= MTOUCH;
	if ((mp->m_flag & MREAD) == 0)
		mp->m_flag |= MREAD|MSTATUS;
}

/*
 * Test to see if the passed file name is a directory.
 * Return true if it is.
 */
int 
is_dir(char *name)
{
	struct stat sbuf;

	if (stat(name, &sbuf) < 0)
		return(0);
	return(S_ISDIR(sbuf.st_mode));
}

/*
 * Count the number of arguments in the given string raw list.
 */
int 
argcount(char **argv)
{
	char **ap;

	for (ap = argv; *ap++ != NULL;)
		;	
	return ap - argv - 1;
}

/*
 * Copy a string, lowercasing it as we go.
 */
void 
i_strcpy(char *dest, const char *src, int size)
{
	char *max;

	max=dest+size-1;
	while (dest<=max) {
		*dest++ = lowerconv(*src & 0377);
		if (*src++ == '\0')
			break;
	}
}

char *
i_strdup(const char *src)
{
	int	sz;
	char	*dest;

	sz = strlen(src) + 1;
	dest = salloc(sz);
	i_strcpy(dest, src, sz);
	return dest;
}

/*
 * Convert a string to lowercase, in-place and with multibyte-aware.
 */
void 
makelow(char *cp)
{
#if defined (HAVE_MBTOWC) && defined (HAVE_WCTYPE_H)
	if (mb_cur_max > 1) {
		char	*tp = cp;
		wchar_t	wc;
		int	len;

		while (*cp) {
			len = mbtowc(&wc, cp, mb_cur_max);
			if (len < 0)
				*tp++ = *cp++;
			else {
				wc = towlower(wc);
				if (wctomb(tp, wc) == len)
					tp += len, cp += len;
				else
					*tp++ = *cp++;
			}
		}
	} else
#endif	/* HAVE_MBTOWC && HAVE_WCTYPE_H */
	{
		do
			*cp = tolower(*cp & 0377);
		while (*cp++);
	}
}

int 
substr(const char *str, const char *sub)
{
	const char	*cp, *backup;

	cp = sub;
	backup = str;
	while (*str && *cp) {
#if defined (HAVE_MBTOWC) && defined (HAVE_WCTYPE_H)
		if (mb_cur_max > 1) {
			wchar_t c, c2;
			int sz;

			if ((sz = mbtowc(&c, cp, mb_cur_max)) < 0)
				goto singlebyte;
			cp += sz;
			if ((sz = mbtowc(&c2, str, mb_cur_max)) < 0)
				goto singlebyte;
			str += sz;
			c = towupper(c);
			c2 = towupper(c2);
			if (c != c2) {
				if ((sz = mbtowc(&c, backup, mb_cur_max)) > 0) {
					backup += sz;
					str = backup;
				} else
					str = ++backup;
				cp = sub;
			}
		} else
#endif	/* HAVE_MBTOWC && HAVE_WCTYPE_H */
		{
			int c, c2;

#if defined (HAVE_MBTOWC) && defined (HAVE_WCTYPE_H)
	singlebyte:
#endif	/* HAVE_MBTOWC && HAVE_WCTYPE_H */
			c = *cp++ & 0377;
			if (islower(c))
				c = toupper(c);
			c2 = *str++ & 0377;
			if (islower(c2))
				c2 = toupper(c2);
			if (c != c2) {
				str = ++backup;
				cp = sub;
			}
		}
	}
	return *cp == '\0';
}

char *
colalign(const char *cp, int col, int fill)
{
	int	n, sz;
	char	*nb, *np;

	np = nb = salloc(mb_cur_max * strlen(cp) + col + 1);
	while (*cp) {
#if defined (HAVE_MBTOWC) && defined (HAVE_WCWIDTH)
		if (mb_cur_max > 1) {
			wchar_t	wc;

			if ((sz = mbtowc(&wc, cp, mb_cur_max)) < 0) {
				n = sz = 1;
			} else {
				if ((n = wcwidth(wc)) < 0)
					n = 1;
			}
		} else
#endif	/* HAVE_MBTOWC && HAVE_WCWIDTH */
		{
			n = sz = 1;
		}
		if (n > col)
			break;
		col -= n;
		if (sz == 1 && spacechar(*cp&0377)) {
			*np++ = ' ';
			cp++;
		} else
			while (sz--)
				*np++ = *cp++;
	}
	if (fill)
		while (col-- > 0)
			*np++ = ' ';
	*np = '\0';
	return nb;
}

void
try_pager(FILE *fp)
{
	long	lines = 0;
	int	c;
	char	*cp;

	fflush(fp);
	rewind(fp);
	while ((c = getc(fp)) != EOF)
		if (c == '\n')
			lines++;
	rewind(fp);
	if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL &&
			lines > (*cp ? atol(cp) : scrnheight))
		run_command(get_pager(), 0, fileno(fp), -1, NULL, NULL, NULL);
	else
		while ((c = getc(fp)) != EOF)
			putchar(c);
}

/*
 * The following code deals with input stacking to do source
 * commands.  All but the current file pointer are saved on
 * the stack.
 */

static	int	ssp;			/* Top of file stack */
struct sstack {
	FILE	*s_file;		/* File we were in. */
	enum condition	s_cond;		/* Saved state of conditionals */
	int	s_loading;		/* Loading .mailrc, etc. */
#define	SSTACK	20
} sstack[SSTACK];

/*
 * Pushdown current input file and switch to a new one.
 * Set the global flag "sourcing" so that others will realize
 * that they are no longer reading from a tty (in all probability).
 */
int 
source(void *v)
{
	char **arglist = v;
	FILE *fi;
	char *cp;

	if ((cp = expand(*arglist)) == NULL)
		return(1);
	if ((fi = Fopen(cp, "r")) == NULL) {
		perror(cp);
		return(1);
	}
	if (ssp >= SSTACK - 1) {
		printf(catgets(catd, CATSET, 3,
					"Too much \"sourcing\" going on.\n"));
		Fclose(fi);
		return(1);
	}
	sstack[ssp].s_file = input;
	sstack[ssp].s_cond = cond;
	sstack[ssp].s_loading = loading;
	ssp++;
	loading = 0;
	cond = CANY;
	input = fi;
	sourcing++;
	return(0);
}

/*
 * Pop the current input back to the previous level.
 * Update the "sourcing" flag as appropriate.
 */
int 
unstack(void)
{
	if (ssp <= 0) {
		printf(catgets(catd, CATSET, 4,
					"\"Source\" stack over-pop.\n"));
		sourcing = 0;
		return(1);
	}
	Fclose(input);
	if (cond != CANY)
		printf(catgets(catd, CATSET, 5, "Unmatched \"if\"\n"));
	ssp--;
	cond = sstack[ssp].s_cond;
	loading = sstack[ssp].s_loading;
	input = sstack[ssp].s_file;
	if (ssp == 0)
		sourcing = loading;
	return(0);
}

/*
 * Touch the indicated file.
 * This is nifty for the shell.
 */
void 
alter(char *name)
{
	struct stat sb;
	struct utimbuf utb;

	if (stat(name, &sb))
		return;
	utb.actime = time((time_t *)0) + 1;
	utb.modtime = sb.st_mtime;
	utime(name, &utb);
}

/*
 * Examine the passed line buffer and
 * return true if it is all blanks and tabs.
 */
int 
blankline(char *linebuf)
{
	char *cp;

	for (cp = linebuf; *cp; cp++)
		if (!blankchar(*cp & 0377))
			return(0);
	return(1);
}

/*
 * Are any of the characters in the two strings the same?
 */
int 
anyof(char *s1, char *s2)
{

	while (*s1)
		if (strchr(s2, *s1++))
			return 1;
	return 0;
}

/*
 * Determine if as1 is a valid prefix of as2.
 * Return true if yep.
 */
int 
is_prefix(const char *as1, const char *as2)
{
	const char *s1, *s2;

	s1 = as1;
	s2 = as2;
	while (*s1++ == *s2)
		if (*s2++ == '\0')
			return(1);
	return(*--s1 == '\0');
}

char *
last_at_before_slash(const char *sp)
{
	const char	*cp;

	for (cp = sp; *cp; cp++)
		if (*cp == '/')
			break;
	while (cp > sp && *--cp != '@');
	return *cp == '@' ? (char *)cp : NULL;
}

enum protocol 
which_protocol(const char *name)
{
	register const char *cp;
	char	*np;
	size_t	sz;
	struct stat	st;
	enum protocol	p;

	if (name[0] == '%' && name[1] == ':')
		name += 2;
	for (cp = name; *cp && *cp != ':'; cp++)
		if (!alnumchar(*cp&0377))
			goto file;
	if (cp[0] == ':' && cp[1] == '/' && cp[2] == '/') {
		if (strncmp(name, "pop3://", 7) == 0)
			return PROTO_POP3;
		if (strncmp(name, "pop3s://", 8) == 0)
#ifdef	USE_SSL
			return PROTO_POP3;
#else	/* !USE_SSL */
			fprintf(stderr, catgets(catd, CATSET, 225,
					"No SSL support compiled in.\n"));
#endif	/* !USE_SSL */
		if (strncmp(name, "imap://", 7) == 0)
			return PROTO_IMAP;
		if (strncmp(name, "imaps://", 8) == 0)
#ifdef	USE_SSL
			return PROTO_IMAP;
#else	/* !USE_SSL */
			fprintf(stderr, catgets(catd, CATSET, 225,
					"No SSL support compiled in.\n"));
#endif	/* !USE_SSL */
		return PROTO_UNKNOWN;
	} else {
	file:	p = PROTO_FILE;
		np = ac_alloc((sz = strlen(name)) + 5);
		strcpy(np, name);
		if (stat(name, &st) == 0) {
			if (S_ISDIR(st.st_mode)) {
				strcpy(&np[sz], "/tmp");
				if (stat(np, &st) == 0 && S_ISDIR(st.st_mode)) {
					strcpy(&np[sz], "/new");
					if (stat(np, &st) == 0 &&
							S_ISDIR(st.st_mode)) {
						strcpy(&np[sz], "/cur");
						if (stat(np, &st) == 0 &&
							S_ISDIR(st.st_mode))
						    p = PROTO_MAILDIR;
					}
				}
			}
		} else {
			strcpy(&np[sz], ".gz");
			if (stat(np, &st) < 0) {
				strcpy(&np[sz], ".bz2");
				if (stat(np, &st) < 0) {
					if ((cp = value("newfolders")) != 0 &&
						strcmp(cp, "maildir") == 0)
					p = PROTO_MAILDIR;
				}
			}
		}
		ac_free(np);
		return p;
	}
}

const char *
protfile(const char *xcp)
{
	const char	*cp = xcp;
	int	state = 0;

	while (*cp) {
		if (cp[0] == ':' && cp[1] == '/' && cp[2] == '/') {
			cp += 3;
			state = 1;
		}
		if (cp[0] == '/' && state == 1)
			return &cp[1];
		if (cp[0] == '/')
			return xcp;
		cp++;
	}
	return cp;
}

char *
protbase(const char *cp)
{
	char	*n = salloc(strlen(cp) + 1);
	char	*np = n;

	while (*cp) {
		if (cp[0] == ':' && cp[1] == '/' && cp[2] == '/') {
			*np++ = *cp++;
			*np++ = *cp++;
			*np++ = *cp++;
		} else if (cp[0] == '/')
			break;
		else
			*np++ = *cp++;
	}
	*np = '\0';
	return n;
}

int 
disconnected(const char *file)
{
	char	*cp, *cq, *vp;
	int	vs, r;

	if (value("disconnected"))
		return 1;
	cp = protbase(file);
	if (strncmp(cp, "imap://", 7) == 0)
		cp += 7;
	else if (strncmp(cp, "imaps://", 8) == 0)
		cp += 8;
	else
		return 0;
	if ((cq = strchr(cp, ':')) != NULL)
		*cq = '\0';
	vp = ac_alloc(vs = strlen(cp) + 14);
	snprintf(vp, vs, "disconnected-%s", cp);
	r = value(vp) != NULL;
	ac_free(vp);
	return r;
}

unsigned 
pjw(const char *cp)
{
	unsigned	h = 0, g;

	cp--;
	while (*++cp) {
		h = (h << 4 & 0xffffffff) + (*cp&0377);
		if ((g = h & 0xf0000000) != 0) {
			h = h ^ g >> 24;
			h = h ^ g;
		}
	}
	return h;
}

long 
nextprime(long n)
{
	const long	primes[] = {
			509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
			131071, 262139, 524287, 1048573, 2097143, 4194301,
			8388593, 16777213, 33554393, 67108859, 134217689,
			268435399, 536870909, 1073741789, 2147483647
		};
	long	mprime = 7;
	int	i;

	for (i = 0; i < sizeof primes / sizeof *primes; i++)
		if ((mprime = primes[i]) >= (n < 65536 ? n*4 :
					n < 262144 ? n*2 : n))
			break;
	if (i == sizeof primes / sizeof *primes)
		mprime = n;	/* not so prime, but better than failure */
	return mprime;
}

#define	Hexchar(n)	((n)>9 ? (n)-10+'A' : (n)+'0')
#define	hexchar(n)	((n)>9 ? (n)-10+'a' : (n)+'0')

char *
strenc(const char *cp)
{
	char	*n, *np;

	np = n = salloc(strlen(cp) * 3 + 1);
	while (*cp) {
		if (alnumchar(*cp&0377) || *cp == '_' || *cp == '@' ||
				(np > n && (*cp == '.' || *cp == '-' ||
					    *cp == ':')))
			*np++ = *cp;
		else {
			*np++ = '%';
			*np++ = Hexchar((*cp&0xf0) >> 4);
			*np++ = Hexchar(*cp&0x0f);
		}
		cp++;
	}
	*np = '\0';
	return n;
}

char *
strdec(const char *cp)
{
	char	*n, *np;

	np = n = salloc(strlen(cp) + 1);
	while (*cp) {
		if (cp[0] == '%' && cp[1] && cp[2]) {
			*np = (int)(cp[1]>'9'?cp[1]-'A'+10:cp[1]-'0') << 4;
			*np++ |= cp[2]>'9'?cp[2]-'A'+10:cp[2]-'0';
			cp += 3;
		} else
			*np++ = *cp++;
	}
	*np = '\0';
	return n;
}

char *
md5tohex(const void *vp)
{
	char	*hex;
	const char	*cp = vp;
	int	i;

	hex = salloc(33);
	for (i = 0; i < 16; i++) {
		hex[2*i] = hexchar((cp[i]&0xf0) >> 4);
		hex[2*i+1] = hexchar(cp[i]&0x0f);
	}
	hex[32] = '\0';
	return hex;
}

char *
cram_md5_string(const char *user, const char *pass, const char *b64)
{
	struct str	in, out;
	char	digest[16], *cp, *sp, *rp, *xp;
	int	ss, rs;

	in.s = (char *)b64;
	in.l = strlen(in.s);
	mime_fromb64(&in, &out, 0);
	hmac_md5((unsigned char *)out.s, out.l,
			(unsigned char *)pass, strlen(pass),
			digest);
	free(out.s);
	xp = md5tohex(digest);
	sp = ac_alloc(ss = strlen(user) + strlen(xp) + 2);
	snprintf(sp, ss, "%s %s", user, xp);
	cp = strtob64(sp);
	ac_free(sp);
	rp = salloc(rs = strlen(cp) + 3);
	snprintf(rp, rs, "%s\r\n", cp);
	free(cp);
	return rp;
}

char *
getuser(void)
{
	char *line = NULL, *user;
	size_t linesize = 0;

	if (is_a_tty[0]) {
		fputs("User: ", stdout);
		fflush(stdout);
	}
	if (readline(stdin, &line, &linesize) == 0) {
		if (line)
			free(line);
		return NULL;
	}
	user = savestr(line);
	free(line);
	return user;
}

char *
getpassword(struct termios *otio, int *reset_tio, const char *query)
{
	struct termios	tio;
	char *line = NULL, *pass;
	size_t linesize = 0;
	int	i;

	if (is_a_tty[0]) {
		fputs(query ? query : "Password:", stdout);
		fflush(stdout);
		tcgetattr(0, &tio);
		*otio = tio;
		tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
		*reset_tio = 1;
		tcsetattr(0, TCSAFLUSH, &tio);
	}
	i = readline(stdin, &line, &linesize);
	if (is_a_tty[0]) {
		fputc('\n', stdout);
		tcsetattr(0, TCSADRAIN, otio);
	}
	*reset_tio = 0;
	if (i < 0) {
		if (line)
			free(line);
		return NULL;
	}
	pass = savestr(line);
	free(line);
	return pass;
}

void 
transflags(struct message *omessage, long omsgCount, int transparent)
{
	struct message	*omp, *nmp, *newdot, *newprevdot;
	int	hf;

	omp = omessage;
	nmp = message;
	newdot = message;
	newprevdot = NULL;
	while (omp < &omessage[omsgCount] &&
			nmp < &message[msgCount]) {
		if (dot && nmp->m_uid == dot->m_uid)
			newdot = nmp;
		if (prevdot && nmp->m_uid == prevdot->m_uid)
			newprevdot = nmp;
		if (omp->m_uid == nmp->m_uid) {
			hf = nmp->m_flag & MHIDDEN;
			if (transparent && mb.mb_type == MB_IMAP)
				omp->m_flag &= ~MHIDDEN;
			*nmp++ = *omp++;
			if (transparent && mb.mb_type == MB_CACHE)
				nmp[-1].m_flag |= hf;
		} else if (omp->m_uid < nmp->m_uid)
			omp++;
		else
			nmp++;
	}
	dot = newdot;
	setdot(newdot);
	prevdot = newprevdot;
	free(omessage);
}

char *
getrandstring(size_t length)
{
	static unsigned char	nodedigest[16];
	static pid_t	pid;
	int	i, fd = -1;
	char	*data;
	char	*cp, *rp;
	MD5_CTX	ctx;

	data = salloc(length);
	if ((fd = open("/dev/urandom", O_RDONLY)) < 0 ||
			read(fd, data, length) != length) {
		if (pid == 0) {
			pid = getpid();
			srand(pid);
			cp = nodename(0);
			MD5Init(&ctx);
			MD5Update(&ctx, (unsigned char *)cp, strlen(cp));
			MD5Final(nodedigest, &ctx);
		}
		for (i = 0; i < length; i++)
			data[i] = (int)(255 * (rand() / (RAND_MAX + 1.0))) ^
				nodedigest[i % sizeof nodedigest];
	}
	if (fd > 0)
		close(fd);
	cp = memtob64(data, length);
	rp = salloc(length+1);
	strncpy(rp, cp, length)[length] = '\0';
	free(cp);
	return rp;
}

void 
out_of_memory(void)
{
	panic("no memory");
}

void *
smalloc(size_t s)
{
	void *p;

	if (s == 0)
		s = 1;
	if ((p = malloc(s)) == NULL)
		out_of_memory();
	return p;
}

void *
srealloc(void *v, size_t s)
{
	void *r;

	if (s == 0)
		s = 1;
	if (v == NULL)
		return smalloc(s);
	if ((r = realloc(v, s)) == NULL)
		out_of_memory();
	return r;
}

void *
scalloc(size_t nmemb, size_t size)
{
	void *vp;

	if (size == 0)
		size = 1;
	if ((vp = calloc(nmemb, size)) == NULL)
		out_of_memory();
	return vp;
}

char *
sstpcpy(char *dst, const char *src)
{
	while ((*dst = *src++) != '\0')
		dst++;
	return dst;
}

char *
sstrdup(const char *cp)
{
	char	*dp;
	
	if (cp) {
		dp = smalloc(strlen(cp) + 1);
		strcpy(dp, cp);
		return dp;
	} else
		return NULL;
}

enum okay 
makedir(const char *name)
{
	int	e;
	struct stat	st;

	if (mkdir(name, 0700) < 0) {
		e = errno;
		if ((e == EEXIST || e == ENOSYS) &&
				stat(name, &st) == 0 &&
				(st.st_mode&S_IFMT) == S_IFDIR)
			return OKAY;
		return STOP;
	}
	return OKAY;
}

#ifdef	HAVE_FCHDIR
enum okay 
cwget(struct cw *cw)
{
	if ((cw->cw_fd = open(".", O_RDONLY)) < 0)
		return STOP;
	if (fchdir(cw->cw_fd) < 0) {
		close(cw->cw_fd);
		return STOP;
	}
	return OKAY;
}

enum okay 
cwret(struct cw *cw)
{
	if (fchdir(cw->cw_fd) < 0)
		return STOP;
	return OKAY;
}

void 
cwrelse(struct cw *cw)
{
	close(cw->cw_fd);
}
#else	/* !HAVE_FCHDIR */
enum okay 
cwget(struct cw *cw)
{
	if (getcwd(cw->cw_wd, sizeof cw->cw_wd) == NULL || chdir(cw->cw_wd) < 0)
		return STOP;
	return OKAY;
}

enum okay 
cwret(struct cw *cw)
{
	if (chdir(cw->cw_wd) < 0)
		return STOP;
	return OKAY;
}

/*ARGSUSED*/
void 
cwrelse(struct cw *cw)
{
}
#endif	/* !HAVE_FCHDIR */

void
makeprint(struct str *in, struct str *out)
{
	static int	print_all_chars = -1;
	char	*inp, *outp;
	size_t	msz, dist;

	out->s = smalloc(msz = in->l + 1);
	if (print_all_chars == -1)
		print_all_chars = value("print-all-chars") != NULL;
	if (print_all_chars) {
		memcpy(out->s, in->s, in->l);
		out->l = in->l;
		out->s[out->l] = '\0';
		return;
	}
	inp = in->s;
	outp = out->s;
#if defined (HAVE_MBTOWC) && defined (HAVE_WCTYPE_H)
	if (mb_cur_max > 1) {
		wchar_t	wc;
		char	mb[MB_LEN_MAX+1];
		int	i, n;
		out->l = 0;
		while (inp < &in->s[in->l]) {
			if (*inp & 0200)
				n = mbtowc(&wc, inp, &in->s[in->l] - inp);
			else {
				wc = *inp;
				n = 1;
			}
			if (n < 0) {
				mbtowc(&wc, NULL, mb_cur_max);
				wc = utf8 ? 0xFFFD : '?';
				n = 1;
			} else if (n == 0)
				n = 1;
			inp += n;
			if (!iswprint(wc) && wc != '\n' && wc != '\r' &&
					wc != '\b' && wc != '\t') {
				if ((wc & ~(wchar_t)037) == 0)
					wc = utf8 ? 0x2400 | wc : '?';
				else if (wc == 0177)
					wc = utf8 ? 0x2421 : '?';
				else
					wc = utf8 ? 0x2426 : '?';
			}
			if ((n = wctomb(mb, wc)) <= 0)
				continue;
			out->l += n;
			if (out->l >= msz - 1) {
				dist = outp - out->s;
				out->s = srealloc(out->s, msz += 32);
				outp = &out->s[dist];
			}
			for (i = 0; i < n; i++)
				*outp++ = mb[i];
		}
	} else
#endif	/* HAVE_MBTOWC && HAVE_WCTYPE_H */
	{
		int	c;
		while (inp < &in->s[in->l]) {
			c = *inp++ & 0377;
			if (!isprint(c) && c != '\n' && c != '\r' &&
					c != '\b' && c != '\t')
				c = '?';
			*outp++ = c;
		}
		out->l = in->l;
	}
	out->s[out->l] = '\0';
}

char *
prstr(const char *s)
{
	struct str	in, out;
	char	*rp;

	in.s = (char *)s;
	in.l = strlen(s);
	makeprint(&in, &out);
	rp = salloc(out.l + 1);
	memcpy(rp, out.s, out.l);
	rp[out.l] = '\0';
	free(out.s);
	return rp;
}

int
prout(const char *s, size_t sz, FILE *fp)
{
	struct str	in, out;
	int	n;

	in.s = (char *)s;
	in.l = sz;
	makeprint(&in, &out);
	n = fwrite(out.s, 1, out.l, fp);
	free(out.s);
	return n;
}

/*
 * Print out a Unicode character or a substitute for it.
 */
int
putuc(int u, int c, FILE *fp)
{
#if defined (HAVE_MBTOWC) && defined (HAVE_WCTYPE_H)
	if (utf8 && u & ~(wchar_t)0177) {
		char	mb[MB_LEN_MAX];
		int	i, n, r = 0;
		if ((n = wctomb(mb, u)) > 0) {
			for (i = 0; i < n; i++)
				r += putc(mb[i] & 0377, fp) != EOF;
			return r;
		} else if (n == 0)
			return putc('\0', fp) != EOF;
		else
			return 0;
	} else
#endif	/* HAVE_MBTOWC && HAVE_WCTYPE_H */
		return putc(c, fp) != EOF;
}

/*
 * Locale-independent character class functions.
 */
int 
asccasecmp(const char *s1, const char *s2)
{
	register int cmp;

	do
		if ((cmp = lowerconv(*s1 & 0377) - lowerconv(*s2 & 0377)) != 0)
			return cmp;
	while (*s1++ != '\0' && *s2++ != '\0');
	return 0;
}

int
ascncasecmp(const char *s1, const char *s2, size_t sz)
{
	register int cmp;
	size_t i = 1;

	if (sz == 0)
		return 0;
	do
		if ((cmp = lowerconv(*s1 & 0377) - lowerconv(*s2 & 0377)) != 0)
			return cmp;
	while (i++ < sz && *s1++ != '\0' && *s2++ != '\0');
	return 0;
}

char *
asccasestr(const char *haystack, const char *xneedle)
{
	char	*needle, *NEEDLE;
	int	i, sz;

	sz = strlen(xneedle);
	if (sz == 0)
		return (char *)haystack;
	needle = ac_alloc(sz);
	NEEDLE = ac_alloc(sz);
	for (i = 0; i < sz; i++) {
		needle[i] = lowerconv(xneedle[i]&0377);
		NEEDLE[i] = upperconv(xneedle[i]&0377);
	}
	while (*haystack) {
		if (*haystack == *needle || *haystack == *NEEDLE) {
			for (i = 1; i < sz; i++)
				if (haystack[i] != needle[i] &&
						haystack[i] != NEEDLE[i])
					break;
			if (i == sz)
				return (char *)haystack;
		}
		haystack++;
	}
	return NULL;
}

const unsigned char class_char[] = {
/*	000 nul	001 soh	002 stx	003 etx	004 eot	005 enq	006 ack	007 bel	*/
	C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,
/*	010 bs 	011 ht 	012 nl 	013 vt 	014 np 	015 cr 	016 so 	017 si 	*/
	C_CNTRL,C_BLANK,C_WHITE,C_SPACE,C_SPACE,C_SPACE,C_CNTRL,C_CNTRL,
/*	020 dle	021 dc1	022 dc2	023 dc3	024 dc4	025 nak	026 syn	027 etb	*/
	C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,
/*	030 can	031 em 	032 sub	033 esc	034 fs 	035 gs 	036 rs 	037 us 	*/
	C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,C_CNTRL,
/*	040 sp 	041  ! 	042  " 	043  # 	044  $ 	045  % 	046  & 	047  ' 	*/
	C_BLANK,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,
/*	050  ( 	051  ) 	052  * 	053  + 	054  , 	055  - 	056  . 	057  / 	*/
	C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,
/*	060  0 	061  1 	062  2 	063  3 	064  4 	065  5 	066  6 	067  7 	*/
	C_OCTAL,C_OCTAL,C_OCTAL,C_OCTAL,C_OCTAL,C_OCTAL,C_OCTAL,C_OCTAL,
/*	070  8 	071  9 	072  : 	073  ; 	074  < 	075  = 	076  > 	077  ? 	*/
	C_DIGIT,C_DIGIT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,
/*	100  @ 	101  A 	102  B 	103  C 	104  D 	105  E 	106  F 	107  G 	*/
	C_PUNCT,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,
/*	110  H 	111  I 	112  J 	113  K 	114  L 	115  M 	116  N 	117  O 	*/
	C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,
/*	120  P 	121  Q 	122  R 	123  S 	124  T 	125  U 	126  V 	127  W 	*/
	C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,C_UPPER,
/*	130  X 	131  Y 	132  Z 	133  [ 	134  \ 	135  ] 	136  ^ 	137  _ 	*/
	C_UPPER,C_UPPER,C_UPPER,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,
/*	140  ` 	141  a 	142  b 	143  c 	144  d 	145  e 	146  f 	147  g 	*/
	C_PUNCT,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,
/*	150  h 	151  i 	152  j 	153  k 	154  l 	155  m 	156  n 	157  o 	*/
	C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,
/*	160  p 	161  q 	162  r 	163  s 	164  t 	165  u 	166  v 	167  w 	*/
	C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,C_LOWER,
/*	170  x 	171  y 	172  z 	173  { 	174  | 	175  } 	176  ~ 	177 del	*/
	C_LOWER,C_LOWER,C_LOWER,C_PUNCT,C_PUNCT,C_PUNCT,C_PUNCT,C_CNTRL
};