/*!
 * \file 2hermes/imapupload.c
 *
 * \brief Upload a list of mailboxes to an IMAP server.
 *
 * This program attempts to deal gracefully with name clashes.
 *
 * The error handling is very basic, and little attention has been
 * paid to memory leaks.
 *
 * \author Tony Finch <fanf2@cam.ac.uk> <dot@dotat.at>
 *
 * $Cambridge: hermes/src/2hermes/imapupload.c,v 1.69 2005/06/13 14:37:56 fanf2 Exp $
 */

static const char camid[] =
"$Cambridge: hermes/src/2hermes/imapupload.c,v 1.69 2005/06/13 14:37:56 fanf2 Exp $";

#include <sys/types.h>
#include <sys/stat.h>

#include <ctype.h>
#include <pwd.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "c-client.h"

#include "err.h"
#include "list.h"
#include "stdbool.h"

static long appendmessage(MAILSTREAM *, void *, char **, char **, STRING **);

/**********************************************************************/

/*!
 * \name global state variables
 * \{
 */

static bool             critical;       /*!< c-client is in critical mode */
static int              debuglevel;     /*!< how much debugging output? */

static const char      *username;       /*!< the user's name */
static const char      *password;       /*!< the user's password */

static const char      *servername;     /*!< IMAP server name in curlies */
static struct list     *serverlist;     /*!< list of mailboxes on server */
static struct list     *clientlist;     /*!< list of local mailboxes */
static MAILSTREAM      *serverstream;   /*!< IMAP connection to server */
static MAILSTREAM      *clientstream;   /*!< local mailbox accessor */

static char            *msgdate;        /*!< message metadata to be freed */
static char            *msgflags;       /*!< message metadata to be freed */
static int              msgnum;         /*!< index of message to append */
static STRING           msgstr;         /*!< message data to append */

static char             buffer[1024];   /*!< general-purpose buffer */

/*!
 * \}
 */

/**********************************************************************/

/*!
 * \name Misc sugar
 * \{
 */

static void *
xalloc(size_t size)
{
	char *ptr = malloc(size);
	if(ptr == NULL)
		err(1, "malloc");
	return(ptr);
}

static char *
xstrdup(const char *old)
{
	char *new = strdup(old);
	if(new == NULL)
		err(1, "malloc");
	return(new);
}

static char *
xasprintf(const char *fmt, ...)
{
	va_list ap;
	size_t size;
	char *ptr, c;

	va_start(ap, fmt);
	/* work around Solaris POSIX compliance bug */
	size = vsnprintf(&c, 1, fmt, ap);
	va_end(ap);

	va_start(ap, fmt);
	ptr = xalloc(size + 1);
	vsnprintf(ptr, size + 1, fmt, ap);
	va_end(ap);

	return(ptr);
}

static bool
fgetline(FILE *stream, char *buf, size_t size)
{
	char *ret;
	int len;

	ret = fgets(buf, size, stream);
	if(ret == NULL) {
		if(feof(stream))
			return(false);
		else
			err(1, "error reading input");
	}
	len = strlen(buf);
	if(len == 0 || buf[len - 1] != '\n')
		errx(1, "error reading input: missing newline");
	buf[len - 1] = '\0';
	return(true);
}

static bool
getline(char *buf, size_t size)
{
	return(fgetline(stdin, buf, size));
}

static void
xfgetline(FILE *stream, char *buf, size_t size)
{
	if(fgetline(stream, buf, size) == false)
		errx(1, "error reading input: unexpected end-of-file");
}

static void
xgetline(char *buf, size_t size)
{
	xfgetline(stdin, buf, size);
}

/*!
 * \}
 */

/**********************************************************************/

/*!
 * \name lists of mailboxes
 * \{
 */

list_types(list, elem, link);

struct elem {
	struct link     link;
	const char     *old;    /*!< original mailbox name */
	const char     *tmp;    /*!< temporary mailbox name */
	const char     *new;    /*!< altered name to avoid clashes */
	int             attr;   /*!< server mailbox flags */
};

list_functions(list, struct list, struct elem, link);

static struct list *
list_create(void)
{
	struct list *list;

	list = xalloc(sizeof(*list));
	list_init(list);
	return(list);
}

