/* $Id: pop.cpp,v 1.31.2.1 2004/01/16 04:43:18 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library General Public License as published *
 *   by the Free Software Foundation; either version 2 of the License, or      *
 *   (at your option) any later version.                                       *
 *                                                                             *
 *   This program is distributed in the hope that it will be useful,           *
 *   but WITHOUT ANY WARRANTY, without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 *   GNU Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library General Public License *
 *   along with this program; if not, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 ******************************************************************************/


#include <fmail.h>
#include <umail.h>
#include <cfgfile.h>
#include <connection.h>
#include <connectionManager.h>
#include "md5.h"

#ifdef WITH_THREADS
	#include <pthread.h>
#endif

#ifdef USE_GPASSWD
#include <gpasswd.h>
extern gPasswd Passwd;
#endif

extern cfgfile Config;
extern void pop_account(struct _pop_src *pop);

list<connection> Connections;
connectionManager ConMan;


struct _uidl *get_popmsg_by_uidl(struct _pop_src *pop, char *uidl);
long get_popmsg_num(struct _pop_src *pop);
void free_uidlist(struct _pop_src *pop);
void save_uidlist(struct _pop_src *pop);
void compare_uidlist(struct _pop_src *pop);
int check_uidlist(struct _pop_src *pop, char *uidstr);
void delete_uidlist(struct _pop_src *pop, char *uidstr);

int get_ipc_sock(struct sockaddr_in *sin) {
	int sock;
#ifdef  _linuxalpha_
	long sinlen;
#else
	int sinlen;
#endif


	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		display_msg(MSG_LOG, "ipc", "can not get socket");
		return -1;
	}

	sinlen = sizeof(struct sockaddr_in);
	bzero((char *) sin, sinlen);

	sin->sin_port = htons(0);

	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = INADDR_ANY;
	if(bind(sock, (struct sockaddr *) sin,
			sizeof(struct sockaddr_in)) < 0) {
		display_msg(MSG_LOG, "ipc", "bind failed");
		return -1;
	}
	/* kovi: glibc2.1.x defined 3rd param as socklen_t type, which is
	unsigned int, in glibc2.0 3rd param was of size_t type (long int on linux ?),
	for earlier libs int worked */  
	if(getsockname(sock, (struct sockaddr *) sin, (unsigned int *) & sinlen)
	   < 0) {
		display_msg(MSG_LOG, "ipc", "getsockname failed");
		return -1;
	}
	return sock;
}


#ifdef WITH_THREADS
extern "C" void gethostbystring_failed(int i) {
	pthread_exit(NULL);
}

extern "C" void *gethostbystring_thread(void *s) {
	struct hostent *hp;

	signal(SIGALRM, gethostbystring_failed);
	alarm(2);

	hp = gethostbyname((char *) s);
	alarm(0);
	pthread_exit((void *) hp);
}
#endif

