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) 2002
 *	Gunnar Ritter.  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 Gunnar Ritter
 *	and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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[] = "@(#)pop3.c	2.43 (gritter) 3/4/06";
#endif
#endif /* not lint */

#include "config.h"

#include "rcv.h"
#include "extern.h"
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>

#include "md5.h"

/*
 * Mail -- a mail program
 *
 * POP3 client.
 */

#ifdef	HAVE_SOCKETS
static int	verbose;

#define	POP3_ANSWER()	if (pop3_answer(mp) == STOP) \
				return STOP;
#define	POP3_OUT(x, y)	if (pop3_finish(mp) == STOP) \
				return STOP; \
			if (verbose) \
				fprintf(stderr, ">>> %s", x); \
			mp->mb_active |= (y); \
			if (swrite(&mp->mb_sock, x) == STOP) \
				return STOP;

static char	*pop3buf;
static size_t	pop3bufsize;
static sigjmp_buf	pop3jmp;
static sighandler_type savealrm;
static int	reset_tio;
static struct termios	otio;
static int	pop3keepalive;
static volatile int	pop3lock;

static void pop3_timer_off(void);
static enum okay pop3_answer(struct mailbox *mp);
static enum okay pop3_finish(struct mailbox *mp);
static void pop3catch(int s);
static void maincatch(int s);
static enum okay pop3_noop1(struct mailbox *mp);
static void pop3alarm(int s);
static enum okay pop3_pass(struct mailbox *mp, const char *pass);
static char *pop3_find_timestamp(const char *bp);
static enum okay pop3_apop(struct mailbox *mp, char *xuser, const char *pass,
		const char *ts);
static enum okay pop3_apop1(struct mailbox *mp,
		const char *user, const char *xp);
static int pop3_use_starttls(const char *uhp);
static int pop3_use_apop(const char *uhp);
static enum okay pop3_user(struct mailbox *mp, char *xuser, const char *pass,
		const char *uhp, const char *xserver);
static enum okay pop3_stat(struct mailbox *mp, off_t *size, int *count);
static enum okay pop3_list(struct mailbox *mp, int n, size_t *size);
static void pop3_init(struct mailbox *mp, int n);
static void pop3_dates(struct mailbox *mp);
static void pop3_setptr(struct mailbox *mp);
static char *pop3_have_password(const char *server);
static enum okay pop3_get(struct mailbox *mp, struct message *m,
		enum needspec need);
static enum okay pop3_exit(struct mailbox *mp);
static enum okay pop3_delete(struct mailbox *mp, int n);
static enum okay pop3_update(struct mailbox *mp);

static void 
pop3_timer_off(void)
{
	if (pop3keepalive > 0) {
		alarm(0);
		safe_signal(SIGALRM, savealrm);
	}
}

static enum okay 
pop3_answer(struct mailbox *mp)
{
	int sz;
	enum okay ok = STOP;

retry:	if ((sz = sgetline(&pop3buf, &pop3bufsize, NULL, &mp->mb_sock)) > 0) {
		if ((mp->mb_active & (MB_COMD|MB_MULT)) == MB_MULT)
			goto multiline;
		if (verbose)
			fputs(pop3buf, stderr);
		switch (*pop3buf) {
		case '+':
			ok = OKAY;
			mp->mb_active &= ~MB_COMD;
			break;
		case '-':
			ok = STOP;
			mp->mb_active = MB_NONE;
			fprintf(stderr, catgets(catd, CATSET, 218,
					"POP3 error: %s"), pop3buf);
			break;
		default:
			/*
			 * If the answer starts neither with '+' nor with
			 * '-', it must be part of a multiline response,
			 * e. g. because the user interrupted a file
			 * download. Get lines until a single dot appears.
			 */
	multiline:	 while (pop3buf[0] != '.' || pop3buf[1] != '\r' ||
					pop3buf[2] != '\n' ||
					pop3buf[3] != '\0') {
				sz = sgetline(&pop3buf, &pop3bufsize,
						NULL, &mp->mb_sock);
				if (sz <= 0)
					goto eof;
			}
			mp->mb_active &= ~MB_MULT;
			if (mp->mb_active != MB_NONE)
				goto retry;
		}
	} else {
	eof: 	ok = STOP;
		mp->mb_active = MB_NONE;
	}
	return ok;
}