static struct elem *
list_create_elem(const char *str)
{
	struct elem *elem;

	elem = xalloc(sizeof(*elem));
	elem->old = xstrdup(str);
	elem->tmp = NULL;
	elem->new = NULL;
	elem->attr = 0;
	return(elem);
}

static int
list_elem_cmp_new(struct elem *e1, struct elem *e2)
{
	return(strcmp(e1->new, e2->new));
}

static int
list_elem_cmp_str(struct elem *e1, const void *ptr)
{
	return(strcmp(e1->old, ptr));
}

/*!
 * \}
 */

/**********************************************************************/

/*!
 * \name Main program etc.
 * \{
 */

/*!
 * \brief Explain command line options.
 */
static void
usage(void)
{
	fprintf(stderr,
	    "usage: %s [-acdfpqr] [-u uid] <server>\n"
	    "	-a	Allow absolute pathnames\n"
	    "	-c	Confirm on command line not interactively\n"
	    "	-d	Increase debugging level\n"
	    "	-f	Do local mailbox name fix-ups\n"
	    "	-n	Do not do the actual upload\n"
	    "	-p	Read password from stdin\n"
	    "			(default is /dev/tty)\n"
	    "	-q	Be quiet\n"
	    "	-r	Rename local mailboxes to avoid clashes\n"
	    "			(default is to append to existing mailbox)\n"
	    "	-u	Optional username\n"
	    "The input to imapuload is a list of email folders "
	    "to upload, one per line.\n",
	    getprogname());
	exit(1);
}

/*!
 * \brief The main program.
 */
