/*
 * 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 <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <dirent.h>
#include <pthread.h>

#include "globals.h"
#include "mbox.h"
#include "maildir.h"
#include "pop3.h"
#include "imap.h"
#include "gui.h"

void version() {
	printf("asmail : AfterStep e-mail monitor version %s\n", VERSION);
}

void usage() {
	version();
	printf("Usage : asmail [-options...]\n");
	printf("\n");
	printf("-V		print version and exit\n");
	printf("-h		print this help screen and exit\n");
	printf("-insecure	allow config file to be readable by others\n");
	printf("-f <filename>	name of the configuration file to use\n");
	printf("-geometry <xy>	X geometry specification (position/size)\n");
	printf("\n");
	printf("Use $DISPLAY environment variable to run asmail on a different display.\n");
	printf("\n");
}

/*
 * Set up the defaults that cannot be determined at compile time.
 */
void defaults() {
	strncpy(config_file_name, (char *) getenv("HOME"), MAX_INPUT_LENGTH);
	strncat(config_file_name, "/", MAX_INPUT_LENGTH-strlen(config_file_name));
	strncat(config_file_name, RCFILE, MAX_INPUT_LENGTH-strlen(config_file_name));
	/*
	 * Just in case, prevent the strings from overrunning
	 */
	config_file_name[MAX_INPUT_LENGTH] = '\0';
	x11_set.geometry[MAX_INPUT_LENGTH] = '\0';
	x11_set.title[MAX_INPUT_LENGTH] = '\0';
	x11_set.on_left[MAX_INPUT_LENGTH] = '\0';
	x11_set.on_middle[MAX_INPUT_LENGTH] = '\0';
	x11_set.on_right[MAX_INPUT_LENGTH] = '\0';
	x11_set.on_new_mail[MAX_INPUT_LENGTH] = '\0';
	x11_set.beep = 1;
	x11_set.shape = 0;
	strcpy(x11_set.geometry, "");
	strcpy(x11_set.title, "asmail");
	strcpy(x11_set.on_left, "");
	strcpy(x11_set.on_middle, "");
	strcpy(x11_set.on_right, "");
	strcpy(x11_set.on_new_mail, "");
}

/*
 * The function parses the command line switches and sets
 * global variables accordingly.
 * Returns 0 on success, -1 = error.
 */
int parse_cmd(int argc, char *argv[]) {
	int i;

	for (i=1; i<argc; i++) {
		if ( ! strcmp(argv[i], "-geometry") ) {
			if ( ++i >= argc ) {
				usage();
				return(-1);
			}
			strncpy(x11_set.geometry, argv[i], MAX_INPUT_LENGTH);
		} else if ( ! strcmp(argv[i], "-title") ) {
			if ( ++i >= argc ) {
				usage();
				return(-1);
			}
			strncpy(x11_set.title, argv[i], MAX_INPUT_LENGTH);
		} else if ( ! strcmp(argv[i], "-f") ) {
			if ( ++i >= argc ) {
				usage();
				return(-1);
			}
			strncpy(config_file_name, argv[i], MAX_INPUT_LENGTH);
		} else if ( ! strcmp(argv[i], "-V") ) {
			version();
			return(-1);
		} else if ( ! strcmp(argv[i], "-h") ) {
			usage();
			return(-1);
		} else if ( ! strcmp(argv[i], "-insecure") ) {
			flag_allow_insecure = 1;
		/*
		 * Unrecognised options
		 */
		} else if ( argv[i][0] == '-' ) {
			printf("asmail: Unknown option: %s\n", argv[i]);
			usage();
			return(-1);
		/*
		 * Old-style configuration file name
		 */
		} else {
			strncpy(config_file_name, argv[i], MAX_INPUT_LENGTH);
		}
	}
	return(0);
}

/*
 * Remove the blanks at the beginning and the end of the line
 */