static enum okay 
pop3_finish(struct mailbox *mp)
{
	while (mp->mb_sock.s_fd > 0 && mp->mb_active != MB_NONE)
		pop3_answer(mp);
	return OKAY;
}

static void 
pop3catch(int s)
{
	if (reset_tio)
		tcsetattr(0, TCSADRAIN, &otio);
	switch (s) {
	case SIGINT:
		fprintf(stderr, catgets(catd, CATSET, 102, "Interrupt\n"));
		siglongjmp(pop3jmp, 1);
		break;
	case SIGPIPE:
		fprintf(stderr, "Received SIGPIPE during POP3 operation\n");
		break;
	}
}

static void
maincatch(int s)
{
	if (interrupts++ == 0) {
		fprintf(stderr, catgets(catd, CATSET, 102, "Interrupt\n"));
		return;
	}
	onintr(0);
}

static enum okay 
pop3_noop1(struct mailbox *mp)
{
	POP3_OUT("NOOP\r\n", MB_COMD)
	POP3_ANSWER()
	return OKAY;
}

enum okay 
pop3_noop(void)
{
	enum okay	ok = STOP;
	sighandler_type	saveint, savepipe;

	(void)&saveint;
	(void)&savepipe;
	(void)&ok;
	verbose = value("verbose") != NULL;
	pop3lock = 1;
	if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
		safe_signal(SIGINT, maincatch);
	savepipe = safe_signal(SIGPIPE, SIG_IGN);
	if (sigsetjmp(pop3jmp, 1) == 0) {
		if (savepipe != SIG_IGN)
			safe_signal(SIGPIPE, pop3catch);
		ok = pop3_noop1(&mb);
	}
	safe_signal(SIGINT, saveint);
	safe_signal(SIGPIPE, savepipe);
	pop3lock = 0;
	return ok;
}

/*ARGSUSED*/
static void 
pop3alarm(int s)
{
	sighandler_type	saveint;
	sighandler_type savepipe;

	if (pop3lock++ == 0) {
		if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
			safe_signal(SIGINT, maincatch);
		savepipe = safe_signal(SIGPIPE, SIG_IGN);
		if (sigsetjmp(pop3jmp, 1)) {
			safe_signal(SIGINT, saveint);
			safe_signal(SIGPIPE, savepipe);
			goto brk;
		}
		if (savepipe != SIG_IGN)
			safe_signal(SIGPIPE, pop3catch);
		if (pop3_noop1(&mb) != OKAY) {
			safe_signal(SIGINT, saveint);
			safe_signal(SIGPIPE, savepipe);
			goto out;
		}
		safe_signal(SIGINT, saveint);
		safe_signal(SIGPIPE, savepipe);
	}
brk:	alarm(pop3keepalive);
out:	pop3lock--;
}

static enum okay 
pop3_pass(struct mailbox *mp, const char *pass)
{
	char o[LINESIZE];

	snprintf(o, sizeof o, "PASS %s\r\n", pass);
	POP3_OUT(o, MB_COMD)
	POP3_ANSWER()
	return OKAY;
}

static char *
pop3_find_timestamp(const char *bp)
{
	const char	*cp, *ep;
	char	*rp;
	int	hadat = 0;

	if ((cp = strchr(bp, '<')) == NULL)
		return NULL;
	for (ep = cp; *ep; ep++) {
		if (spacechar(*ep&0377))
			return NULL;
		else if (*ep == '@')
			hadat = 1;
		else if (*ep == '>') {
			if (hadat != 1)
				return NULL;
			break;
		}
	}
	if (*ep != '>')
		return NULL;
	rp = salloc(ep - cp + 2);
	memcpy(rp, cp, ep - cp + 1);
	rp[ep - cp + 1] = '\0';
	return rp;
}

