diff options
Diffstat (limited to 'maildrop/reformail.C')
| -rw-r--r-- | maildrop/reformail.C | 1204 | 
1 files changed, 1204 insertions, 0 deletions
| diff --git a/maildrop/reformail.C b/maildrop/reformail.C new file mode 100644 index 0000000..be1e9cb --- /dev/null +++ b/maildrop/reformail.C @@ -0,0 +1,1204 @@ +/* +** Copyright 1998 - 2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#include	"config.h" + +#include	<stdio.h> +#include	<iostream> +#include	<iomanip> +#include	<stdlib.h> +#include	<string.h> +#include	<ctype.h> +#include	<signal.h> +#include	<pwd.h> + +#if	HAVE_LOCALE_H +#include	<locale.h> +#endif + +#if HAVE_UNISTD_H +#include	<unistd.h> +#endif +#if HAVE_FCNTL_H +#include	<fcntl.h> +#endif +#include	"mytime.h" +#include	<sys/types.h> +#include	"mywait.h" +#if HAVE_SYS_FILE_H +#include	<sys/file.h> +#endif +#include	"rfc822.h" +#include	"buffer.h" +#include	"liblock/config.h" +#include	"liblock/liblock.h" + +#if	HAS_GETHOSTNAME +#else +extern "C" int gethostname(const char *, size_t); +#endif + + +static int inbody=0, addcrs=0, catenate=0; +static const char *(*append_more_headers)(); +static Buffer	optx, optX, opta, optA, opti, optI, optu, optU, optubuf, optUbuf, optR; + +static Buffer add_from_filter_buf; +static const char *add_from_filter_buf_ptr; +static const char *cache_maxlen="", *cache_name=""; + +static const char *( *from_filter)(); +static Buffer	current_line; + +void outofmem() +{ +	std::cerr << "reformail: Out of memory." << std::endl; +	exit(1); +} + +void help() +{ +	std::cerr << "reformail: Invalid arguments." << std::endl; +	exit(1); +} + +// Return next line from standard input.  Trailing CRs are stripped + +const char *NextLine() +{ +static Buffer buf; +int	c; + +	buf.reset(); +	while ((c=std::cin.get()) >= 0 && c != '\n') +		buf.push(c); +	if (c < 0 && buf.Length() == 0)	return (0); +	if (buf.Length())	// Strip CRs +	{ +		c=buf.pop(); +		if (c != '\r')	buf.push(c); +	} +	buf.push('\n'); +	buf.push('\0'); +	return (buf); +} + +// from_filter is the initial filtering done on the message. +// from_filter adds or subtracts "From " quoting from the message. +// from_filter returns the next line from the message, after filtering. +// The line is always terminated by a newline character. +// When the header is being read, multiline headers are silently +// concatenated into a single return from from_filter() (separated by +// newlines, of course. +// +// from_filter is initialized to either from_filter(), add_from_filter() +// and rem_from_filter() respectively, to cause either addition, removal of, +// or no change to from quoting.  The pointer is automatically updated. +// +// Also, the from_filter silently discards empty lines at the beginning of +// the message. + +// DO NOT CHANGE FROM QUOTING. + +const char *no_from_filter_header(); + +const char *no_from_filter() +{ +const	char *p; + +	while ((p=NextLine()) && *p == '\n') +		; +	if (!p)	return (0); + +	current_line=p; +	return ( no_from_filter_header() ); +} + +const char *read_blank(); + +const char *no_from_filter_header() +{ +const	char *p; + +static Buffer buf; + +	from_filter= &no_from_filter_header; + +	while ((p=NextLine()) && *p && *p != '\n' && isspace((unsigned char)*p)) +		current_line += p; + +	buf=current_line; +	buf+='\0'; +	if (!p || *p == '\n') +	{ +		from_filter= &read_blank; +		return (buf); +	} +	current_line=p; +	return (buf); +} + +const char *read_blank() +{ +	from_filter= &NextLine; +	return ("\n"); +} + +////////////////////////////////////////////////////////////////////////// +// +//  Add 'From ' quoting.  All headers are read into add_from_filter_buf, +//  and a suitable return address is located.  The 'From ' line is +//  generated, and return.  Subsequent calls fetch one header at a +//  time from add_from_filter_buf, then resume reading the body of the +//  message. +// +////////////////////////////////////////////////////////////////////////// + +const char *add_from_filter_header(); +const char *add_from_filter() +{ +const	char *p; +int n; +	while ((p=NextLine()) && *p == '\n') +		; +	if (!p)	return (0); + +	current_line=p; +	if (strncmp(p, "From ", 5) == 0) +		return ( no_from_filter_header() ); +	add_from_filter_buf.reset(); +	while (p && *p != '\n') +	{ +		add_from_filter_buf += p; +		p=NextLine(); +	} + +	add_from_filter_buf += '\0'; + +static Buffer	return_path; +static Buffer	from_header; + +	return_path.reset(); +	from_header.reset(); + +	for (p=add_from_filter_buf; *p; ) +	{ +	Buffer	header; + +		while (*p && *p != ':' && *p != '\n') +		{ +		int	c= (unsigned char)*p++; + +			c=tolower(c); +			header.push(c); +		} +		for (;;) +		{ +			while (*p && *p != '\n') +			{ +				header.push(*p); +				p++; +			} +			if (!*p)	break; +			++p; +			header.push('\n'); +			if (!*p || !isspace((unsigned char)*p))	break; +		} +		header.push('\0'); +		if (strncmp(header, "return-path:", 12) == 0 || +			strncmp(header, ">return-path:", 13) == 0 || +			strncmp(header, "errors-to:", 10) == 0 || +			strncmp(header, ">errors-to:", 11) == 0) +		{ +			for (p=header; *p != ':'; p++) +				; +			return_path=p; +		} + +		if (strncmp(header, "from:", 5) == 0) +			from_header=(const char *)header + 5; +	} +	if (return_path.Length() == 0)	return_path=from_header; +	return_path += '\0'; + +	struct rfc822t *rfc=rfc822t_alloc_new( (const char *)return_path, +					       NULL, NULL); + +	if (!rfc)	outofmem(); + +	struct rfc822a *rfca=rfc822a_alloc( rfc); + +	if (!rfca)	outofmem(); + +	from_header.reset(); + +	for (n=0; n<rfca->naddrs; ++n) +	{ +		if (rfca->addrs[n].tokens) +		{ +			char *p=rfc822_display_addr_tobuf(rfca, n, NULL); + +			if (p) +			{ +				try { +					from_header=p; +				} catch (...) +				{ +					free(p); +					throw; +				} +				free(p); +				break; +			} +		} +	} + +	rfc822a_free(rfca); +	rfc822t_free(rfc); + +	if (from_header.Length() == 0)	from_header="root"; +	return_path="From "; +	return_path += from_header; +	return_path.push(' '); +time_t	t; + +	time(&t); +	p=ctime(&t); +	while (*p && *p != '\n') +	{ +		return_path.push(*p); +		p++; +	} +	return_path += '\n'; +	return_path += '\0'; +	from_filter=add_from_filter_header; +	add_from_filter_buf_ptr=add_from_filter_buf; +	return (return_path); +} + +const char *add_from_filter_body(); + +const char *add_from_filter_header() +{ +static Buffer buf; + +	buf.reset(); +	 +	if (*add_from_filter_buf_ptr == '\0') +	{ +		from_filter= &add_from_filter_body; +		return ("\n"); +	} + +	do +	{ +		while (*add_from_filter_buf_ptr) +		{ +			buf.push ( (unsigned char)*add_from_filter_buf_ptr ); +			if ( *add_from_filter_buf_ptr++ == '\n')	break; +		} +	} while ( *add_from_filter_buf_ptr && *add_from_filter_buf_ptr != '\n' +		&& isspace( (unsigned char)*add_from_filter_buf_ptr )); +	buf += '\0'; +	return (buf); +} + +const char *add_from_filter_body() +{ +const char *p=NextLine(); + +	if (!p)	return (p); + +const char *q; + +	for (q=p; *q == '>'; q++) +		; +	if (strncmp(q, "From ", 5))	return (p); + +static Buffer add_from_buf; + +	add_from_buf=">"; +	add_from_buf += p; +	add_from_buf += '\0'; +	return (add_from_buf); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Strip From quoting. +// +//////////////////////////////////////////////////////////////////////////// + +const char *rem_from_filter_header(); + +const char *(*rem_from_filter_header_ptr)(); + +const char *rem_from_filter() +{ +const	char *p; + +	while ((p=NextLine()) && *p == '\n') +		; +	if (!p)	return (0); + +	if (strncmp(p, "From ", 5)) +	{ +		current_line=p; +		return ( no_from_filter_header() ); +	} +	p=NextLine(); +	if (!p)	return (p); +	current_line=p; +	rem_from_filter_header_ptr= &no_from_filter_header; +	return ( rem_from_filter_header() ); +} + +const char *rem_from_filter_body(); +const char *rem_from_filter_header() +{ +const char *p=(*rem_from_filter_header_ptr)(); + +	rem_from_filter_header_ptr=from_filter; +	from_filter=rem_from_filter_header; +	if (!p || *p == '\n') +	{ +		from_filter=&rem_from_filter_body; +		p="\n"; +	} +	return (p); +} + +const char *rem_from_filter_body() +{ +const char *p=NextLine(); + +	if (!p)	return (p); + +	if (*p == '>') +	{ +	const char *q; + +		for (q=p; *q == '>'; q++) +			; +		if (strncmp(q, "From ", 5) == 0)	++p; +	} +	return (p); +} + +static const char *HostName() +{ +static char hostname_buf[256]; + +	hostname_buf[0]=0; +	hostname_buf[sizeof(hostname_buf)-1]=0; +	gethostname(hostname_buf, sizeof(hostname_buf)-1); +	return (hostname_buf); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Return TRUE if header is already in a list of headers. +// +// hdrs: null separated list of headers (and header contents) +// hdr - header to check (must be lowercase and terminated by a colon) +// pos - offset into hdrs where it's found. +// + +static int has_hdr(const Buffer &hdrs, const char *hdr, unsigned &pos) +{ +const char *r=hdrs; +int l=hdrs.Length(); +Buffer	buf2; +unsigned pos2=0; + +	while (l) +	{ +		buf2.reset(); +		pos=pos2; +		while (l) +		{ +			--l; +			++pos2; +			buf2.push( tolower(*r)); +			if (*r++ == 0)	break; +		} +		buf2.push('\0'); +		if (strncmp(hdr, buf2, strlen(hdr)) == 0)	return (1); +	} +	return (0); +} + +static int has_hdr(const Buffer &hdrs, const char *hdr) +{ +unsigned dummy; + +	return (has_hdr(hdrs, hdr, dummy)); +} + +static void strip_empty_header(Buffer &buf) +{ +Buffer	newbuf; +int l; +const char *p; + +	for (p=buf, l=buf.Length(); l; ) +	{ +		if (p[strlen(p)-1] == ':') +		{ +			while (l) +			{ +				--l; +				if (*p++ == '\0')	break; +			} +			continue; +		} +		while (l) +		{ +			--l; +			newbuf.push( *p ); +			if (*p++ == '\0')	break; +		} +	} +	buf=newbuf; +} + +static void strip_header(Buffer &header, unsigned offset) +{ +Buffer	buf1; +const char *p=header; +int l=header.Length(); + +	while (l) +	{ +		if (!offset) +		{ +			while (l) +			{ +				--l; +				if (*p++ == '\0')	break; +			} +			break; +		} +		buf1.push( *p++ ); +		--l; +		--offset; +	} +	while (l--) +		buf1.push( *p++ ); +	header=buf1; +} + +const char *ReadLineAddNewHeader(); + +const char *ReadLineAddHeader() +{ +Buffer	buf1; +const char *q; +const char *p; +unsigned pos; +static Buffer oldbuf; + +	for (;;) +	{ +		p= (*from_filter)(); + +		if (!p)	return p; +		if (*p == '\n') +		{ +			strip_empty_header(opti); +			strip_empty_header(optI); +			return ( ReadLineAddNewHeader()); +		} +		buf1.reset(); +		for (q=p; *q && *q != '\n'; q++) +		{ +			buf1.push( tolower(*q) ); +			if (*q == ':')	break; +		} +		buf1 += '\0'; + +		if (has_hdr(opti, buf1)) +		{ +			oldbuf="old-"; +			oldbuf += buf1; +			buf1=oldbuf; + +		Buffer	tbuf; + +			tbuf="Old-"; +			tbuf += p; +			oldbuf=tbuf; +			oldbuf += '\0'; +			p=oldbuf; +		} +		if (has_hdr(optR, buf1, pos)) +		{ +		Buffer	tbuf; + +			q=optR; +			q += pos + strlen(buf1); +			tbuf=q; + +			p += strlen(buf1); +			tbuf += p; +			oldbuf=tbuf; +			oldbuf += '\0'; +			p=oldbuf; +		} + +		if (has_hdr(optI, buf1)) +			continue; +		if (has_hdr(optu, buf1)) +		{ +			if (!has_hdr(optubuf, buf1)) +			{ +				q=p; +				do +				{ +					optubuf.push( *q ); +				} while (*q++); +				break; +			} +			continue; +		} + +		if (has_hdr(optU, buf1)) +		{ +			if (has_hdr(optUbuf, buf1, pos)) +				strip_header(optUbuf, pos); +			while (*p) +			{ +				optUbuf.push( *p ); +				p++; +			} +			optUbuf.pop(); +			optUbuf.push('\0'); +			continue; +		} +		break; +	} + +unsigned offset; + +	if (has_hdr(opta, buf1, offset)) +		strip_header(opta, offset); +	return (p); +} + +const char *ReadLineAddNewHeaderDone(); + +const char *ReadLineAddNewHeader() +{ +	append_more_headers= &ReadLineAddNewHeader; + +Buffer	*bufptr; + +	if (opta.Length())	bufptr= &opta; +	else if (optA.Length())	bufptr= &optA; +	else if (opti.Length())	bufptr= &opti; +	else if (optI.Length())	bufptr= &optI; +	else if (optUbuf.Length())	bufptr= &optUbuf; +	else +	{ +		append_more_headers=&ReadLineAddNewHeaderDone; +		return ("\n"); +	} + +static Buffer buf1; +Buffer	buf2; + +	buf1.reset(); + +const char *p= *bufptr; +int l= bufptr->Length(); + +	while (l) +	{ +		if ( !*p ) +		{ +			p++; +			l--; +			break; +		} +		buf1.push( *p ); +		p++; +		l--; +	} +	buf1.push('\n'); +	buf1.push('\0'); + +	while (l--) +		buf2.push (*p++); +	*bufptr=buf2; +	return (buf1); +} + +const char *ReadLineAddNewHeaderDone() +{ +	return ( (*from_filter)() ); +} + +//////////////////////////////////////////////////////////////////////////// +const char *ReadLine() +{ +const char *p=(*append_more_headers)(); + +	if (!p)	return (p); + +static Buffer	buf; + +	if (*p == '\n') +		inbody=1; + +	if (catenate && !inbody) +	{ +	const char *q; + +		buf.reset(); +		for (q=p; *q; q++) +		{ +			if (*q != '\n') +			{ +				buf.push(*q); +				continue; +			} +			do +			{ +				++q; +			} while (*q && isspace(*q)); +			if (*q) +				buf.push(' '); +			--q; +		} +		if (addcrs)	buf.push('\r'); +		buf.push('\n'); +		buf.push('\0'); +		return (buf); +	} + +	if (addcrs) +	{ +		buf=p; +		buf.pop(); +		buf += "\r\n"; +		buf += '\0'; +		return (buf); +	} +	return (p); +} + +///////////////////////////////////////////////////////////////////////// +// +// Default activity: just copy the message (let the low-level format +// filters do their job. +// +///////////////////////////////////////////////////////////////////////// + +void copy(int, char *[], int) +{ +const char *p; + +	while ((p= ReadLine()) != 0) +		std::cout << p; +} + +void cache(int, char *[], int) +{ +const char *p; +Buffer	buf; +int found=0; + +	addcrs=0; +	while ((p= ReadLine()) != 0) +	{ +	int	c; + +		if (inbody)	break; +		buf.reset(); +		while (*p && *p != '\n') +		{ +			c= (unsigned char)*p; +			c=tolower(c); +			buf.push(c); +			if (*p++ == ':')	break; +		} +		if (!(buf == "message-id:"))	continue; +		buf += '\0'; +		while (*p && isspace( (unsigned char)*p))	p++; +		buf.reset(); +		while (*p) +		{ +			buf.push(*p); +			++p; +		} + +		while ( (c=(unsigned char)buf.pop()) != 0 && isspace(c)) +			; +		if (c)	buf.push(c); +		if (buf.Length() == 0)	break; +		buf.push('\0'); + +	int	fd=open(cache_name, O_RDWR | O_CREAT, 0600); + +		if (fd < 0) +		{ +			perror("open"); +			exit(75); +		} + +		if (ll_lock_ex(fd) < 0) +		{ +			perror("lock"); +			exit(75); +		} + +	off_t	pos=0; + +		if (lseek(fd, 0L, SEEK_END) == -1 || +			(pos=lseek(fd, 0L, SEEK_CUR)) == -1 || +			lseek(fd, 0L, SEEK_SET) == -1) +		{ +			perror("seek"); +			exit(75); +		} + +	off_t	maxlen_n=atol(cache_maxlen); +	char	*charbuf; +	off_t	newpos=maxlen_n; + +		if (newpos < pos)	newpos=pos; + +		if ((charbuf=new char[newpos+buf.Length()+1]) == NULL) +			outofmem(); + +	off_t	readcnt=read(fd, charbuf, newpos); + +		if (readcnt < 0)	perror("read"); + +	char *q, *r; + +		for (q=r=charbuf; q<charbuf+readcnt; ) +		{ +			if (*q == '\0')	break;	// Double null terminator +			if (strcmp( (const char *)buf, q) == 0) +			{ +				found=1; +				while (q < charbuf+readcnt) +					if (*q++ == '\0')	break; +			} +			else while (q < charbuf+readcnt) +				if ( (*r++=*q++) == '\0') break; +		} +		memcpy(r, (const char *)buf, buf.Length()); +		r += buf.Length(); +		for (q=charbuf; q<r; ) +		{ +			if (r - q < maxlen_n) +				break; +			while (q < r) +				if (*q++ == '\0')	break; +		} +		if (q == r)	q=charbuf; +		*r++ = '\0'; +		if (lseek(fd, 0L, SEEK_SET) == -1) +		{ +			perror("lseek"); +			exit(1); +		} +		while (q < r) +		{ +			readcnt=write(fd, q, r-q); +			if (readcnt == -1) +			{ +				perror("write"); +				exit(1); +			} +			q += readcnt; +		} +		close(fd); +		delete[] charbuf; +		break; +	} +	while ((p= ReadLine()) != 0) +		; +	exit(found ? 0:1); +} + +////////////////////////////////////////////////////////////////////////// +// +// Extract headers + +void extract_headers(int, char *[], int) +{ +const char *p, *q; +Buffer	b; + +	catenate=1; +	while ((p=ReadLine()) && !inbody) +	{ +		b.reset(); +		for (q=p; *q && *q != '\n'; ) +		{ +		int	c= (unsigned char)*q; + +			b.push( tolower(c) ); +			if ( *q++ == ':')	break; +		} +		b.push(0); + +		if (has_hdr(optx, b)) +		{ +			while (*q && *q != '\n' && isspace(*q)) +				q++; +			std::cout << q; +			continue; +		} + +		if (has_hdr(optX, b)) +		{ +			std::cout << p; +			continue; +		} +	} +	if (!std::cin.seekg(0, std::ios::end).fail()) +		return; +	std::cin.clear(); + +	while ( ReadLine() ) +		; +} +////////////////////////////////////////////////////////////////////////// +// +// Split mbox file into messages. + +void split(int argc, char *argv[], int argn) +{ +const char *p; +Buffer	buf; +int	l; +int	do_environ=1; +unsigned long	environ=0; +unsigned	environ_len=3; +const	char *env; + +	if (argn >= argc)	help(); + +	while ( (p=NextLine()) && *p == '\n') +		; + +	signal(SIGCHLD, SIG_DFL); +	signal(SIGPIPE, SIG_IGN); +	env=getenv("FILENO"); +	if (env) +	{ +	const char *q; + +		for (q=env; *q; q++) +			if (!isdigit(*q))	break; +		if (*q)	do_environ=0; +		else +		{ +			environ_len=strlen(env); +			environ=atol(env); +		} +	} + +	while (p) +	{ +	int	fds[2]; + +		if (pipe(fds) < 0) +		{ +			std::cerr << "reformail: pipe() failed." << std::endl; +			exit(1); +		} + +	pid_t	pid=fork(); + +		if (pid == -1) +		{ +			std::cerr << "reformail: fork() failed." << std::endl; +			exit(1); +		} + +		if (pid == 0) +		{ +			dup2(fds[0], 0); +			close(fds[0]); +			close(fds[1]); + +		Buffer	buf, buf2; + +			if (do_environ) +			{ +			char	*s; + +				while (environ || environ_len) +				{ +					buf.push( "0123456789"[environ % 10]); +					environ /= 10; +					if (environ_len)	--environ_len; +				} + +				buf2="FILENO="; +				while (buf.Length()) +					buf2.push(buf.pop()); +				buf2 += '\0'; +				s=strdup(buf2); +				if (!s) +				{ +					perror("strdup"); +					exit (1); +				} +				putenv(s); +			} + +			execvp( argv[argn], argv+argn); +			std::cerr << "reformail: exec() failed." << std::endl; +			exit(1); +		} +		close(fds[0]); +		environ++; + +		do +		{ +			buf=p; +			p=ReadLine(); +			if (!p || strncmp(p, "From ", 5) == 0) +				buf.pop();	// Drop trailing newline +			else +			{ +				if (addcrs) +				{ +					buf.pop(); +					buf.push('\r'); +					buf.push('\n'); +				} +			} + +		const char *q=buf; + +			l=buf.Length(); +			while (l) +			{ +			int	n= ::write( fds[1], q, l); +				if (n <= 0) +				{ +					std::cerr +					  << "reformail: write() failed." +					  << std::endl; +					exit(1); +				} +				q += n; +				l -= n; +			} +		} while (p && strncmp(p, "From ", 5)); +		close(fds[1]); + +	int	wait_stat; + +		while ( wait(&wait_stat) != pid ) +			; +		if (!WIFEXITED(wait_stat) || WEXITSTATUS(wait_stat)) +			break;	// Rely on diagnostic from child +	} +} + +////////////////////////////////////////////////////////////////////////////// + +static void add_bin64(Buffer &buf, unsigned long n) +{ +int	i; + +	for (i=0; i<16; i++) +	{ +		buf.push ( "0123456789ABCDEF"[n % 16] ); +		n /= 16; +	} +} + +static void add_messageid(Buffer &buf) +{ +time_t	t; + +	buf.push('<'); +	time(&t); +	add_bin64(buf,t); +	buf.push('.'); +	add_bin64(buf, getpid() ); +	buf += ".reformail@"; +	buf += HostName(); +	buf.push('>'); +} + +static void add_opta(Buffer &buf, const char *optarg) +{ +Buffer	chk_buf; +const char *c; + +	for (c=optarg; *c; c++) +		chk_buf.push ( tolower( (unsigned char)*c )); +	if (chk_buf == "message-id:" || chk_buf == "resent_message_id:") +	{ +		chk_buf=optarg; +		chk_buf += ' '; +		add_messageid(chk_buf); +		chk_buf += '\0'; +		optarg=chk_buf; +	} + +	do +	{ +		buf.push( *optarg ); +	} while (*optarg++); +} + +int main(int argc, char *argv[]) +{ +int	argn, done; +const char	*optarg; +void	(*function)(int, char *[], int)=0; + +#if HAVE_SETLOCALE +	setlocale(LC_ALL, "C"); +#endif + +	from_filter= &no_from_filter; +	append_more_headers=&ReadLineAddHeader; +	done=0; +	for (argn=1; argn<argc; argn++) +	{ +		if (strcmp(argv[argn], "--") == 0 || strcmp(argv[argn],"-")==0) +		{ +			++argn; +			break; +		} +		if (argv[argn][0] != '-')	break; +		optarg=argv[argn]+2; +		if (!*optarg)	optarg=0; +		switch ( argv[argn][1] )	{ +		case 'd': +			if (!optarg || !*optarg)	optarg="1"; +			addcrs=atoi(optarg); +			break; +		case 'c': +			catenate=1; +			break; +		case 'f': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || *optarg == '0') +				from_filter=&rem_from_filter; +			else +				from_filter=&add_from_filter; +			break; +		case 'D': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || argn+1 >= argc)	help(); +			if (function)	help(); +			function=cache; +			cache_maxlen=optarg; +			cache_name=argv[++argn]; +			break; +		case 'a': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			add_opta(opta, optarg); +			break; +		case 'A': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			add_opta(optA, optarg); +			break; +		case 'i': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			do +			{ +				opti.push( *optarg ); +			} while (*optarg++); +			break; +		case 'I': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			do +			{ +				optI.push( *optarg ); +			} while (*optarg++); +			break; +		case 'R': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			while (*optarg) +				optR.push(*optarg++); +			if (argn+1 >= argc)	help(); +			optarg=argv[++argn]; +			while (*optarg) +				optR.push(*optarg++); +			optR.push(0); +			break; +		case 'u': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			while (*optarg) +				optu.push(*optarg++); +			optu.push(0); +			break; +		case 'U': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			while (*optarg) +				optU.push(*optarg++); +			optU.push(0); +			break; +		case 'x': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			while (*optarg) +				optx.push(*optarg++); +			optx.push(0); +			break; +		case 'X': +			if (!optarg && argn+1 < argc)	optarg=argv[++argn]; +			if (!optarg || !*optarg)	help(); +			if (function)	help(); +			while (*optarg) +				optX.push(*optarg++); +			optX.push(0); +			break; +		case 's': +			if (function)	help(); +			function= &split; +			++argn; +			done=1; +			break; +		default: +			help(); +		} +		if (done)	break; +	} +	if (optx.Length() || optX.Length()) +	{ +		if (function)	help(); +		function=extract_headers; +	} + +	if (!function)	function=copy; +	(*function)(argc, argv, argn); +	std::cout.flush(); +	if (std::cout.fail()) +	{ +		std::cerr << "reformail: error writing output." << std::endl; +		exit(1); +	} +	exit(0); +} | 