int
main(int argc, char *argv[])
{
	int c;
	bool allowabs, confirmed, fixup;
	bool mailprefix, noaction;
	bool pipepasswd, quiet, renameclashes;
	struct elem *elem, *next, *srv;
	const char *s;
        FILE *subs_file;

	/* general initialization */
	setprogname(argv[0]);
	serverlist = list_create();
	clientlist = list_create();
	allowabs = confirmed = fixup = false;
	mailprefix = noaction = false;
	pipepasswd = quiet = renameclashes = false;

	/* c-client initialization */
#	include "linkage.c"
	/* don't be so stupid */
	mail_parameters(NIL, SET_RSHTIMEOUT, 0);

	/* argument parsing */
	while((c = getopt(argc, argv, "acdfmnpqru:")) != -1)
		switch(c) {
		case('a'):	/* Allow absolute pathnames */
			allowabs = true;
			break;
		case('c'):	/* Don't check with the user */
			confirmed = true;
			break;
		case('d'):      /* Debugging */
			debuglevel++;
			err_set_debug(debuglevel);
			break;
		case('f'):	/* Local mailbox name fix-ups */
			fixup = true;
			break;
		case('m'):	/* Prefix remote folder names with mail/ */
			mailprefix = true;
			break;
		case('n'):	/* Do nothing */
			noaction = true;
			break;
		case('p'):	/* Read password from stdin */
			pipepasswd = true;
			break;
		case('q'):	/* Be quiet */
			quiet = true;
			break;
		case('r'):	/* Rename clashing mailboxes */
			renameclashes = true;
			break;
		case('u'):	/* Non-default username */
			username = optarg;
			break;
		default:
			usage();
		}
	argc -= optind;
	argv += optind;
	if(argc != 1)
		usage();
	servername = xasprintf("{%s}", argv[0]);
	debug(1, "servername %s", servername);

	if(pipepasswd) {
		xgetline(buffer, sizeof(buffer));
		password = xstrdup(buffer);
	} else {
		password = getpass("Password: ");
	}
	if(username == NULL) {
		uid_t uid = getuid();
		struct passwd *pw = getpwuid(uid);
		if(pw != NULL)
			username = xstrdup(pw->pw_name);
	}
	if(username == NULL) {
		printf("Username: ");
		xgetline(buffer, sizeof(buffer));
		username = xstrdup(buffer);
	}

	/* read list of local mailboxes to upload */
	while(getline(buffer, sizeof(buffer))) {
		elem = list_create_elem(buffer);
		list_insert_last(clientlist, elem);
		debug(1, "client filename: %s", elem->old);
		if(!fixup) {
			elem->new = xasprintf("%s%s", servername,
			    elem->old + strspn(elem->old, "./"));
			continue;
		}
		/* rename user's inbox */
		if(strncmp(elem->old, "/var/mail/", 10) == 0
		    || strncmp(elem->old, "/var/spool/mail/", 16) == 0) {
			elem->new = elem->old +
			    (elem->old[5] == 'm' ? 10 : 16);
			if(strcmp(elem->new, username) == 0) {
				elem->new = "INBOX";
			} else if(!allowabs) {
				warnx("ignoring mailbox %s: "
				    "doesn't match username %s",
				    elem->old, username);
				list_remove(elem);
				continue;
			} else {
				elem->new = xasprintf("inbox-%s", elem->new);
			}
		/* mailboxes outside the user's home */
		} else if(elem->old[0] == '/') {
			if(!allowabs) {
				warnx("ignoring mailbox %s: "
				    "absolute pathnames not permitted",
				    elem->old);
				list_remove(elem);
				continue;
			}
			elem->new = elem->old + 1;
		} else if(strcasecmp(elem->old, "inbox") == 0) {
			elem->new = "INBOX";
		} else {
			elem->new = elem->old;
		}
		/* i18n naming compatibility (RFC 3501 sect. 5.1.3) */
		for(c = 0; elem->new[c] != '\0'; c++) {
			if(elem->new[c] < ' ' || elem->new[c] > 0x7E) {
				warnx("ignoring mailbox %s: "
				    "I can't handle unencoded special characters",
				    elem->old);
				list_remove(elem);
				continue;
			}
			/* encode ampersands */
			if(elem->new[c] == '&')
				elem->new = xasprintf("%.*s&-%s",
				    c, elem->new, elem->new+c+1);
		}
		/* strip mail and Mail from pathnames */
		while(strncmp(elem->new, "mail/", 5) == 0
		    || strncmp(elem->new, "Mail/", 5) == 0)
                       elem->new += 5;
		/* And add it back if necessary */
		if(mailprefix)
			elem->new = xasprintf("%smail/%s", servername, elem->new);
		else
			elem->new = xasprintf("%s%s", servername, elem->new);
		debug(1, "server mailbox: %s", elem->new);
	}

	/* connect to server but don't open a mailbox */
	debug(1, "doing mail_open %s", servername);
	serverstream = mail_open(NIL, servername,
	    (debuglevel > 1 ? OP_DEBUG : NIL) | OP_HALFOPEN);
	if(serverstream == NIL)
		errx(1, "mail_open %s failed", servername);

	/* read list of remote mailboxes for avoiding clashes */
	debug(1, "doing mail_list %s *", servername);
	mail_list(serverstream, servername, "*");

	/* deal with name clashes */
	list_qsort(clientlist, list_elem_cmp_new);
	elem = list_first(clientlist);
	while(!list_attail(elem)) {
		debug(2, "checking %s -> %s", elem->old, elem->new);
		/* find the end of this group of local mailboxes */
		for(next = list_next(elem);
		    !list_attail(next);
		    next = list_next(next))
			if(strcmp(elem->new, next->new) != 0)
				break;
			else
				debug(2, "matches %s -> %s",
				    next->old, next->new);
		/* is there a local clash or a remote clash? */
		srv = list_find(list_first(serverlist), elem->new,
		    list_elem_cmp_str);
		if(srv != NULL)
			debug(2, "clash with %s", srv->old);
		if(list_next(elem) == next && srv == NULL) {
			debug(2, "no clash");
			elem = next;
			continue;
		}
		/* even if we aren't renaming, we might be forced to */
		if(!renameclashes) {
			if(srv == NULL || (srv->attr & LATT_NOSELECT) == 0) {
				elem = next;
				continue;
			}
			warnx("unselectable server mailbox: %s", srv->old);
		}
		/* number each clashing mailbox */
		c = 0;
		while(elem != next) {
			do {
				s = xasprintf("%s-%d", elem->new, c++);
				srv = list_find(list_first(serverlist),
				    s, list_elem_cmp_str);
				if(srv != NULL)
					debug(2, "clash with %s", s);
			} while(srv != NULL);
			elem->new = s;
			debug(1, "mailbox %s now %s",
			    elem->old, elem->new);
			elem = list_next(elem);
		}
	}

	/* tell the user what we plan to do */
	if(!quiet) {
		printf("Existing mailboxes on the server %s\n", servername);
		for(elem = list_first(serverlist);
		    !list_attail(elem);
		    elem = list_next(elem))
			printf("\t%s\n", elem->old);
		printf("Local mailboxes to be uploaded, and their\n"
		    "destination mailboxes on the server...\n");
		for(elem = list_first(clientlist);
		    !list_attail(elem);
		    elem = list_next(elem))
			printf("\t%s\n\t->\t%s\n", elem->old, elem->new);
	}
	fflush(stdout);
	sleep(1);
	/* confirm with the user that it's ok */
	while(!confirmed) {
		FILE *yn;
		yn = fopen("/dev/tty", "r+");
		if(yn == NULL)
			err(1, "opening terminal");
		fprintf(yn, "Proceed? (y/n) ");
		xfgetline(yn, buffer, sizeof(buffer));
		for(s = buffer; isspace(*(unsigned const char *)s); s++);
		if(*s == '\0')
			continue;
		if(*s != 'y' && *s != 'Y')
			errx(2, "not proceeding");
		confirmed = true;
	}
	if(noaction) {
		debug(1, "doing mail_close %s", servername);
		serverstream = mail_close(serverstream);
		return(0);
	}

	for(elem = list_first(clientlist);
	    !list_attail(elem);
	    elem = list_next(elem)) {
		MAILSTREAM *newstream;
		FILE *fo, *ft;
		bool clean = true;
		/* GAH. dirty fix for NUL characters in mailboxes */
		fo = fopen(elem->old, "r");
		if(fo == NULL) {
			warn("open %s", elem->old);
			warnx("mailbox clean check failed; skipping %s -> %s",
			    elem->old, elem->new);
			continue;
		}
		for(;;) {
			c = getc(fo);
			if(c == EOF)
				break;
			if(c == '\0')
				clean = false;
		}
		fclose(fo);
		if(clean)
			debug(1, "mailbox %s is clean", elem->old);
		else {
			debug(1, "mailbox %s is dirty", elem->old);
			elem->tmp = xasprintf("%s.%d.tmp", elem->old, getpid());
			debug(1, "temporary filename: %s", elem->tmp);
			fo = fopen(elem->old, "r");
			if(fo == NULL)
				warn("open %s", elem->old);
			ft = fopen(elem->tmp, "w");
			if(ft == NULL)
				warn("open %s", elem->tmp);
			if(fo == NULL || ft == NULL) {
				if(fo != NULL)
					fclose(fo);
				if(ft != NULL)
					fclose(ft);
				warnx("mailbox cleaning failed; "
				    "skipping %s -> %s", elem->old, elem->new);
				unlink(elem->tmp);
				continue;
			}
			if(fchmod(fileno(ft), 0600) < 0)
				warn("chmod 0600 %s", elem->tmp);
			for(;;) {
				c = getc(fo);
				if(c == EOF)
					break;
				if(c == '\0')
					putc('?', ft);
				else
					putc(c, ft);
			}
			if(fclose(fo) == EOF || fclose(ft) == EOF) {
				warn("copy");
				warnx("mailbox cleaning failed; "
				    "skipping %s -> %s", elem->old, elem->new);
				unlink(elem->tmp);
				continue;
			}
		}
		/* ensure the server mailbox exists before opening it */
		if(list_find(list_first(serverlist), elem->new,
		    list_elem_cmp_str) == NULL) {
			debug(1, "mail_create %s", elem->new);
			mail_create(serverstream, elem->new);
			srv = list_create_elem(elem->new);
			list_insert_last(serverlist, srv);
		}
		debug(1, "mail_open %s", elem->new);
		newstream = mail_open(serverstream, elem->new,
		    (debuglevel > 1 ? OP_DEBUG : NIL));
		if(newstream == NIL) {
			warnx("remote mail_open failed; skipping %s -> %s",
			    elem->old, elem->new);
			if(elem->tmp != NULL)
				unlink(elem->tmp);
			continue;
		} else {
			serverstream = newstream;
		}
		/* now open the local mailbox */
		debug(1, "mail_open %s",
		    elem->tmp != NULL ? elem->tmp : elem->old);
		clientstream = mail_open(NIL,
		    elem->tmp != NULL ? elem->tmp : elem->old,
		    (debuglevel > 1 ? OP_DEBUG : NIL) | OP_READONLY);
		if(clientstream == NIL) {
			warnx("local mail_open failed; skipping %s -> %s",
			    elem->old, elem->new);
			if(elem->tmp != NULL)
				unlink(elem->tmp);
			continue;
		}
		/* cache some metadata */
		debug(1, "mailbox has %d messages", clientstream->nmsgs);
		mail_fetchfast(clientstream,
		    xasprintf("1:%d", clientstream->nmsgs));
		/* do the copy */
		if(clientstream->nmsgs == 0) {
			warnx("%s contains no messages", elem->old);
		} else {
			warnx("uploading %s to %s", elem->old, elem->new);
			msgnum = 0;
			if(mail_append_multiple(serverstream, elem->new,
			    appendmessage, NULL) == NIL)
				warnx("upload failed!");
		}
		clientstream = mail_close(clientstream);
		if(elem->tmp != NULL)
			unlink(elem->tmp);
	}

	subs_file = fopen(".mailboxlist", "r");
	if(subs_file != NULL) {
		debug(1, "uploading local subscription list");
		while(fgetline(subs_file, buffer, sizeof(buffer))) {
			/* chomp any home directory prefix off the mailbox
			name and translate in the same way as we did before */
			if(strncmp(buffer, "/home/", 6) == 0 &&
			    strncmp(buffer+6, username, strlen(username)) == 0 &&
			    buffer[strlen(username)+6] == '/')
				s = buffer + strlen(username) + 7;
			else if(strncmp(buffer, "~/", 2) == 0)
				s = buffer + 2;
			else
				s = buffer;
			debug(3, "mangled %s to %s", buffer, s);
			if(strcmp(buffer, "INBOX") != 0) {
				elem = list_find(list_first(clientlist), s,
				    list_elem_cmp_str);
				if(elem != NULL) {
					debug(2, "%s -> %s", elem->old, elem->new);
					mail_subscribe(serverstream, elem->new);
				} else {
					debug(2, "ignoring %s", buffer);
				}
			} else {
				/* all the various local versions of INBOX */
				s = xasprintf("/var/mail/%s", username);
				debug(3, "checking %s", s);
				elem = list_find(list_first(clientlist), s,
				    list_elem_cmp_str);
				if(elem != NULL) {
					debug(2, "%s -> %s", s, elem->new);
					mail_subscribe(serverstream, elem->new);
				}
				s = xasprintf("/var/spool/mail/%s", username);
				debug(3, "checking %s", s);
				elem = list_find(list_first(clientlist), s,
				    list_elem_cmp_str);
				if(elem != NULL) {
					debug(2, "%s -> %s", s, elem->new);
					mail_subscribe(serverstream, elem->new);
				}
				s = xasprintf("%sINBOX", servername);
				debug(2, "%s -> %s", buffer, s);
				mail_subscribe(serverstream, s);
			}
		}
		fclose(subs_file);
		debug(2, "finished with subscription list");
	}

	debug(1, "doing mail_close %s", servername);
	serverstream = mail_close(serverstream);
	return(0);
}