static enum okay 
pop3_apop(struct mailbox *mp, char *xuser, const char *pass, const char *ts)
{
	char	*user, *catp, *xp;
	unsigned char	digest[16];
	MD5_CTX	ctx;

retry:	if (xuser == NULL) {
		if ((user = getuser()) == NULL)
			return STOP;
	} else
		user = xuser;
	if (pass == NULL) {
		if ((pass = getpassword(&otio, &reset_tio, NULL)) == NULL)
			return STOP;
	}
	catp = savecat(ts, pass);
	MD5Init(&ctx);
	MD5Update(&ctx, (unsigned char *)catp, strlen(catp));
	MD5Final(digest, &ctx);
	xp = md5tohex(digest);
	if (pop3_apop1(mp, user, xp) == STOP) {
		pass = NULL;
		goto retry;
	}
	return OKAY;
}

static enum okay 
pop3_apop1(struct mailbox *mp, const char *user, const char *xp)
{
	char	o[LINESIZE];

	snprintf(o, sizeof o, "APOP %s %s\r\n", user, xp);
	POP3_OUT(o, MB_COMD)
	POP3_ANSWER()
	return OKAY;
}

static int 
pop3_use_starttls(const char *uhp)
{
	char	*var;

	if (value("pop3-use-starttls"))
		return 1;
	var = savecat("pop3-use-starttls-", uhp);
	return value(var) != NULL;
}

static int 
pop3_use_apop(const char *uhp)
{
	char	*var;

	if (value("pop3-use-apop"))
		return 1;
	var = savecat("pop3-use-apop-", uhp);
	return value(var) != NULL;
}

static enum okay 
pop3_user(struct mailbox *mp, char *xuser, const char *pass,
		const char *uhp, const char *xserver)
{
	char o[LINESIZE], *user, *ts = NULL, *server, *cp;

	POP3_ANSWER()
	if (pop3_use_apop(uhp)) {
		if ((ts = pop3_find_timestamp(pop3buf)) == NULL) {
			fprintf(stderr, "Could not determine timestamp from "
				"server greeting. Impossible to use APOP.\n");
			return STOP;
		}
	}
	if ((cp = strchr(xserver, ':')) != NULL) {
		server = salloc(cp - xserver + 1);
		memcpy(server, xserver, cp - xserver);
		server[cp - xserver] = '\0';
	} else
		server = (char *)xserver;
#ifdef	USE_SSL
	if (mp->mb_sock.s_use_ssl == 0 && pop3_use_starttls(uhp)) {
		POP3_OUT("STLS\r\n", MB_COMD)
		POP3_ANSWER()
		if (ssl_open(server, &mp->mb_sock, uhp) != OKAY)
			return STOP;
	}
#else	/* !USE_SSL */
	if (pop3_use_starttls(uhp)) {
		fprintf(stderr, "No SSL support compiled in.\n");
		return STOP;
	}
#endif	/* !USE_SSL */
	if (ts != NULL)
		return pop3_apop(mp, xuser, pass, ts);
retry:	if (xuser == NULL) {
		if ((user = getuser()) == NULL)
			return STOP;
	} else
		user = xuser;
	snprintf(o, sizeof o, "USER %s\r\n", user);
	POP3_OUT(o, MB_COMD)
	POP3_ANSWER()
	if (pass == NULL) {
		if ((pass = getpassword(&otio, &reset_tio, NULL)) == NULL)
			return STOP;
	}
	if (pop3_pass(mp, pass) == STOP) {
		pass = NULL;
		goto retry;
	}
	return OKAY;
}