void shorten(char * line) {
	int i = 0, j = 0;

	if (line[strlen(line)-1] == '\n')
		line[strlen(line)-1] = '\0';
    
	while (isspace(line[i]) && line[i]) 
		i++;
	if (i == 0 || i == strlen(line))
		return;
	while (line[i])
		line[j++] = line[i++];
	line[j] = '\0';

	i = strlen(line);
	while (isspace(line[i]) && i > 0)
		i--;
	if (line[i] == '\n')
		i--;
	line[++i] = '\0';
}

/*
 * Clean the config line, check and prepare for parsing
 * Returns 0 if ok
 *        -1 if error
 */
int parse_cfg_check(char * line) {
	if (line[strlen(line)-1] != '\n') {
		/* The line does not end in EOL - too long */
		printf("asmail: a line in configuration is too long, maximum length is %d.\n",
				MAX_INPUT_LENGTH);
		printf("asmail: Offending line: %s\n", line);
		return(-1);
	}
	/* Leading and trailing space are removed */
	shorten(line);
	return(0);
}

/*
 * Parse the X11 settings (were global definitions)
 */
int parse_cfg_x11(FILE * f, int * line_counter, char * filename) {
	char line[MAX_INPUT_LENGTH+1];
	char key[MAX_INPUT_LENGTH+1];
	char rest[MAX_INPUT_LENGTH+1];

	printf("X11 configuration found\n");
	while ( fgets(line, MAX_INPUT_LENGTH, f) ) {
		++(*line_counter);
		if ( parse_cfg_check(line) ) {
			return(-1);
		}
		/* Weed out comments and empty lines */
		if ( (line[0] == '#') || (line[0] == '\0') ) {
			continue;
		}
		/* Read the command */
		sscanf( line, "%s", key );
		printf("X11 Key: %s (%s)\n", key, line);
		if (!strcasecmp(key, "beep")) {
			sscanf(line, "%s %s", key, rest);
			x11_set.beep = strcasecmp(rest, "yes") ? 0 : 1;
			printf("Beep: %s\n", x11_set.beep?"Yes":"No");
		} else if (!strcasecmp(key, "shape")) {
			sscanf(line, "%s %s", key, rest);
			x11_set.shape = strcasecmp(rest, "yes") ? 0 : 1;
			printf("Shape: %s\n", x11_set.shape?"Yes":"No");
		} else if (!strcasecmp(key, "on_mouse_left")) {
			if ( strlen(line) > (strlen(key) + 1) ) {
				strcpy(x11_set.on_left, &line[strlen(key)+1]);
			}
			printf("On-left: %s\n", x11_set.on_left);
		} else if (!strcasecmp(key, "on_mouse_middle")) {
			if ( strlen(line) > (strlen(key) + 1) ) {
				strcpy(x11_set.on_middle, &line[strlen(key)+1]);
			}
			printf("On-middle: %s\n", x11_set.on_middle);
		} else if (!strcasecmp(key, "on_mouse_right")) {
			if ( strlen(line) > (strlen(key) + 1) ) {
				strcpy(x11_set.on_right, &line[strlen(key)+1]);
			}
			printf("On-right: %s\n", x11_set.on_right);
		} else if (!strcasecmp(key, "on_new_mail")) {
			if ( strlen(line) > (strlen(key) + 1) ) {
				strcpy(x11_set.on_new_mail, &line[strlen(key)+1]);
			}
			printf("On-new-mail: %s\n", x11_set.on_new_mail);
		} else if ( !strcmp(key, "}") ) {
			/* config block terminator */
			return(0);
		} else {
			printf("asmail: cannot understand config file %s\n",
					filename);
			printf("asmail: Offending line (%d): %s\n", 
					(*line_counter), line);
			return(-1);
		}
	}
	/*
	 * If we reach here, we did not find the terminating '}'
	 */
	printf("asmail: Did not find terminating '}' for X11 configuration block.\n");
	return(-1);
}

/*
 * Parse the configuration block related to a mailbox
 */