/*!
 * \}
 */

/**********************************************************************/

/*!
 * \name message strings
 * \{
 */

static void
msg_string_init(STRING *s, void *data, unsigned long size)
{
	/* remember the length of the header in data1 */
	mail_fetchheader_full(clientstream, msgnum,
	    NIL, &s->data1, FT_PREFETCHTEXT);
	mail_fetchtext_full(clientstream, msgnum, &s->size, NIL);
	s->size += s->data1;
	SETPOS(s, 0);
}

static char
msg_string_next(STRING *s)
{
	char c = *s->curpos++;
	SETPOS(s, GETPOS(s));
	return(c);
}

static void
msg_string_setpos(STRING *s, unsigned long i)
{
	if (i < s->data1) {
		/* want header */
		s->chunk = mail_fetchheader(clientstream, msgnum);
		s->chunksize = s->data1;
		s->offset = 0;
	} else if (i < s->size) {
		/* want body */
		s->chunk = mail_fetchtext(clientstream, msgnum);
		s->chunksize = s->size - s->data1;
		s->offset = s->data1;
	} else {
		/* off end of message */
		s->chunk = NIL;
		s->chunksize = 1;
		s->offset = i;
	}
	i -= s->offset;
	s->curpos = s->chunk + i;
	s->cursize = s->chunksize - i;
}

