/*
 * 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 "globals.h"
#include "imap.h"
#include "socklib.h"


#define CREATE_OPEN_SOCK { \
	if ( !( *sock = Sopen() ) ) { \
		return STAT_FAIL; \
	} \
	s = *sock; \
\
   if ( Sclient(s, mb->server, mb->port) == -1) { \
		BYE(STAT_CONN); \
	} \
}


#define CHECK_GREETING { \
   WAITOK; \
   if ( strncmp(input, "* OK", 4) ) { \
      BYE(STAT_CONN); \
   } \
}


#define BYE(RET_STATUS) { \
	Sclose(s); \
   *sock = NULL; \
	return(RET_STATUS); \
}


#ifdef HAVE_OPENSSL_SSL_H

#define WRITE_OUTPUT { \
   ret = (mb->flags & FLAG_SSL) && (s->ssl) ? \
      Sslwrite(s, output) : Swrite(s->sd, output); \
   if ( ret == -1 ) { \
      BYE(STAT_CONN); \
   } \
}

#define WAITOK { \
   ret = (mb->flags & FLAG_SSL) && (s->ssl) ? \
      Sslread(s, input, MAX_INPUT_LENGTH, mb->timeout) : \
      Sread(s->sd, input, MAX_INPUT_LENGTH, mb->timeout); \
\
   switch (ret) {	\
      case -1:	/* Error */			\
         BYE(STAT_CONN);				\
         break; \
      case 0:		/* Timeout */			\
         BYE(STAT_TIMEOUT);				\
         break; \
   } \
}

#else

#define WRITE_OUTPUT { \
   ret = Swrite(s->sd, output); \
   if ( ret == -1 ) { \
      BYE(STAT_CONN); \
   } \
}


#define WAITOK { \
   ret = Sread(s->sd, input, MAX_INPUT_LENGTH, mb->timeout); \
   switch (ret) {	\
      case -1:	/* Error */	\
         BYE(STAT_CONN); \
         break; \
      case 0:		/* Timeout */ \
         BYE(STAT_TIMEOUT); \
         break; \
   } \
}

#endif




int imap_login( struct mbox_struct * mb, SOCKET ** sock ) {
   int ret;
	char input[MAX_INPUT_LENGTH+1];
	char output[MAX_INPUT_LENGTH+1];
	SOCKET * s;


   CREATE_OPEN_SOCK;

#ifdef HAVE_OPENSSL_SSL_H
   if ( (mb->flags & FLAG_SSL) && (Sslclient(s, mb->trustedCaDir) == -1) ) {

      /* no imaps server:
       *    close the connection, try to re-connect and negotiate tls */

      Sclose(s);
      s = NULL;

      CREATE_OPEN_SOCK;
      CHECK_GREETING;

      sprintf(output, "A000 STARTTLS\r\n");
      WRITE_OUTPUT;
      WAITOK;

      if ( strncmp(input, "A000 OK", 7) ) {
         BYE(STAT_CONN);
      }

      if ( Sslclient(s, mb->trustedCaDir) == -1) {
         BYE(STAT_CONN);
      }
   }
   else {
      CHECK_GREETING;
   }
#else
   CHECK_GREETING;
#endif

	/* connection is open, let's log in */

	sprintf(output, "A000 LOGIN %s %s\r\n", mb->user, mb->pass);
   WRITE_OUTPUT;
	WAITOK;
	if ( strncmp(input, "A000 OK", 7) ) {
		BYE(STAT_LOGIN);
	}
	return(STAT_IDLE);
}


