/*
 * asmail is the AfterStep mailbox monitor
 * Copyright (c) 2002 Albert Dorofeev <albert@tigr.net>
 * For the updates see http://www.tigr.net/
 *
 * This software is distributed under GPL. For details see LICENSE file.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>

#include "config.h"

#ifdef HAVE_OPENSSL_SSL_H
#include <openssl/md5.h>
#else
#include "md5.h"
#define MD5_DIGEST_LENGTH 16
#endif

#include "globals.h"
#include "pop3.h"
#include "socklib.h"

#define ACK_MSG "+OK"

#define BYE(RET_STATUS) { \
	Sclose(s); \
	return(RET_STATUS); \
}

#define WAITOK {					\
    switch (Sread(s->sd, input, MAX_INPUT_LENGTH, mb->timeout)) {	\
        case -1:	/* Error */			\
	   BYE(STAT_CONN);				\
        case 0:		/* Timeout */			\
           BYE(STAT_TIMEOUT);				\
    }							\
    sscanf(input, "%5s", key);				\
}
#define SENDOK {					\
	if ( Swrite(s->sd, output) == -1 ) {		\
		BYE(STAT_CONN);				\
	}						\
}

int pop3_mailcheck( struct mbox_struct * mb ) {
	SOCKET * s;
	char input[MAX_INPUT_LENGTH+1];
	char output[MAX_INPUT_LENGTH+1];
	char key[MAX_INPUT_LENGTH+1];
	int ctotal;

	char md5_digest[MD5_DIGEST_LENGTH];
	char * str_ptr;
	char * str_end;
	char * str_target;
	int i;
#ifndef HAVE_OPENSSL_SSL_H
	MD5_CTX mdc;
#endif

	if ( !( s = Sopen() ) ) {
		return STAT_FAIL;
	}
	if ( Sclient(s, mb->server, mb->port) == -1 ) {
		BYE(STAT_CONN);
	}
	/* Once we connect, we should receive the greeting */
	WAITOK;
	if ( strcmp(key, ACK_MSG) ) {
		BYE(STAT_CONN);
	}

	/* Check if the server supports MD5 */
	if ( (mb->auth & AUTH_MD5) && 
			(str_ptr = strchr(input, '<')) && 
			strchr(input, '@') && 
			(str_end = strchr(input, '>')) &&
			(str_ptr < str_end) ) {
		str_target = key;
		while ( str_ptr <= str_end ) {
			*str_target++ = *str_ptr++;
		}
		*str_target = '\0';
		strcat(key, mb->pass);
		pthread_mutex_lock(&md5_lock);
#ifdef HAVE_OPENSSL_SSL_H
		MD5((unsigned char *)key, strlen(key), (unsigned char *)md5_digest);
#else
		MD5Init(&mdc);
		MD5Update(&mdc, key, strlen(key));
		MD5Final(md5_digest, &mdc);
#endif
		pthread_mutex_unlock(&md5_lock);
		/* basically sprintf(key, "%x", md5_digest); */
		str_target = key;
		for ( i=0; i<MD5_DIGEST_LENGTH; ++i) {
			sprintf(str_target, "%02x", 
					(unsigned char)md5_digest[i]);
			str_target += 2;
		}
		str_target[MD5_DIGEST_LENGTH*2] = '\0';
		/* Login into the server with MD5 hash */
		sprintf(output, "APOP %s %s\r\n", mb->user, key);
		SENDOK;
		WAITOK;
		if ( strcmp(key, ACK_MSG) ) {
			sprintf(output, "QUIT\r\n");
			SENDOK;
			BYE(STAT_LOGIN);
		}
	} else if ( mb->auth & AUTH_PLAIN) {
		/* Login into the server with password in clear */
		sprintf(output, "USER %s\r\n", mb->user);
		SENDOK;
		WAITOK;
		if ( strcmp(key, ACK_MSG) ) {
			sprintf(output, "QUIT\r\n");
			SENDOK;
			BYE(STAT_LOGIN);
		}
		sprintf(output, "PASS %s\r\n", mb->pass);
		SENDOK;
		WAITOK;
		if ( strcmp(key, ACK_MSG) ) {
			sprintf(output, "QUIT\r\n");
			SENDOK;
			BYE(STAT_LOGIN);
		}
	} else {
		/* requested login method unsupported */
		printf("asmail: pop3_mailcheck: specified login method is unsupported by POP server.\n");
		sprintf(output, "QUIT\r\n");
		SENDOK;
		BYE(STAT_LOGIN);
	}
	/* seems we are logged in, get statistics */
	sprintf(output, "STAT\r\n");
	SENDOK;
	WAITOK;
	if ( strcmp(key, ACK_MSG) ) {
		BYE(STAT_CONN);
	}
	sprintf(output, "%s %s", ACK_MSG, "%d");
	sscanf(input, output, &ctotal);
	/* and good-bye */
	sprintf(output, "QUIT\r\n");
	SENDOK;
	WAITOK;
	if ( strcmp(key, ACK_MSG) ) {
		BYE(STAT_CONN);
	}
	Sclose(s);
	/* Ok, now some tricks to figure out of we have new
	 * messages. Basically, we have only the total number
	 * of messages but, what the heck, we can do some
	 * dirty tricks, right?
	 */
	if ( ctotal == mb->ctotal ) {
		/* Ok, assume nothing has changed, although
		 * it is quite possible that some mail was
		 * deleted and some has arrived in exactly
		 * the same quantities. Well, too bad.
		 */
		return(STAT_IDLE);
	}
	if ( ctotal > mb->ctotal ) {
		/* we probably got more new mail */
		mb->cnew += ctotal - mb->ctotal;
		mb->ctotal = ctotal;
		pthread_mutex_lock(&mb->mutex);
		mb->flags |= FLAG_ARRIVED;
		pthread_mutex_unlock(&mb->mutex);
	} else {
		/* right, you may have read and deleted
		 * some messages and, maybe, there are
		 * new ones but I would not know...
		 */
		mb->cnew = 0;
		mb->ctotal = ctotal;
	}
	return(STAT_IDLE);
}

void pop3_handle( struct mbox_struct * mb ) {
	/* Sanity check */
	if ( mb->type != MBOX_POP3 ) {
		printf("asmail: pop3_handle: Cowardly refusing to work with a non-POP3 server.\n");
		mb->status = STAT_FAIL;
		pthread_exit(NULL);
	}
	if ( ! strlen(mb->server) ) {
		printf("asmail: pop3_handle: no server name specified!\n");
		mb->status = STAT_FAIL;
		pthread_exit(NULL);
	}
	if ( ! strlen(mb->user) ) {
		printf("asmail: pop3_handle: no user name specified!\n");
		mb->status = STAT_FAIL;
		pthread_exit(NULL);
	}
	if ( mb->port == 0 ) 
		mb->port = PORT_POP3;
	while (1) {
		mb->status |= STAT_RUN;
		signal_update();

		if ( ! ( mb->status = pop3_mailcheck(mb) ) ) {
			mb->status = STAT_IDLE;
			if ( mb->cnew > 0)
				mb->mail = MAIL_NEW;
			else if ( mb->ctotal > 0 )
				mb->mail = MAIL_OLD;
			else
				mb->mail = MAIL_NONE;
		}
		signal_update();
		sleep_check(mb->update);
	}
}