static enum okay
pop3_stat(struct mailbox *mp, off_t *size, int *count)
{
	char *cp;
	enum okay ok = OKAY;

	POP3_OUT("STAT\r\n", MB_COMD);
	POP3_ANSWER()
	for (cp = pop3buf; *cp && !spacechar(*cp & 0377); cp++);
	while (*cp && spacechar(*cp & 0377))
		cp++;
	if (*cp) {
		*count = (int)strtol(cp, NULL, 10);
		while (*cp && !spacechar(*cp & 0377))
			cp++;
		while (*cp && spacechar(*cp & 0377))
			cp++;
		if (*cp)
			*size = (int)strtol(cp, NULL, 10);
		else
			ok = STOP;
	} else
		ok = STOP;
	if (ok == STOP)
		fprintf(stderr, catgets(catd, CATSET, 260,
			"invalid POP3 STAT response: %s\n"), pop3buf);
	return ok;
}

static enum okay
pop3_list(struct mailbox *mp, int n, size_t *size)
{
	char o[LINESIZE], *cp;

	snprintf(o, sizeof o, "LIST %u\r\n", n);
	POP3_OUT(o, MB_COMD)
	POP3_ANSWER()
	for (cp = pop3buf; *cp && !spacechar(*cp & 0377); cp++);
	while (*cp && spacechar(*cp & 0377))
		cp++;
	while (*cp && !spacechar(*cp & 0377))
		cp++;
	while (*cp && spacechar(*cp & 0377))
		cp++;
	if (*cp)
		*size = (size_t)strtol(cp, NULL, 10);
	else
		*size = 0;
	return OKAY;
}

static void 
pop3_init(struct mailbox *mp, int n)
{
	struct message *m = &message[n];
	char *cp;

	m->m_flag = MUSED|MNEW|MNOFROM|MNEWEST;
	m->m_block = 0;
	m->m_offset = 0;
	pop3_list(mp, m - message + 1, &m->m_xsize);
	if ((cp = hfield("status", m)) != NULL) {
		while (*cp != '\0') {
			if (*cp == 'R')
				m->m_flag |= MREAD;
			else if (*cp == 'O')
				m->m_flag &= ~MNEW;
			cp++;
		}
	}
}

/*ARGSUSED*/
static void 
pop3_dates(struct mailbox *mp)
{
	int	i;

	for (i = 0; i < msgCount; i++)
		substdate(&message[i]);
}

static void 
pop3_setptr(struct mailbox *mp)
{
	int i;

	message = scalloc(msgCount + 1, sizeof *message);
	for (i = 0; i < msgCount; i++)
		pop3_init(mp, i);
	setdot(message);
	message[msgCount].m_size = 0;
	message[msgCount].m_lines = 0;
	pop3_dates(mp);
}

static char *
pop3_have_password(const char *server)
{
	char *var, *cp;

	var = ac_alloc(strlen(server) + 10);
	strcpy(var, "password-");
	strcpy(&var[9], server);
	if ((cp = value(var)) != NULL)
		cp = savestr(cp);
	ac_free(var);
	return cp;
}