int parse_cfg_mbox(FILE * f, int * line_counter, char * filename) {
	char line[MAX_INPUT_LENGTH+1];
	char key[MAX_INPUT_LENGTH+1];
	char rest[MAX_INPUT_LENGTH+1];
	struct mbox_struct * new_mbox;
	char * str_ptr;

	printf("mailbox configuration found\n");
	new_mbox = (struct mbox_struct *) malloc(sizeof(struct mbox_struct));
	if ( ! new_mbox ) {
		printf("asmail: failed to allocate memory for the mailbox control structure.\n");
		return(-1);
	}
	if ( pthread_mutex_init(&new_mbox->mutex, NULL) ) {
		printf("asmail: parse_cfg_mbox: failed to initialize mutex.\n");
		return(-1);
	}
	new_mbox->type = 0;
	strcpy(new_mbox->file, "");
	strcpy(new_mbox->server, "");
	strcpy(new_mbox->user, "");
	strcpy(new_mbox->pass, "");
	strcpy(new_mbox->mbox, "");
	new_mbox->auth = AUTH_PLAIN;
	new_mbox->port = 0;
	new_mbox->update = 10;
	new_mbox->flags = FLAG_DEFAULT;
	new_mbox->status = STAT_IDLE;
	new_mbox->mail = MAIL_NONE;
	new_mbox->ctotal = 0;
	new_mbox->cnew = 0;
	new_mbox->next = NULL;
	while ( fgets(line, MAX_INPUT_LENGTH, f) ) {
		++(*line_counter);
		if ( parse_cfg_check(line) ) {
			return(-1);
		}
		/* Weed out comments and empty lines */
		if ( (line[0] == '#') || (line[0] == '\0') ) {
			continue;
		}
		/* Read the command */
		sscanf( line, "%s", key );
		printf("mbox Key: %s (%s)\n", key, line);
		if (!strcasecmp(key, "type")) {
			sscanf(line, "%s %s", key, rest);
			if ( !strcasecmp(rest, "mbox") ) {
				new_mbox->type = MBOX_FILE;
			} else if ( !strcasecmp(rest, "maildir") ) {
				new_mbox->type = MBOX_DIR;
			} else if ( !strcasecmp(rest, "pop3") ) {
				new_mbox->type = MBOX_POP3;
			} else if ( !strcasecmp(rest, "imap") ) {
				new_mbox->type = MBOX_IMAP;
			} else {
				printf("asmail: don't know how to handle mailbox type '%s'\n",
						rest);
				printf("asmail: Offending line (%d): %s\n", 
						(*line_counter), line);
			}
			printf("mbox type: %d\n", new_mbox->type);
		} else if (!strcasecmp(key, "update")) {
			sscanf(line, "%s %d", key, &new_mbox->update);
			printf("mbox update: %d\n", new_mbox->update);
		} else if (!strcasecmp(key, "file")) {
			sscanf(line, "%s %s", key, new_mbox->file);
			printf("mbox file: %s\n", new_mbox->file);
		} else if (!strcasecmp(key, "server")) {
			sscanf(line, "%s %s", key, new_mbox->server);
			printf("server: %s\n", new_mbox->server);
		} else if (!strcasecmp(key, "user")) {
			sscanf(line, "%s %s", key, new_mbox->user);
			printf("user: %s\n", new_mbox->user);
		} else if (!strcasecmp(key, "password")) {
			sscanf(line, "%s %s", key, new_mbox->pass);
			printf("password: %s\n", new_mbox->pass);
		} else if (!strcasecmp(key, "auth")) {
			/* Watch the magic: empty list (just the keyword
			 * without parameters) resets the settings to
			 * the default */
			new_mbox->auth = AUTH_PLAIN;
			if ( strlen(line) > (strlen(key) + 1) ) {
				new_mbox->auth = 0;
				strcpy(rest, &line[strlen(key)+1]);
				str_ptr = strtok(rest, " \t\n");
				while ( str_ptr ) {
					sscanf(str_ptr, "%s", key);
					if ( !strcasecmp(key, "plain")) {
						new_mbox->auth |= AUTH_PLAIN;
						printf("Allowed PASS\n");
					} else if (!strcasecmp(key, "md5")) {
						new_mbox->auth |= AUTH_MD5;
						printf("Allowed MD5\n");
					} else {
						printf("asmail: cannot understand authorisation scheme '%s'\n", key);
						printf("asmail: Offending configuration line (%d): %s\n", (*line_counter), line);
						return(-1);
					}
					str_ptr = strtok(NULL, " \t\n");
				}
			}
		} else if (!strcasecmp(key, "port")) {
			sscanf(line, "%s %d", key, new_mbox->port);
			printf("port: %d\n", new_mbox->port);
		} else if (!strcasecmp(key, "mailbox")) {
			sscanf(line, "%s %s", key, new_mbox->mbox);
			printf("mailbox: %s\n", new_mbox->mbox);
		} else if (!strcasecmp(key, "unread-is-new")) {
			sscanf(line, "%s %s", key, rest);
			if ( ! strcasecmp(rest, "yes") ) {
				new_mbox->flags |= FLAG_UNREAD_AS_NEW;
			} else {
				new_mbox->flags &= !FLAG_UNREAD_AS_NEW;
			}
			printf("Unread : %s\n", new_mbox->flags&FLAG_UNREAD_AS_NEW?"Yes":"No");
		} else if ( !strcmp(key, "}") ) {
			/* config block terminator */
			new_mbox->next = mbox;
			mbox = new_mbox;
			return(0);
		} else {
			printf("asmail: cannot understand config file %s\n",
					filename);
			printf("asmail: Offending line (%d): %s\n", 
					(*line_counter), line);
			return(-1);
		}
	}
	/*
	 * If we reach here, we did not find the terminating '}'
	 */
	printf("asmail: Did not find terminating '}' for mailbox configuration block.\n");
	return(-1);
}