int imap_checkmbox ( struct mbox_struct * mb, SOCKET ** sock ) {
   int ret;
	int ctotal = 0;
	int cnew = 0;
	char input[MAX_INPUT_LENGTH+1];
	char output[MAX_INPUT_LENGTH+1];
	char * tmp_str;
	SOCKET * s;
	s = *sock;


   if (mb->status != (STAT_IDLE | STAT_RUN))
      return(mb->status);

	/* seems we are logged in, get statistics */
	sprintf(output, "A001 STATUS %s (MESSAGES)\r\n", mb->mbox);
   WRITE_OUTPUT;
	WAITOK;
	if ( strncmp(input, "* STATUS", 8) ) {
		/* Notes 6 sends a different (non-standard?) response */
		/* According to Jason Day <jasonday@worldnet.att.net> :
		 * * OK Domino IMAP4 Server Release 6.0.1 ready
		 * A000 LOGIN xxx xxx
		 * A000 OK LOGIN completed
		 * A001 STATUS INBOX (MESSAGES)
		 * * 10 EXISTS
		 * * 0 RECENT
		 * * STATUS INBOX (MESSAGES 10)
		 * A001 OK STATUS completed
		 */
		if (strncmp (input, "* ", 2)) {
		BYE(STAT_CONN);
		}
		WAITOK;

		if (strncmp (input, "* ", 2)) {
			BYE(STAT_CONN);
		}
		WAITOK;

		if ( strncmp(input, "* STATUS", 8) ) {
			BYE(STAT_CONN);
		}
	}
	if ( (tmp_str = strstr(input, "MESSAGES")) ) {
		sscanf(tmp_str, "MESSAGES %d", &ctotal);
	} else {
		printf("asmail: imap_mailcheck: did not get MESSAGES in response to the STATUS.\n");
		BYE(STAT_FAIL);
	}
	WAITOK;
	if ( strncmp(input, "A001 OK", 7) ) {
		BYE(STAT_CONN);
	}
	sprintf(output, "A002 STATUS %s (UNSEEN)\r\n", mb->mbox);
   WRITE_OUTPUT;
	WAITOK;
	if ( strncmp(input, "* STATUS", 8) ) {
		BYE(STAT_CONN);
	}
	if ( (tmp_str = strstr(input, "UNSEEN")) ) {
		sscanf(tmp_str, "UNSEEN %d", &cnew);
	} else {
		printf("asmail: imap_mailcheck: did not get UNSEEN in response to the STATUS.\n");
		BYE(STAT_FAIL);
	}
	WAITOK;
	if ( strncmp(input, "A002 OK", 7) ) {
		BYE(STAT_CONN);
	}
	if ( (mb->cnew != cnew) && (cnew != 0) ) {
		pthread_mutex_lock(&mb->mutex);
		mb->flags |= FLAG_ARRIVED;
		pthread_mutex_unlock(&mb->mutex);
	}
	mb->cnew = cnew;
	mb->ctotal = ctotal;
	return(STAT_IDLE);
}

int imap_goodbye ( struct mbox_struct * mb, SOCKET ** sock ) {
   int ret;
	char input[MAX_INPUT_LENGTH+1];
	char output[MAX_INPUT_LENGTH+1];
	SOCKET * s;
	s = *sock;

   if (!s)
      return(mb->status);

	/* and good-bye */
	sprintf(output, "A004 LOGOUT\r\n");
   WRITE_OUTPUT;
   WAITOK;
	if ( strncmp(input, "* BYE", 5) ) {
		BYE(STAT_CONN);
	}
	Sclose(*sock);
   *sock = NULL;
	return(STAT_IDLE);
}

void imap_handle( struct mbox_struct * mb ) {
	SOCKET * s;
	/* Sanity check */
	if ( mb->type != MBOX_IMAP ) {
		printf("asmail: imap_handle: Cowardly refusing to work with a non-IMAP server.\n");
		mb->status = STAT_FAIL;
		pthread_exit(NULL);
	}
	if ( ! strlen(mb->server) ) {
		printf("asmail: imap_handle: no server name specified!\n");
		mb->status = STAT_FAIL;
		pthread_exit(NULL);
	}
	if ( ! strlen(mb->user) ) {
		printf("asmail: imap_handle: no user name specified!\n");
		mb->status = STAT_FAIL;
		pthread_exit(NULL);
	}
	if ( mb->port == 0 ) {
      mb->port = (mb->flags & FLAG_SSL) ? PORT_IMAPS : PORT_IMAP;
   }
	if ( ! strlen(mb->mbox) )
		strcpy(mb->mbox, IMAP_MAILBOX_DEFAULT);
	if ( mb->flags & FLAG_PERSISTENT_CONN )
		mb->status = imap_login(mb, &s);
	while (1) {
		mb->status |= STAT_RUN;
		signal_update();

		if ( mb->flags & FLAG_PERSISTENT_CONN ) {
			if ( (mb->status = imap_checkmbox(mb, &s)) ) {
				/* something failed - retry once */
				mb->status = imap_goodbye(mb, &s);
				mb->status = imap_login(mb, &s) | 
					imap_checkmbox(mb, &s);
			}
		} else {
			mb->status |= imap_login(mb, &s);
			mb->status |= imap_checkmbox(mb, &s);
			mb->status |= imap_goodbye(mb, &s);
		}
		mb->status &= ~STAT_RUN;
		if ( ! mb->status ) {
			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);
	}
}