int 
pop3_setfile(const char *server, int newmail, int isedit)
{
	struct sock	so;
	sighandler_type	saveint;
	sighandler_type savepipe;
	char *user;
	const char *cp, *sp = server, *pass, *uhp;
	int use_ssl = 0;

	(void)&sp;
	(void)&use_ssl;
	(void)&user;
	if (newmail)
		return 1;
	if (strncmp(sp, "pop3://", 7) == 0) {
		sp = &sp[7];
		use_ssl = 0;
#ifdef	USE_SSL
	} else if (strncmp(sp, "pop3s://", 8) == 0) {
		sp = &sp[8];
		use_ssl = 1;
#endif	/* USE_SSL */
	}
	uhp = sp;
	pass = pop3_have_password(uhp);
	if ((cp = last_at_before_slash(sp)) != NULL) {
		user = salloc(cp - sp + 1);
		memcpy(user, sp, cp - sp);
		user[cp - sp] = '\0';
		sp = &cp[1];
		user = strdec(user);
	} else
		user = NULL;
	verbose = value("verbose") != NULL;
	if (sopen(sp, &so, use_ssl, uhp, use_ssl ? "pop3s" : "pop3",
				verbose) != OKAY) {
		return -1;
	}
	quit();
	edit = isedit;
	if (mb.mb_sock.s_fd >= 0)
		sclose(&mb.mb_sock);
	if (mb.mb_itf) {
		fclose(mb.mb_itf);
		mb.mb_itf = NULL;
	}
	if (mb.mb_otf) {
		fclose(mb.mb_otf);
		mb.mb_otf = NULL;
	}
	initbox(server);
	mb.mb_type = MB_VOID;
	pop3lock = 1;
	mb.mb_sock = so;
	saveint = safe_signal(SIGINT, SIG_IGN);
	savepipe = safe_signal(SIGPIPE, SIG_IGN);
	if (sigsetjmp(pop3jmp, 1)) {
		sclose(&mb.mb_sock);
		safe_signal(SIGINT, saveint);
		safe_signal(SIGPIPE, savepipe);
		pop3lock = 0;
		return 1;
	}
	if (saveint != SIG_IGN)
		safe_signal(SIGINT, pop3catch);
	if (savepipe != SIG_IGN)
		safe_signal(SIGPIPE, pop3catch);
	if ((cp = value("pop3-keepalive")) != NULL) {
		if ((pop3keepalive = strtol(cp, NULL, 10)) > 0) {
			savealrm = safe_signal(SIGALRM, pop3alarm);
			alarm(pop3keepalive);
		}
	}
	mb.mb_sock.s_desc = "POP3";
	mb.mb_sock.s_onclose = pop3_timer_off;
	if (pop3_user(&mb, user, pass, uhp, sp) != OKAY ||
			pop3_stat(&mb, &mailsize, &msgCount) != OKAY) {
		sclose(&mb.mb_sock);
		pop3_timer_off();
		safe_signal(SIGINT, saveint);
		safe_signal(SIGPIPE, savepipe);
		pop3lock = 0;
		return 1;
	}
	mb.mb_type = MB_POP3;
	mb.mb_perm = Rflag ? 0 : MB_DELE;
	pop3_setptr(&mb);
	setmsize(msgCount);
	sawcom = 0;
	safe_signal(SIGINT, saveint);
	safe_signal(SIGPIPE, savepipe);
	pop3lock = 0;
	if (!edit && msgCount == 0) {
		if (mb.mb_type == MB_POP3 && value("emptystart") == NULL)
			fprintf(stderr, catgets(catd, CATSET, 258,
				"No mail at %s\n"), server);
		return 1;
	}
	return 0;
}