/*
 * Parse the specified config file. Return 0 on success, -1 = error.
 */
int parse_cfg(char * filename) {
	FILE * f;
	struct stat f_stat;
	int line_counter = 0;
	char line[MAX_INPUT_LENGTH+1];
	char key[MAX_INPUT_LENGTH+1];
	char rest[MAX_INPUT_LENGTH+1];

	if ( stat(filename, &f_stat) ) {
		printf("asmail: Cannot stat the config file (%s).\n", filename);
		perror("asmail: parse_cfg");
		return(-1);
	}
	if ( ((f_stat.st_mode & S_IRWXG) != 0) || 
			((f_stat.st_mode & S_IRWXO) != 0) ) {
		if ( flag_allow_insecure ) {
			printf("asmail: configuration file mode is insecure but '-insecure' was given.\n");
		} else {
			printf("asmail: The configuration file has permissions for group and/or others set.\n");
			printf("asmail: Configuration file : '%s'.\n", filename);
			printf("asmail: Make sure the permissions are set to 0600 before proceeding.\n");
			return(-1);
		}
	}
	if ( ( f = fopen(filename, "r") ) == NULL ) {
		printf("asmail: Cannot open the config file (%s).\n", filename);
		perror("asmail: parse_cfg");
		return(-1);
	}

	while ( fgets(line, MAX_INPUT_LENGTH, f) ) {
		++line_counter;
		if ( parse_cfg_check(line) ) {
			return(-1);
		}
		/* Weed out comments and empty lines */
		if ( (line[0] == '#') || (line[0] == '\0') ) {
			continue;
		}
		/* Read the command */
		sscanf( line, "%s", key );
		printf("Key: %s (%s)\n", key, line);
		if (!strcasecmp(key, "x11")) {
			sscanf( line, "%s %s", key, rest);
			if ( !strcmp(rest, "{") ) {
				if ( parse_cfg_x11(f, &line_counter, filename) ) {
					return(-1);
				}
			}
		} else if (!strcasecmp(key, "mailbox")) {
			sscanf( line, "%s %s", key, rest);
			if ( !strcmp(rest, "{") ) {
				if ( parse_cfg_mbox(f, &line_counter, filename) ) {
					return(-1);
				}
			}
		} else {
			printf("asmail: cannot understand config file %s\n",
					filename);
			printf("asmail: Offending line (%d): %s\n", 
					line_counter, line);
			return(-1);
		}
	}

	if ( fclose(f) ) {
		printf("asmail: Cannot close the config file (%s).\n", filename);
		perror("asmail: parse_cfg");
		return(-1);
	}
	printf("Total of %d lines parsed in %s\n", line_counter, filename);
	return 0;
}