STRINGDRIVER msg_string = {
	msg_string_init,
	msg_string_next,
	msg_string_setpos
};

/*!
 * \}
 */

/**********************************************************************/

/*!
 * \name c-client call-back functions.
 * \{
 */

static long
appendmessage(MAILSTREAM *stream, void *data,
    char **flags, char **date, STRING **message)
{
	MESSAGECACHE *elt;
	unsigned long uflg, i;

	/* free data from the previous message */
	if(msgflags != NULL) {
		free(msgflags);
		msgflags = NULL;
	}
	if(msgdate != NULL) {
		free(msgdate);
		msgdate = NULL;
	}
	mail_gc(clientstream, GC_TEXTS);
	/* get this message */
	msgnum++;
	if(msgnum > clientstream->nmsgs) {
		*message = NIL;
		return(T);
	}
	debug(1, "append message %d", msgnum);
	elt = mail_elt(clientstream, msgnum);
	/* format the message's flags */
	buffer[0] = '\0';
	buffer[1] = '\0';
	if(elt->seen)
		strcat(buffer, " \\Seen");
	if(elt->deleted)
		strcat(buffer, " \\Deleted");
	if(elt->flagged)
		strcat(buffer, " \\Flagged");
	if(elt->answered)
		strcat(buffer, " \\Answered");
	if(elt->draft)
		strcat(buffer, " \\Draft");
	uflg = elt->user_flags;
	for(;;) {
		i = find_rightmost_bit(&uflg);
		if(i == -1)
			break;
		strcat(buffer, " ");
		strcat(buffer, clientstream->user_flags[i]);
	}
	*flags = msgflags = xstrdup(buffer + 1);
	debug(2, "message flags %s", msgflags);
	mail_date(buffer, elt);
	*date = msgdate = xstrdup(buffer);
	debug(2, "message date %s", msgdate);
	*message = &msgstr;
	INIT(&msgstr, msg_string, NULL, elt->rfc822_size);
	return(T);
}