static enum okay 
pop3_get(struct mailbox *mp, struct message *m, enum needspec need)
{
	sighandler_type	saveint = SIG_IGN;
	sighandler_type savepipe = SIG_IGN;
	off_t offset;
	char o[LINESIZE], *line = NULL, *lp;
	size_t linesize = 0, linelen, size;
	int number = m - message + 1;
	int emptyline = 0, lines;

	(void)&saveint;
	(void)&savepipe;
	(void)&number;
	(void)&emptyline;
	(void)&need;
	verbose = value("verbose") != NULL;
	if (mp->mb_sock.s_fd < 0) {
		fprintf(stderr, catgets(catd, CATSET, 219,
				"POP3 connection already closed.\n"));
		return STOP;
	}
	if (pop3lock++ == 0) {
		if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
			safe_signal(SIGINT, maincatch);
		savepipe = safe_signal(SIGPIPE, SIG_IGN);
		if (sigsetjmp(pop3jmp, 1)) {
			safe_signal(SIGINT, saveint);
			safe_signal(SIGPIPE, savepipe);
			pop3lock--;
			return STOP;
		}
		if (savepipe != SIG_IGN)
			safe_signal(SIGPIPE, pop3catch);
	}
	fseek(mp->mb_otf, 0L, SEEK_END);
	offset = ftell(mp->mb_otf);
retry:	switch (need) {
	case NEED_HEADER:
		snprintf(o, sizeof o, "TOP %u 0\r\n", number);
		break;
	case NEED_BODY:
		snprintf(o, sizeof o, "RETR %u\r\n", number);
		break;
	case NEED_UNSPEC:
		abort();
	}
	POP3_OUT(o, MB_COMD|MB_MULT)
	if (pop3_answer(mp) == STOP) {
		if (need == NEED_HEADER) {
			/*
			 * The TOP POP3 command is optional, so retry
			 * with the entire message.
			 */
			need = NEED_BODY;
			goto retry;
		}
		if (interrupts)
			onintr(0);
		return STOP;
	}
	size = 0;
	lines = 0;
	while (sgetline(&line, &linesize, &linelen, &mp->mb_sock) > 0) {
		if (line[0] == '.' && line[1] == '\r' && line[2] == '\n' &&
				line[3] == '\0') {
			mp->mb_active &= ~MB_MULT;
			break;
		}
		if (line[0] == '.') {
			lp = &line[1];
			linelen--;
		} else
			lp = line;
		/*
		 * Need to mask 'From ' lines. This cannot be done properly
		 * since some servers pass them as 'From ' and others as
		 * '>From '. Although one could identify the first kind of
		 * server in principle, it is not possible to identify the
		 * second as '>From ' may also come from a server of the
		 * first type as actual data. So do what is absolutely
		 * necessary only - mask 'From '.
		 *
		 * If the line is the first line of the message header, it
		 * is likely a real 'From ' line. In this case, it is just
		 * ignored since it violates all standards.
		 */
		if (lp[0] == 'F' && lp[1] == 'r' && lp[2] == 'o' &&
				lp[3] == 'm' && lp[4] == ' ') {
			if (lines != 0) {
				fputc('>', mp->mb_otf);
				size++;
			} else
				continue;
		}
		lines++;
		if (lp[linelen-1] == '\n' && (linelen == 1 ||
					lp[linelen-2] == '\r')) {
			emptyline = linelen <= 2;
			if (linelen > 2)
				fwrite(lp, 1, linelen - 2, mp->mb_otf);
			fputc('\n', mp->mb_otf);
			size += linelen - 1;
		} else {
			emptyline = 0;
			fwrite(lp, 1, linelen, mp->mb_otf);
			size += linelen;
		}
	}
	if (!emptyline) {
		/*
		 * This is very ugly; but some POP3 daemons don't end a
		 * message with \r\n\r\n, and we need \n\n for mbox format.
		 */
		fputc('\n', mp->mb_otf);
		lines++;
		size++;
	}
	m->m_size = size;
	m->m_lines = lines;
	m->m_block = mailx_blockof(offset);
	m->m_offset = mailx_offsetof(offset);
	fflush(mp->mb_otf);
	switch (need) {
	case NEED_HEADER:
		m->m_have |= HAVE_HEADER;
		break;
	case NEED_BODY:
		m->m_have |= HAVE_HEADER|HAVE_BODY;
		m->m_xlines = m->m_lines;
		m->m_xsize = m->m_size;
		break;
	case NEED_UNSPEC:
		break;
	}
	if (line)
		free(line);
	if (saveint != SIG_IGN)
		safe_signal(SIGINT, saveint);
	if (savepipe != SIG_IGN)
		safe_signal(SIGPIPE, savepipe);
	pop3lock--;
	if (interrupts)
		onintr(0);
	return OKAY;
}

enum okay 
pop3_header(struct message *m)
{
	return pop3_get(&mb, m, NEED_HEADER);
}


enum okay 
pop3_body(struct message *m)
{
	return pop3_get(&mb, m, NEED_BODY);
}

static enum okay 
pop3_exit(struct mailbox *mp)
{
	POP3_OUT("QUIT\r\n", MB_COMD)
	POP3_ANSWER()
	return OKAY;
}