extern "C" struct hostent *gethostbystring(char *s) {

#if 0 //WITH_THREADS
	struct hostent **hpp;
	pthread_t resolv_thread;
#endif

	struct hostent *hp;
	char *haddr[1];

#ifdef __osf__
	static in_addr_t iaddr;
#else
	static long iaddr;
#endif


#if 0 //WITH_THREADS
	pthread_create(&resolv_thread, NULL, gethostbystring_thread,
				   (void *) s);
	pthread_join(resolv_thread, (void **) hpp);

	if(*hpp == NULL) {
#else
	if((hp = gethostbyname(s)) == NULL) {
#endif
		if((iaddr = inet_addr(s)) == -1) {
			display_msg(MSG_LOG, "resolver", "Unable to resolve host: %s",
						s);
			return NULL;
		} else {
			hp = (struct hostent *) malloc(sizeof(struct hostent));
			hp->h_length = sizeof(iaddr);
			memcpy (&iaddr, hp->h_addr_list[0], hp->h_length);
			hp->h_addr_list = haddr;
			hp->h_addr = (char *) &iaddr;
			hp->h_addrtype = AF_INET;
			return hp;
		}
	}
#if 0 //WITH_THREADS
	return *hpp;
#else
	return hp;
#endif
}

int getdata(char *s, long n, FILE * iop, FILE * ofile) {
	class connection *conn;
	char buf[128], *p, *p1, *lbuf;
	long rdone, res;

	if((conn = ConMan.get_conn(fileno(iop))) == NULL)
		return -1;
	lbuf = conn->getBuf();

	if(n == 0)
		return 0;

	rdone = 0;

	if(ofile)
		p = buf;
	else
		p = s;

	if((res = (long) strlen(lbuf)) > 0) {
		if(res >= n) {
			if(ofile) {
				if(fwrite(lbuf, (size_t) n, 1, ofile) != 1) {
					display_msg(MSG_WARN, "getdata", "Write failed");
					return -1;
				}
			} else {
				strncpy(s, lbuf, (int) n);
				s[n] = '\0';
			}

			strcpy(buf, lbuf + n);
			strcpy(lbuf, buf);
			return 0;
		}

		if(ofile) {
			if(fputs(lbuf, ofile) == EOF) {
				display_msg(MSG_WARN, "recv", "Write failed!");
				return -1;
			}
		} else {
			strcpy(s, lbuf);
			p += res;
		}

		lbuf[0] = '\0';
		rdone = res;
	}

	if((res = my_check_io_forms(fileno(iop), 0, SOCKET_TIMEOUT)) < 0) {
		lbuf[0] = '\0';
		return res;
	}

	while(rdone < n) {
		if(
		  (res =
		   read(fileno(iop), p,
				(int) (n - rdone) > 127 ? 127 : (int) (n - rdone))) ==
		  -1) {
			if((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
				if(
				  (res =
				   my_check_io_forms(fileno(iop), 0,
									 SOCKET_TIMEOUT)) < 0) {
					lbuf[0] = '\0';
					return res;
				}

				continue;
			}
			display_msg(MSG_WARN, "recv: getdata", "connection error");
			lbuf[0] = '\0';
			return -1;
		}
		if(res == 0) {
			display_msg(MSG_WARN, "recv: getdata",
						"connection closed by foreign host");
			lbuf[0] = '\0';
			return -1;
		}

		p[res] = '\0';
		rdone += res;

		p1 = p;
		while((p1 = strchr(p1, '\r')) != NULL)
			memmove(p1, p1 + 1, strlen(p1));

		if(ofile) {
			if(fputs(p, ofile) == EOF) {
				display_msg(MSG_WARN, "recv: getdata", "Write failed!");
				return -1;
			}
		} else
			p += strlen(p);
	}

	return 0;
}

char *getline(char *s, int n, FILE * iop) {
	class connection *conn;
	char tbuf[128], *p, *p1, *lbuf;
	int len, res, allocbuf, len1;

	if((conn = ConMan.get_conn(fileno(iop))) == NULL)
		return NULL;
	lbuf = conn->getBuf();

	if(n < 0) {
		n = abs(n);
		allocbuf = 1;
	} else
		allocbuf = 0;

	if((len = strlen(lbuf)) > 0) {
		if((p = strchr(lbuf, '\n')) != NULL) {
			p1 = p + 1;
			*p = '\0';
			p--;
			if(*p == '\r')
				*p = '\0';
			if(allocbuf)
				s = (char *) malloc(strlen(lbuf) + 1);
			strcpy(s, lbuf);
			strcpy(tbuf, p1);
			strcpy(lbuf, tbuf);

			return s;
		}
		if(allocbuf) {
			s = (char *) malloc(strlen(lbuf) + 1);
			allocbuf = strlen(lbuf) + 1;
		}

		strcpy(s, lbuf);
		p = s + len;
		lbuf[0] = '\0';
	} else {
		if(allocbuf) {
			s = (char *) malloc(1);
			allocbuf = 1;
		}
		s[0] = '\0';
		p = s;
		len = 0;
	}

	if((res = my_check_io_forms(fileno(iop), 0, SOCKET_TIMEOUT)) < 0) {
		lbuf[0] = '\0';
		if(allocbuf)
			free(s);
		return((res == -2) && allocbuf) ? strdup("") : NULL;
	}

	while(len < n) {
		len1 = (n - len) > 127 ? 127 : (n - len);
		if(allocbuf) {
			allocbuf += (len1 + 1);
			s = (char *) realloc(s, allocbuf);
			p = s + strlen(s);
		}

		if((res = read(fileno(iop), p, len1)) == -1) {
			if((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
				if(
				  (res =
				   my_check_io_forms(fileno(iop), 0,
									 SOCKET_TIMEOUT)) < 0) {
					lbuf[0] = '\0';
					if(allocbuf)
						free(s);
					return((res == -2) && allocbuf) ? strdup("") : NULL;
				}

				continue;
			}
			display_msg(MSG_WARN, "recv: getline", "connection error");
			if(allocbuf)
				free(s);
			lbuf[0] = '\0';
			return NULL;
		}
		if(res == 0)
			break;

		p[res] = '\0';
		if((p1 = strchr(p, '\n')) != NULL) {
			*p1 = '\0';
			strcpy(lbuf, p1 + 1);
			len += (p1 - p);
			p1--;
			if(*p1 == '\r')
				*p1 = '\0';

			break;
		}
		p += res;
		len += res;
	}

	s[len] = '\0';

	if(len >= n) {
		if(logging & LOG_NET)
			display_msg(MSG_LOG, "recv: getline",
						"string is too long, splitting");
		return s;
	}

	if((len == 0) && (*lbuf == '\0')) {
		display_msg(MSG_WARN, "recv: getline",
					"connection closed by foreign host");
		if(allocbuf)
			free(s);
		return NULL;
	}

	return s;
}

/*
 * Writes to a socket may not write all the data. This function checks the
 * length written and makes sure all data is written.
 */
int fullwrite(int fd, char *buf, int len) {
	int n;
	int todo;
	char *bufptr;

	todo = len;
	bufptr = buf;
	while (todo > 0) {
		n = write(fd, bufptr, todo);
		if (n == -1) {
			if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
			  continue;
			}
			return (-1);
		}
		todo -= n;
		bufptr += n;
	}

	return len;
}

int putdata(char *s, int n, FILE * iop, FILE * ifile) {
	class connection *conn;
	char sbuf[512], lastchar, *lbuf;
	int sent, chunk, res;

	if((conn = ConMan.get_conn(fileno(iop))) == NULL)
		return -1;
	lbuf = conn->getBuf();

	if(s) {
		chwrite1:
		if((res = my_check_io_forms(fileno(iop), 1, SOCKET_TIMEOUT)) < 0) {
			lbuf[0] = '\0';
			return res;
		}

		if(fullwrite(fileno(iop), s, n) == -1) {
			if((errno == EAGAIN) || (errno == EWOULDBLOCK))
				goto chwrite1;

			display_msg(MSG_WARN, "send", "connection lost");
			lbuf[0] = '\0';
			return -1;
		}

	} else {
		sent = 0;
		lastchar = '\0';

		while(sent < n) {
			if(fgets(sbuf, 511, ifile) == NULL) {
				if(ferror(ifile))
					return -1;
				if(feof(ifile))
					break;
			}

			chunk = strlen(sbuf);
			if(chunk && (sbuf[chunk - 1] == '\n')) {
				if(chunk > 1)
					lastchar = sbuf[chunk - 2];

				if(lastchar != '\r') {
					sbuf[chunk - 1] = '\r';
					chunk++;
					sbuf[chunk - 1] = '\n';
					sbuf[chunk] = '\0';
				}

				lastchar = '\n';
			} else
				lastchar = chunk ? sbuf[chunk - 1] : '\0';

			if((res = my_check_io_forms(fileno(iop), 1, SOCKET_TIMEOUT)) <
			   0) {
				lbuf[0] = '\0';
				return res;
			}

			if(fullwrite(fileno(iop), sbuf, chunk) == -1) {
				if((errno == EAGAIN) || (errno == EWOULDBLOCK))
					continue;

				display_msg(MSG_WARN, "send", "connection lost");
				lbuf[0] = '\0';
				return -1;
			}

			sent += chunk;
		}
	}

	chwrite2:
	if((res = my_check_io_forms(fileno(iop), 1, SOCKET_TIMEOUT)) < 0) {
		lbuf[0] = '\0';
		return res;
	}

	if(write(fileno(iop), "\r\n", 2) == -1) {
		if((errno == EAGAIN) || (errno == EWOULDBLOCK))
			goto chwrite2;

		display_msg(MSG_WARN, "send", "connection lost");
		lbuf[0] = '\0';
		return -1;
	}

	return 0;
}

int putline(char *s, FILE * iop) {
	class connection *conn;
	char sbuf[512], *lbuf;
	int res;

	if((conn = ConMan.get_conn(fileno(iop))) == NULL)
		return -1;
	lbuf = conn->getBuf();

	if(strlen(s) >= 510) {
		display_msg(MSG_WARN, "send", "line too long");
		return -1;
	}

	snprintf(sbuf, sizeof(sbuf), "%s\r\n", s);
	chwrite:
	if((res = my_check_io_forms(fileno(iop), 1, SOCKET_TIMEOUT)) < 0)
		return res;

	if(fullwrite(fileno(iop), sbuf, strlen(sbuf)) == -1) {
		if((errno == EAGAIN) || (errno == EWOULDBLOCK))
			goto chwrite;

		display_msg(MSG_WARN, "send", "connection lost");
		lbuf[0] = '\0';
		return -1;
	}

	return 0;
}
#if STDC_HEADERS || defined(HAVE_STDARG_H)
char *pop_command(struct _pop_src *pop, char *fmt, ...)
#else
char *pop_command(struct _pop_src *pop, char *fmt, va_dcl
				  va_list)
#endif
{
	va_list ap;
	static char commandln[514];

	/* If the pop socket is closed, don't even try sending. */
	if (pop->pop_out == NULL)
		return NULL;

#if STDC_HEADERS || defined(HAVE_STDARG_H)
	va_start(ap, fmt);
#else
	va_start(ap);
#endif

	vsnprintf(commandln, sizeof(commandln), fmt, ap);

	va_end(ap);

	if(pop->flags & PSRC_LOGSESSION) {
		if(strncasecmp(commandln, "pass ", 5))
			display_msg(MSG_LOG, "pop", "-> %-.127s", commandln);
		else
			display_msg(MSG_LOG, "pop", "-> PASS *******");
	}

	if(putline(commandln, pop->pop_out) == -1)
		return NULL;

	pop->response[0] = '\0';

	if(getline(pop->response, sizeof(pop->response) - 1, pop->pop_in) == NULL)
		return NULL;

	if(pop->flags & PSRC_LOGSESSION)
		display_msg(MSG_LOG, "pop", "<- %-.127s", pop->response);

	if(pop->response[0] == '+')
		return pop->response;

	if(strncasecmp(fmt, "UIDL", 4)
	   && !strncasecmp(pop->response, "-ERR ", 4)) display_msg(MSG_WARN,
															   "pop",
															   "%-.127s",
															   pop->
															   response +
															   4);

	return NULL;
}

void pop_close(struct _pop_src *pop) {
	struct _uidl *ucache;

	if (pop->popsock != -1)
		ConMan.del_cinfo(pop->popsock);

	while(pop->uidlcache) {
		ucache = pop->uidlcache;
		pop->uidlcache = pop->uidlcache->next;
		free(ucache);
	}

	if(pop->pop_in != NULL)
		fclose(pop->pop_in);
	else if (pop->pop_out != NULL)
		fclose(pop->pop_out);

	pop->pop_in = NULL;
	pop->pop_out = NULL;
	pop->popsock = -1;

	return;
}

void pop_end(struct _pop_src *pop) {
	pop_command(pop, "QUIT");
	pop_close(pop);
	save_uidlist(pop);
	free_uidlist(pop);
}

int pop_init(struct _pop_src *pop) {
	char buf[514], apoptstamp[512], apopdigest[40], *p, *p1;
	int logattempts = 2, i;
	unsigned char digest[16];
	MD5_CTX ctx;

	if(pop->popsock != -1) {
		display_msg(MSG_WARN, "pop", "POP busy");
		return -1;
	}

	pop->nouidl = 0;
	pop->popsock = ConMan.host_connect(pop->hostname, pop->service, NULL);

	if(pop->popsock == -1)
		return -2;

	if((pop->pop_in = fdopen(pop->popsock, "r+")) == NULL) {
		display_msg(MSG_WARN, "pop", "fdopen failed");
		pop_close(pop);
		return -1;
	}

	pop->pop_out = pop->pop_in;

	if(getline(buf, 513, pop->pop_in) == NULL) {
		pop_close(pop);
		return -1;
	}

	if(buf[0] != '+') {
		display_msg(MSG_WARN, "pop", "Invalid response from pop server");
		pop_close(pop);
		return -1;
	}

	if(pop->flags & PSRC_APOP) {
		apoptstamp[0] = '\0';
		if(((p = strchr(buf, '<')) != NULL) &&
		   ((p1 = strchr(p, '>')) != NULL)) {
			i = p1 - p + 1;
			strncpy(apoptstamp, p, i);
			apoptstamp[i] = '\0';
		} else
			display_msg(MSG_LOG, pop->name,
						"APOP is not supported on this server");
	}

	if(supress_errors != 1) {
		if((strlen(pop->password) <= 1) && !(pop->flags & PSRC_STOREPWD))
			pop_account(pop);
	}

	while(logattempts > 0) {
		if((pop->flags & PSRC_APOP) && *apoptstamp) {
			MD5Init(&ctx);
			MD5Update(&ctx, (unsigned char *) apoptstamp,
					  strlen(apoptstamp));
			MD5Update(&ctx, (unsigned char *) pop->password,
					  strlen(pop->password));
			MD5Final(digest, &ctx);

			for(p = apopdigest, i = 0; i < 16; i++, p += 2)
				sprintf(p, "%02x", digest[i] & 0xff);
			apopdigest[32] = '\0';

			if(pop_command(pop, "APOP %s %s", pop->username, apopdigest))
				return 0;
		} else {
			if(pop_command(pop, "USER %s", pop->username) == NULL) {
				pop_close(pop);
				return -1;
			}

			//cerr << "SENDING: " << pop->password << endl;
			if(pop_command(pop, "PASS %s", pop->password))
				return 0;
		}

		if(strncasecmp(pop->response, "-ERR ", 4)) {
			pop_close(pop);
			return -1;
		}

		logattempts--;
		pop_account(pop);
	}

	pop_close(pop);
	return -1;
}

int multiline(struct _pop_src *pop) {
	char buffer[PSRC_RESPONSE_LEN];

	if(getline(buffer, sizeof(buffer) - 1, pop->pop_in) == NULL)
		return -1;

	if(buffer[0] == '.') {
		if(buffer[1] == '\0')
			return 0;
		else
			snprintf(pop->response, sizeof(pop->response), "%s", buffer + 1);
	} else
		snprintf(pop->response, sizeof(pop->response), "%s", buffer);

	return 1;
}

long
get_pop_msg(struct _pop_src *pop, long num, int only_header, long *realen) {
	char buf[255];
	char *p, *p1;
	FILE *mfd;
	int st, pbuf, resplen;
	long mnum, rlen, acclen, tdiff;
	struct timeval tv1, tv2;
	struct _uidl *ucache;

	if((mnum = get_new_name(ftemp)) == -1) {
		display_msg(MSG_WARN, "pop", "No space in %s", FTEMP);
		return -1;
	}

	snprintf(buf, sizeof(buf), "%s/%ld", ftemp->fold_path, mnum);

	if((mfd = fopen(buf, "w")) == NULL) {
		display_msg(MSG_WARN, "pop", "Can not open file %s", buf);
		return -1;
	}

	if(only_header == 1) {
		if((p = pop_command(pop, "TOP %ld 0", num)) == NULL) {
			display_msg(MSG_WARN, "pop",
						"Failed to retrieve header of message %d from server",
						num);
			fclose(mfd);
			unlink(buf);
			return -1;
		}
	} else if(only_header == 2) {
		if((p = pop_command(pop, "TOP %ld 999999", num)) == NULL) {
			display_msg(MSG_WARN, "pop",
						"Failed to retrieve message %d from server", num);
			fclose(mfd);
			unlink(buf);
			return -1;
		}
	} else {
		if((p = pop_command(pop, "RETR %ld", num)) == NULL) {
			display_msg(MSG_WARN, "pop",
						"Failed to retrieve message %d from server", num);
			fclose(mfd);
			unlink(buf);
			return -1;
		}
	}

	rlen = -1;
	if((p = strchr(p, ' ')) != NULL) {
		while(*p == ' ')
			p++;

		rlen = strtoul(p, &p1, 10);
		if(*p1 && (*p1 != ' '))
			rlen = -1;
	}

	if(rlen <= 0) {
		ucache = pop->uidlcache;
		while(ucache && (ucache->mnum != num))
			ucache = ucache->next;

		if(ucache && (ucache->flags & POP_LENGTH))
			rlen = ucache->mlen;
	}

	if(realen) {
		if((*realen = rlen) <= 0)
			*realen = 1;
	}

	rlen = 0;
	acclen = 0;
	pbuf = 0;
	gettimeofday(&tv1, NULL);

	while((st = multiline(pop)) == 1) {
		resplen = strlen(pop->response);
		rlen += resplen + 2;
		acclen += resplen + 2;
		if((only_header != 1) && realen && (*realen > 8192)) {
			if(acclen > (*realen * 0.05)) {
				acclen = 0;
				gettimeofday(&tv2, NULL);
				tdiff =
				(tv2.tv_sec - tv1.tv_sec) * 1000000 - tv1.tv_usec +
				tv2.tv_usec + 1;
				display_msg(MSG_STAT, NULL,
							"POP: retrieving message %ld of %lu (%d %% - %.2f kb/sec)",
							num, pop->num_msgs, rlen * 100 / *realen,
							(double) (((double) rlen * 1000000) /
									  (double) tdiff / 1024));
			}
		}

		if(resplen < 1)
			pbuf++;
		else {
			while(pbuf) {
				fputc('\n', mfd);
				pbuf--;
			}

			if(fputs(pop->response, mfd) == EOF) {
				if(errno == ENOSPC)
					display_msg(MSG_WARN, "pop", "DISK FULL!");
				else
					display_msg(MSG_WARN, "pop", "Error writing %s", buf);
				fclose(mfd);
				unlink(buf);
				errno = 0;
				return -1;
			}
			fputc('\n', mfd);
		}
	}

	if(fclose(mfd) == EOF) {
		if(errno == ENOSPC)
			display_msg(MSG_WARN, "pop", "DISK FULL!");
		else
			display_msg(MSG_WARN, "pop", "Error writing %s", buf);
		fclose(mfd);
		unlink(buf);
		errno = 0;
		return -1;
	}

	if(st == -1) {
		display_msg(MSG_WARN, "pop",
					"Error when retrieving message from server");
		unlink(buf);
		return -1;
	}

	return mnum;
}

int pop_send_message(struct _pop_src *pop, struct _mail_msg *msg) {
	char *p;

	if(!msg)
		return -1;

	switch(pop_init(pop)) {
		case -1:
			return -1;
			break;

		case -2:
			return -1;
			break;
	}

	if((p = pop_command(pop, "XTND XMIT")) == NULL) {
		display_msg(MSG_WARN, "Transmit command failed!",
					"Probably it's not supported on this POP server");
		pop_end(pop);
		return -1;
	}

	if(smtp_message(msg, pop->pop_out) == -1) {
		pop_end(pop);
		return -1;
	}

	if((p = pop_command(pop, ".")) == NULL) {
		display_msg(MSG_WARN, "POP Send", "Failed to send message");
		pop_end(pop);
		return -1;
	}

	pop_end(pop);
	return 0;
}

long get_popmsg_len(struct _pop_src *pop, long num) {
	int st;
	struct _uidl *ucache;
	unsigned long msg_len;
	long mnum;

	if((pop->uidlcache == NULL) || !(pop->uidlcache->flags & POP_LENGTH)) {
		get_popmsg_by_uidl(pop, "");
		if(pop->uidlcache == NULL) {
			if(!pop->nouidl)
				return -2;

			if(pop_command(pop, "LIST %ld", num) == NULL) {
				display_msg(MSG_WARN, "pop",
							"Can not determine message length!");
				return -2;
			}

			mnum = -1;
			msg_len = ULONG_MAX;
			sscanf(pop->response, "%ld %lu", &mnum, &msg_len);
			if((mnum != num) || (msg_len == ULONG_MAX))
				return -2;

			return msg_len;

		}

		if(pop_command(pop, "LIST") == NULL) {
			display_msg(MSG_WARN, "pop",
						"Can not determine message length!");
			return -2;
		}

		ucache = pop->uidlcache;

		while((st = multiline(pop)) == 1) {
			mnum = -1;
			msg_len = 0L;
			sscanf(pop->response, "%ld %lu", &mnum, &msg_len);

			if((mnum == -1) || (msg_len == 0L))
				continue;

			while(ucache && (ucache->mnum != mnum))
				ucache = ucache->next;

			if(!ucache) {
				ucache = pop->uidlcache;
				while(ucache && (ucache->mnum != mnum))
					ucache = ucache->next;
			}

			if(!ucache)
				continue;

			ucache->mlen = msg_len;
			ucache->flags |= POP_LENGTH;
			ucache = ucache->next;
		}
	}

	ucache = pop->uidlcache;
	while(ucache && (ucache->mnum != num))
		ucache = ucache->next;

	if(!ucache || !(ucache->flags & POP_LENGTH)) {
		display_msg(MSG_WARN, "pop",
					"Can not determine message length (%d)!", num);
		return -2;
	}

	return ucache->mlen;
}

char *get_popmsg_uidl(struct _pop_src *pop, long num) {
	struct _uidl *ucache;

	if(pop->uidlcache == NULL)
		get_popmsg_by_uidl(pop, "");

	if(pop->nouidl)
		return NULL;

	ucache = pop->uidlcache;

	while(ucache) {
		if(ucache->mnum == num)
			return ucache->uidlstr;

		ucache = ucache->next;
	}

	return NULL;
}

struct _uidl *get_popmsg_by_uidl(struct _pop_src *pop, char *uidl) {
	long st;
	char muidl[MAX_UIDL];
	struct _uidl *ucache, *uidl1, *uidl2;

	if(pop->uidlcache == NULL) {
		if(pop->nouidl)
			return NULL;

		if(pop_command(pop, "UIDL") == NULL) {
			pop->nouidl = 1;
			return NULL;
		}

	} else {
		ucache = pop->uidlcache;
		while(ucache) {
			if(!strcmp(ucache->uidlstr, uidl))
				return ucache;

			ucache = ucache->next;
		}

		return NULL;
	}

	uidl2 = NULL;

	while(multiline(pop) == 1) {
		muidl[0] = '\0';
		st = 0;
		sscanf(pop->response, "%ld %70s", &st, muidl);
		ucache = (struct _uidl *) malloc(sizeof(struct _uidl));
		ucache->mnum = st;
		ucache->mlen = 0;
		ucache->flags = 0;
		strcpy(ucache->uidlstr, muidl);
		ucache->next = NULL;

		if(!strcmp(muidl, uidl))
			uidl2 = ucache;

		if(pop->uidlcache == NULL)
			pop->uidlcache = ucache;
		else {
			uidl1 = pop->uidlcache;
			while(uidl1->next)
				uidl1 = uidl1->next;

			uidl1->next = ucache;
		}
	}

	compare_uidlist(pop);
	return uidl2;
}

long get_popmsg_num(struct _pop_src *pop) {
	char *p;
	char dumb[5];
	int msgs_len = 0;

	if((p = pop_command(pop, "STAT")) == NULL)
		return -1;

	sscanf(p, "%s %lu %d", dumb, &pop->num_msgs, &msgs_len);

	if(pop->num_msgs == ULONG_MAX) {
		display_msg(MSG_WARN, "pop", "STAT failed");
		return -1;
	}

	return (long)pop->num_msgs;
}

int pop_delmsg_by_uidl(struct _pop_src *pop, struct _mail_msg *msg) {
	long mnum;
	struct _head_field *fld;
	struct _uidl *uidl;
	int init;

	if(pop == NULL)
		return -1;

	init = (pop->popsock == -1) ? 1 : 0;

	if(!msg)
		return -1;

	if((fld = find_field(msg, XUIDL)) == NULL) {
		display_msg(MSG_WARN, "pop",
					"This message does not have POP %s identifier", XUIDL);
		return -1;
	}

	if(init) {
		if(pop_init(pop) != 0)
			return -1;
	}

	if((uidl = get_popmsg_by_uidl(pop, fld->f_line)) == NULL) {
		if(pop->nouidl)
			display_msg(MSG_WARN, "pop",
						"You can not use this feature\nsince your POP server does not support UIDL command");
		if(init)
			pop_end(pop);
		return -1;
	}

	mnum = uidl->mnum;

	if(mnum == 0) {
		if(init)
			pop_end(pop);
		return -1;
	}

	if(!(uidl->flags & POP_DELETED)) {
		display_msg(MSG_STAT, NULL, "POP: deleting message %ld", mnum);
		pop_command(pop, "DELE %ld", mnum);
		uidl->flags |= POP_DELETED;
	}

	if(init)
		pop_end(pop);

	msg->flags &= ~H_ONLY;
	delete_uidlist(pop, fld->f_line);

	return 0;
}

int pop_getfull_msg(struct _pop_src *pop, struct _mail_msg *msg) {
	struct _head_field *fld;
	struct _uidl *uidl;
	long mnum, nnum, realen;
	int st;
	FILE *mfd, *bfd;
	char buf[255];
	char msgf[255];

	if(!msg || !pop)
		return -1;

	if(!(msg->flags & H_ONLY))
		return -1;

	if((fld = find_field(msg, XUIDL)) == NULL) {
		display_msg(MSG_WARN, "pop", "Message does not have %s identifier",
					XUIDL);
		return -1;
	}

	if(pop_init(pop) != 0)
		return -1;

	if((uidl = get_popmsg_by_uidl(pop, fld->f_line)) == NULL) {
		if(pop->nouidl)
			display_msg(MSG_WARN, "pop",
						"You can not use thise feature\nsince your POP server does not support UIDL command");
		else
			display_msg(MSG_WARN, "pop", "Failed to find message");
		pop_end(pop);
		return -1;
	}

	mnum = uidl->mnum;

	if(mnum == 0) {
		display_msg(MSG_WARN, "pop",
					"Can not find message, probably it's no longer on the server");
		pop_end(pop);
		return -1;
	}

	if((nnum = get_pop_msg(pop, mnum, 0, &realen)) == -1) {
		pop_end(pop);
		return -1;
	}

	if(pop->flags & PSRC_DELETE)
		pop_command(pop, "DELE %ld", mnum);

	snprintf(msgf, sizeof(msgf), "%s/%ld", ftemp->fold_path, nnum);

	if((bfd = fopen(msgf, "r")) == NULL) {
		display_msg(MSG_WARN, "pop", "Can not open retrieved message");
		unlink(msgf);
		pop_end(pop);
		return -1;
	}

	if((mfd = fopen(msg->get_file(msg), "a")) == NULL) {
		display_msg(MSG_WARN, "pop", "Can not open message %s",
					msg->get_file(msg));
		fclose(bfd);
		unlink(msgf);
		pop_end(pop);
		return -1;
	}

	st = 0;
	while(fgets(buf, 255, bfd)) {
		if(!st && ((buf[0] == '\n') || (buf[0] == '\r'))) {
			st = 1;
			continue;
		}

		if(st)
			fputs(buf, mfd);
	}

	fflush(mfd);
	msg->msg_len = ftell(mfd);
	fclose(mfd);
	fclose(bfd);
	unlink(msgf);

	msg->flags &= ~H_ONLY;
	replace_field(msg, XUIDL, uidl->uidlstr);
	pop_end(pop);

	return 0;
}

int if_popmsg_retr(struct _pop_src *pop, long num) {
	char *p;
	int st;

	if(pop->flags & PSRC_STATXLST)
		p = pop_command(pop, "XTND XLST Status %ld", num);
	else
		p = pop_command(pop, "TOP %ld 0", num);

	if(!p) {
		display_msg(MSG_WARN, "pop", "Can not determine message status");
		return 0;
	}

	st = 0;
	while(multiline(pop) == 1) {
		if((p = strstr(pop->response, "Status:")) != NULL) {
			p += 7;
			if(strchr(p, 'R'))
				st = 1;
		}
	}

	return st;
}

void free_uidlist(struct _pop_src *pop) {
	int i;

	if(pop->uidfirst == -2) {
		for(i = 0; i < MAX_POP_UIDS; i++)
			pop->pop_uids[i] = NULL;
	} else {
		for(i = 0; i < MAX_POP_UIDS; i++) {
			if(pop->pop_uids[i] != NULL)
				free(pop->pop_uids[i]);
			pop->pop_uids[i] = NULL;
		}
	}
	pop->uidfirst = -1;

	return;
}

void load_uidlist(struct _pop_src *pop) {
	char uidfname[255];
	char uidstr[MAX_UIDL + 2];
	FILE *ufd;
	int i = 0;

	free_uidlist(pop);

	snprintf(uidfname, sizeof(uidfname), "%s/.xfmpopuid-%s", configdir,
			 pop->name);
	if((ufd = fopen(uidfname, "r")) == NULL) {
		pop->uidfirst = 0;
		return;
	}

	while(fgets(uidstr, MAX_UIDL - 1, ufd)) {
		strip_newline(uidstr);
		pop->pop_uids[i] = strdup(uidstr);
		if(++i >= MAX_POP_UIDS)
			break;
	}
	fclose(ufd);
	pop->uidfirst = 0;

	return;
}

void save_uidlist(struct _pop_src *pop) {
	FILE *ufd;
	char uidfname[255];
	int i = pop->uidfirst;

	if(pop->uidfirst < 0)
		return;

	snprintf(uidfname, sizeof(uidfname), "%s/.xfmpopuid-%s", configdir,
			 pop->name);
	if((ufd = fopen(uidfname, "w")) == NULL) {
		display_msg(MSG_WARN, "Message uids will not be stored",
					"Can not open %s", uidfname);
		pop->uidfirst = -3;
		return;
	}

	do {
		if(pop->pop_uids[i]) {
			fputs(pop->pop_uids[i], ufd);
			fputc('\n', ufd);
		}

		i++;
		if(i >= MAX_POP_UIDS)
			i = 0;
	} while(i != pop->uidfirst);
	fclose(ufd);

	return;
}

void append_uidlist(struct _pop_src *pop, char *uidstr) {
	if(!uidstr || !*uidstr || (strlen(uidstr) >= MAX_UIDL))
		return;

	if(pop->uidfirst == -3)
		return;

	if(pop->uidfirst < 0)
		load_uidlist(pop);

	if(check_uidlist(pop, uidstr) != 0)
		return;

	pop->uidfirst--;
	if(pop->uidfirst < 0)
		pop->uidfirst = MAX_POP_UIDS - 1;
	if(pop->pop_uids[pop->uidfirst])
		free(pop->pop_uids[pop->uidfirst]);
	pop->pop_uids[pop->uidfirst] = strdup(uidstr);

	return;
}

int check_uidlist(struct _pop_src *pop, char *uidstr) {
	int i = 0;

	if(!uidstr || !*uidstr || (strlen(uidstr) >= MAX_UIDL))
		return 0;

	if(pop->uidfirst == -3)
		return 0;

	if(pop->uidfirst < 0)
		load_uidlist(pop);

	for(i = 0; i < MAX_POP_UIDS; i++) {
		if(pop->pop_uids[i] && !strcmp(pop->pop_uids[i], uidstr))
			return 1;
	}

	return 0;
}

void delete_uidlist(struct _pop_src *pop, char *uidstr) {
	int i;

	if(!uidstr || !*uidstr || (strlen(uidstr) >= MAX_UIDL))
		return;

	if(pop->uidfirst < 0)
		load_uidlist(pop);

	if(pop->uidfirst == -3)
		return;

	for(i = 0; i < MAX_POP_UIDS; i++) {
		if(pop->pop_uids[i] && !strcmp(pop->pop_uids[i], uidstr)) {
			free(pop->pop_uids[i]);
			pop->pop_uids[i] = NULL;
			break;
		}
	}

	return;
}

void compare_uidlist(struct _pop_src *pop) {
	int i;

	if(pop->uidlcache == NULL)
		return;

	if(pop->uidfirst < 0)
		load_uidlist(pop);

	if(pop->uidfirst == -3)
		return;

	for(i = 0; i < MAX_POP_UIDS; i++) {
		if(pop->pop_uids[i] &&
		   (get_popmsg_by_uidl(pop, pop->pop_uids[i]) == NULL)) {
			free(pop->pop_uids[i]);
			pop->pop_uids[i] = NULL;
		}
	}

	return;
}

int if_popmsg_uid_cached(struct _pop_src *pop, long msg) {
	char *uid;

	if(pop->uidfirst < 0)
		load_uidlist(pop);

	if(pop->uidfirst < 0)
		return -1;

	if((uid = get_popmsg_uidl(pop, msg)) == NULL)
		return -1;

	return check_uidlist(pop, uid);
}

long pop_inc(struct _retrieve_src *source, long *notify) {
	struct _pop_src *pop;
	struct _mail_msg *msg;
	int honly, uidcached;
	long i, realen, num_msgs, retr_num;
	long popmaxmsg = -1, nnum = -1;
	char mclen[16], *muidl;

	if(source->flags & RSRC_DISABLED)
		return 0;

	pop = (struct _pop_src *) source->spec;

	if(pop->maxmsg >= 0)
		popmaxmsg = pop->maxmsg * 1024;

	if(pop_init(pop) != 0)
		return -1;

	if((num_msgs = get_popmsg_num(pop)) == -1) {
		pop_end(pop);
		return -1;
	}

	if(num_msgs == 0) {
		free_uidlist(pop);
		pop->uidfirst = 0;
		save_uidlist(pop);
		pop_end(pop);
		return 0;
	}

	retr_num = 0;

	for(i = 1; i <= num_msgs; i++) {

		honly = 0;
		uidcached = -1;
		realen = 0;

		if(abortpressed())
			break;

		if(!(pop->flags & PSRC_POP2) &&
		   ((pop->flags & PSRC_CHECKUID) ||
			(pop->flags & PSRC_CHECKSTAT))) {
			if((pop->flags & PSRC_CHECKSTAT) &&
			   (if_popmsg_retr(pop, i) > 0)) continue;

			if((pop->flags & PSRC_CHECKUID) &&
			   ((uidcached = if_popmsg_uid_cached(pop, i)) == 1))
				continue;
		}

		if(!(pop->flags & PSRC_POP2) && (popmaxmsg >= 0)
		   && ((realen = get_popmsg_len(pop, i)) >= popmaxmsg)) {

			if(realen >= popmaxmsg) {
				if(!is_iconized()) {
					display_msg(MSG_WARN, "pop",
								"Skipping Message: %ldk > %ldk",
								realen / 1024, popmaxmsg / 1024);
				}
			}

			if(pop->flags & PSRC_SKIPBIG)
				continue;

			if(pop->nouidl) {
				if(!is_iconized()) {
					display_msg(MSG_WARN,
								"Can not retrieve message header, skipping",
								"Your POP server does not support UIDL command\nIt will be impossible to match header and message left on the server later");
				}
				continue;
			}

			display_msg(MSG_STAT, NULL,
						"POP: retrieving header of message %ld of %ld", i,
						num_msgs);
			if((nnum = get_pop_msg(pop, i, 1, &realen)) == -1) {
				pop_end(pop);
				return -1;
			}

			if((msg = get_message(nnum, ftemp)) == NULL) {
				pop_end(pop);
				return -1;
			}

			msg->flags |= H_ONLY;

			if(realen > 0) {
				snprintf(mclen, sizeof(mclen), "%lu", realen);
				replace_field(msg, MIME_C_LENGTH, mclen);
			}

			honly = 1;
		} else {

			display_msg(MSG_STAT, NULL, "POP: retrieving message %ld of %ld",
						i, num_msgs);
			if((nnum = get_pop_msg(pop, i, (pop->flags & PSRC_DMARK) ? 2 : 0,
						   &realen)) == -1) {
				pop_end(pop);
				return -1;
			}

			if((msg = get_message(nnum, ftemp)) == NULL) {
				pop_end(pop);
				return -1;
			}

		}

		if(!(pop->flags & PSRC_POP2)) {
			if((muidl = get_popmsg_uidl(pop, i)) != NULL) {
				replace_field(msg, XUIDL, muidl);
				if((pop->flags & PSRC_CHECKUID) &&
				   (uidcached == 0) && (!(pop->flags & PSRC_DELETE)
										|| honly))
					append_uidlist(pop, muidl);
			}
		}

		set_flags_by_status(msg);
		convert_fields(msg);
		msg->folder = ftemp;
		msg->status |= (CHANGED | RECENT);
		if(source->flags & RSRC_MARKREAD)
			msg->flags &= ~UNREAD;
		replace_field(msg, "X-RDate", get_arpa_date(time(NULL)));
		replace_field(msg, SOURCE_FIELD, source->name);
		msg->header->rcv_time = time(NULL);

#ifdef WITH_FACES
		update_faces(msg);
#endif

		switch(apply_rule(msg, 0)) {
			case 0:
				if(!(source->flags & RSRC_NONOTIFY))
					(*notify)++;
				break;

			case -1:
				pop_end(pop);
				unlink(msg->get_file(msg));
				discard_message(msg);
				return -1;
				break;
		}

		retr_num++;

		if((pop->flags & PSRC_DELETE) && !honly)
			pop_command(pop, "DELE %ld", i);

	}

	pop_end(pop);
	return retr_num;
}

void init_pop_source(struct _retrieve_src *source) {
	struct _pop_src *pop;
	struct _uidl *ucache;
	int i;

	if(source->spec == NULL) {
		source->spec = (struct _pop_src *) malloc(sizeof(struct _pop_src));
		pop = (struct _pop_src *) source->spec;
		strcpy(pop->name, source->name);
		strcpy(pop->hostname, "127.0.0.1");
		strcpy(pop->service, "110");
		strcpy(pop->username, user_n);
		*pop->password = '\0';
		pop->maxmsg = -1;
		pop->flags = PSRC_DELETE;
		for(i = 0; i < MAX_POP_UIDS; i++)
			pop->pop_uids[i] = NULL;
	} else {
		pop = (struct _pop_src *) source->spec;
		if(pop->popsock > 0)
			close(pop->popsock);
		if(pop->pop_in)
			fclose(pop->pop_in);
		else if(pop->pop_out)
			fclose(pop->pop_out);

		while(pop->uidlcache) {
			ucache = pop->uidlcache;
			pop->uidlcache = pop->uidlcache->next;
			if(ucache != NULL) {
				free(ucache);
				ucache = NULL;
			}
		}

		for(i = 0; i < MAX_POP_UIDS; i++) {
			if(pop->pop_uids[i])
				free(pop->pop_uids[i]);
			pop->pop_uids[i] = NULL;
		}
	}

	pop->popsock = -1;
	pop->pop_in = NULL;
	pop->pop_out = NULL;
	pop->uidlcache = NULL;
	pop->nouidl = 0;
	pop->num_msgs = ULONG_MAX;
	pop->uidfirst = -2;
	*pop->response = '\0';

	return;
}

void free_pop_source(struct _retrieve_src *source) {
	if(source->spec) {
		init_pop_source(source);
		if(source->spec != NULL) {
			free(source->spec);
			source->spec = NULL;
		}
	}

	return;
}

int load_pop_source(struct _retrieve_src *source, FILE * fd) {
	struct _pop_src *pop;
	char buf[255], *p, *p1;

	pop = (struct _pop_src *) source->spec;
	if(!fgets(buf, 255, fd))
		return -1;
	strip_newline(buf);
	if(sscanf(buf, "%s %15s", pop->hostname, pop->service) != 2)
		return -1;

	if(!fgets(buf, 255, fd))
		return -1;
	strip_newline(buf);

	p1 = buf;
	if((p = get_quoted_str(&p1)) == NULL)
		return -1;
	strncpy(pop->username, p, POP_MAX_USERNAME_LEN);
	pop->username[POP_MAX_USERNAME_LEN] = '\0';
	*pop->password = '\0';
	if((p = get_quoted_str(&p1)) != NULL) {
#ifdef USE_GPASSWD
		if(Config.getInt("use_gpasswd",0) != 0) {
			int ecode = CE_BASE64;
			char *passwd = NULL;
			/* now encrypted passwords are also base_64 encoded so they are
			   text strings */
			base64_decode(NULL, &ecode);	/* initialize decoder */
			passwd  = base64_decode(p, &ecode);
			if (passwd) {
				//cerr << "Decrypting Pass: " << p << endl;
				p = passwd;
				strncpy(pop->password, Passwd.decrypt(p).c_str(), POP_MAX_PASSWD_LEN);
				//cerr << "Decrypted Pass: " << pop->password << endl;
			} else 
				*pop->password = '\0';
		} else {
#endif
			strncpy(pop->password, p, POP_MAX_PASSWD_LEN);
			pop->password[POP_MAX_PASSWD_LEN] = '\0';
#ifdef USE_GPASSWD
		}
#endif
	}

	if(!fgets(buf, 255, fd))
		return -1;
	strip_newline(buf);
	if(sscanf(buf, "%ld %d", &pop->maxmsg, &pop->flags) != 2)
		return -1;

	return 0;
}

int save_pop_source(struct _retrieve_src *source, FILE * fd) {
	struct _pop_src *pop;
#ifdef USE_GPASSWD
	char passwd[4 * ((POP_MAX_PASSWD_LEN + 2) / 3) + 1];
	int len, len64 ,len1 = 0, len2 = 0 ;
	char *init = NULL, *body = NULL, *end = NULL;
#else
	char passwd[POP_MAX_PASSWD_LEN + 1];
#endif

	pop = (struct _pop_src *) source->spec;
	fprintf(fd, "%s %s\n", pop->hostname, pop->service);

	if(strchr(pop->username, ' '))
		fprintf(fd, "\"%s\"", pop->username);
	else
		fprintf(fd, "%s", pop->username);

	if(pop->flags & PSRC_STOREPWD) {
#ifdef USE_GPASSWD
		if(Config.getInt("use_gpasswd", 0) != 0) {
			//cerr << "Encrypting Pass: " << pop->password << endl;
			strncpy(passwd, Passwd.encrypt(pop->password).c_str(), POP_MAX_PASSWD_LEN);

			/* md5 encryption to a text string */
			len = strlen(passwd);
			len64 = 4 * ((len + 2) / 3) ;
			init = base64_encode(NULL, len64 + 12);
			if (init)
				body = base64_encode(passwd, len);
			*passwd = '\0';
			if(body) {
				len1 = strlen(body);
				end = base64_encode(NULL,len);	/* terminate encoding */
			}
			if(end) {
				len2 = strlen(end);
				if( len1 + len2 < sizeof(passwd)) {
					strncpy(passwd, body, len1);
					strncpy(passwd + len1, end, len2);
					*(passwd + len1 + len2) = '\0';
				}
			}
			//cerr << "Encrypted Pass: " << passwd << endl;
		} else {
#endif
			strncpy(passwd, pop->password, POP_MAX_PASSWD_LEN);
	
#ifdef USE_GPASSWD
		}
#endif
		fprintf(fd, " %s\n", passwd);
	} else
		fprintf(fd, "\n");

	fprintf(fd, "%ld %d\n", pop->maxmsg, pop->flags);
	return 0;
}

void pop_source(struct _retrieve_src *source) {
	init_pop_source(source);
	source->type = RSRC_POP;
	source->retrieve = pop_inc;
	source->free = free_pop_source;
	source->init = init_pop_source;
	source->load = load_pop_source;
	source->save = save_pop_source;

	return;
}