void
mm_searched(MAILSTREAM *stream, unsigned long msgno)
{
	debug(2, "mm_searched()");
}

void
mm_exists(MAILSTREAM *stream, unsigned long number)
{
	debug(2, "mm_exists()");
}

void
mm_expunged(MAILSTREAM *stream, unsigned long number)
{
	debug(2, "mm_expunged()");
}

void
mm_flags(MAILSTREAM *stream, unsigned long number)
{
	debug(2, "mm_flags()");
}

void
mm_list(MAILSTREAM *stream, int delim, char *name, long attr)
{
	struct elem *elem;

	debug(2, "mm_list: delim %c name %s attr %s%s%s%s", delim, name,
	    attr & LATT_NOINFERIORS ? "\\Noinferiors" : "",
	    attr & LATT_NOSELECT    ? "\\Noselect"    : "",
	    attr & LATT_MARKED      ? "\\Marked"      : "",
	    attr & LATT_UNMARKED    ? "\\Unmarked"    : "");
	elem = list_create_elem(name);
	elem->attr = attr;
	list_insert_last(serverlist, elem);
	debug(1, "Added to serverlist: %s", name);
}

void
mm_lsub(MAILSTREAM *stream, int delimiter, char *name, long attributes)
{
	debug(2, "mm_lsub()");
}

void
mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status)
{
	debug(2, "mm_status()");
}

void
mm_notify(MAILSTREAM *stream, char *string, long errflg)
{
	mm_log(string, errflg);
}

void
mm_log(char *string, long errflg)
{
	switch(errflg) {
	case BYE:
	case NIL:
		if(debuglevel > 0)
			warnx("info: %s", string);
		break;
	case PARSE:
	case WARN:
		warnx("warning: %s", string);
		break;
	case ERROR:
	default:
		warnx("error: %s", string);
		break;
	}
}

void
mm_dlog(char *string)
{
	debug(2, "%s", string);
}

void
mm_login(NETMBX *mb, char *u, char *p, long trial)
{
	strcpy(u, username);
	strcpy(p, password);
}

void
mm_critical(MAILSTREAM *stream)
{
	critical = T;
}

void
mm_nocritical(MAILSTREAM *stream)
{
	critical = NIL;
}

long
mm_diskerror(MAILSTREAM *stream, long errcode, long serious)
{
	return(T);
}

void
mm_fatal(char *string)
{
	warnx("mm_fatal: %s", string);
}

/*!
 * \}
 */

/**********************************************************************/

/* EOF 2hermes/imapupload.c */