void mbox_thread( void * ptr ) {
	struct mbox_struct * mb;
	mb = (struct mbox_struct *) ptr;
	printf("Mailbox type : %d\n", mb->type);
	printf("Mailbox update : %d\n", mb->update);
	if ( mb->type == MBOX_FILE ) {
		mbox_handle( mb );
	} else if ( mb->type == MBOX_DIR ) {
		maildir_handle( mb );
	} else if ( mb->type == MBOX_POP3 ) {
		pop3_handle( mb );
	} else if ( mb->type == MBOX_IMAP ) {
		imap_handle( mb );
	} else {
		printf("Don't know how to handle the mailbox of format '%d'\n",
				mb->type);
		mb->status = STAT_FAIL;
		signal_update();
	}
	pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
	struct mbox_struct * tmp_mbox;

	x11_set.argc = argc;
	x11_set.argv = argv;

	if ( pthread_mutex_init(&update_lock, NULL) ) {
		printf("asmail: main: failed to initialize update mutex.\n");
		exit(-1);
	}
	if ( pthread_cond_init(&update_cv, NULL) ) {
		printf("asmail: main: failed to initialize update condition variable.\n");
		exit(-1);
	}
	if ( pthread_mutex_init(&md5_lock, NULL) ) {
		printf("asmail: main: failed to initialize MD5 mutex.\n");
		exit(-1);
	}
	if ( pthread_mutex_init(&check_lock, NULL) ) {
		printf("asmail: main: failed to initialize check mutex.\n");
		exit(-1);
	}
	if ( pthread_cond_init(&check_cv, NULL) ) {
		printf("asmail: main: failed to initialize check condition variable.\n");
		exit(-1);
	}
	defaults();
	if ( parse_cmd(argc, argv) )
		exit(-1);
	if ( parse_cfg(config_file_name) )
		exit(-1);
	if ( ! mbox ) {
		printf("asmail: no mailboxes to check, cowardly quitting.\n");
		return(-1);
	}
	printf("asmail: finished configuration, starting.\n");
	tmp_mbox = mbox;
	while (tmp_mbox) {
		pthread_create( &tmp_mbox->thread, pthread_attr_default,
			(void*)&mbox_thread, (void*) tmp_mbox);
		pthread_detach( tmp_mbox->thread );
		tmp_mbox = tmp_mbox->next;
	}

	pthread_create( &x11_set.thread, pthread_attr_default,
		(void*)&startx, (void*) &x11_set);
	pthread_detach( x11_set.thread );

	printf("asmail: finished startup.\n");
	while (1) {
		pthread_mutex_lock(&update_lock);
		while( update_count <= 0 ) {
			pthread_cond_wait(&update_cv, &update_lock);
		}
		printf("%d > ", update_count);
		--update_count;
		pthread_mutex_unlock(&update_lock);
		signal_xkick(); /* let X handler know we need to update */
		tmp_mbox = mbox;
		while ( tmp_mbox ) {
			printf("[%d] ", tmp_mbox->thread);
			switch (tmp_mbox->status) {
				case STAT_IDLE : printf(" ");
					 break;
				case STAT_RUN : printf("R");
					 break;
				case STAT_FAIL : printf("F");
					 break;
				case STAT_CONN : printf("C");
					 break;
				case STAT_LOGIN : printf("L");
					 break;
				case STAT_TIMEOUT : printf("T");
					 break;
			}
			printf(" ");
			switch (tmp_mbox->mail) {
				case MAIL_NONE : printf(" ");
					 break;
				case MAIL_OLD : printf("O");
					 break;
				case MAIL_NEW : printf("N");
			}
			printf(" %d/%d  ", tmp_mbox->cnew, tmp_mbox->ctotal);
			tmp_mbox = tmp_mbox->next;
		}
		printf("\n");
	}
	exit(0);
}