static enum okay 
pop3_delete(struct mailbox *mp, int n)
{
	char o[LINESIZE];

	snprintf(o, sizeof o, "DELE %u\r\n", n);
	POP3_OUT(o, MB_COMD)
	POP3_ANSWER()
	return OKAY;
}

static enum okay 
pop3_update(struct mailbox *mp)
{
	FILE *readstat = NULL;
	struct message *m;
	int dodel, c, gotcha, held;

	if (Tflag != NULL) {
		if ((readstat = Zopen(Tflag, "w", NULL)) == NULL)
			Tflag = NULL;
	}
	if (!edit) {
		holdbits();
		for (m = &message[0], c = 0; m < &message[msgCount]; m++) {
			if (m->m_flag & MBOX)
				c++;
		}
		if (c > 0)
			makembox();
	}
	for (m = &message[0], gotcha=0, held=0; m < &message[msgCount]; m++) {
		if (readstat != NULL && (m->m_flag & (MREAD|MDELETED)) != 0) {
			char *id;

			if ((id = hfield("message-id", m)) != NULL ||
					(id = hfield("article-id", m)) != NULL)
				fprintf(readstat, "%s\n", id);
		}
		if (edit) {
			dodel = m->m_flag & MDELETED;
		} else {
			dodel = !((m->m_flag&MPRESERVE) ||
					(m->m_flag&MTOUCH) == 0);
		}
		if (dodel) {
			pop3_delete(mp, m - message + 1);
			gotcha++;
		} else
			held++;
	}
	if (readstat != NULL)
		Fclose(readstat);
	if (gotcha && edit) {
		printf(catgets(catd, CATSET, 168, "\"%s\" "), mailname);
		printf(value("bsdcompat") || value("bsdmsgs") ?
				catgets(catd, CATSET, 170, "complete\n") :
				catgets(catd, CATSET, 212, "updated.\n"));
	} else if (held && !edit) {
		if (held == 1)
			printf(catgets(catd, CATSET, 155,
				"Held 1 message in %s\n"), mailname);
		else if (held > 1)
			printf(catgets(catd, CATSET, 156,
				"Held %d messages in %s\n"), held, mailname);
	}
	fflush(stdout);
	return OKAY;
}

void 
pop3_quit(void)
{
	sighandler_type	saveint;
	sighandler_type savepipe;

	verbose = value("verbose") != NULL;
	if (mb.mb_sock.s_fd < 0) {
		fprintf(stderr, catgets(catd, CATSET, 219,
				"POP3 connection already closed.\n"));
		return;
	}
	pop3lock = 1;
	saveint = safe_signal(SIGINT, SIG_IGN);
	savepipe = safe_signal(SIGPIPE, SIG_IGN);
	if (sigsetjmp(pop3jmp, 1)) {
		safe_signal(SIGINT, saveint);
		safe_signal(SIGPIPE, saveint);
		pop3lock = 0;
		return;
	}
	if (saveint != SIG_IGN)
		safe_signal(SIGINT, pop3catch);
	if (savepipe != SIG_IGN)
		safe_signal(SIGPIPE, pop3catch);
	pop3_update(&mb);
	pop3_exit(&mb);
	sclose(&mb.mb_sock);
	safe_signal(SIGINT, saveint);
	safe_signal(SIGPIPE, savepipe);
	pop3lock = 0;
}
#else	/* !HAVE_SOCKETS */
static void 
nopop3(void)
{
	fprintf(stderr, catgets(catd, CATSET, 216,
				"No POP3 support compiled in.\n"));
}

int 
pop3_setfile(const char *server, int newmail, int isedit)
{
	nopop3();
	return -1;
}

enum okay 
pop3_header(struct message *mp)
{
	nopop3();
	return STOP;
}

enum okay 
pop3_body(struct message *mp)
{
	nopop3();
	return STOP;
}

void 
pop3_quit(void)
{
	nopop3();
}

enum okay 
pop3_noop(void)
{
	nopop3();
	return STOP;
}
#endif	/* HAVE_SOCKETS */