/*
 * Send SMS through email
 *
 * Copyright (C) 2010 by Multi-Tech Systems
 *
 * Author: James Maki <jmaki@multitech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <string.h>
#include <ctype.h>

#include "global.h"

#if HAVE_LIBESMTP
#include <openssl/ssl.h>
#include <auth-client.h>
#include <libesmtp.h>
#endif

#include "utils.h"
#include "cmd_options.h"
#include "sms_utils.h"
#include "atcmd.h"
#include "phonebook.h"

struct send_options {
	char *file;
	char *subject;
};

#if HAVE_LIBESMTP
static FILE *open_message_file(struct send_options *options)
{
	FILE *fp;
	int tmp;

	if (options->file) {
		fp = fopen(options->file, "r");
		if (!fp) {
			log_error("failed to open %s for reading", options->file);
			return NULL;
		}
	} else if (Global.core.interactive && Global.core.edit_file && Global.core.editor) {
		tmp = systemf("%s %s", Global.core.editor, Global.core.edit_file);
		if (tmp < 0 || !WIFEXITED(tmp) || WEXITSTATUS(tmp)) {
			log_error("edit failed");
			return NULL;
		}

		char *path = shell_path_expand(Global.core.edit_file);
		if (!path) {
			return NULL;
		}

		fp = fopen(path, "r");
		if (!fp) {
			log_error("failed to open %s for reading", path);
			free(path);
			return NULL;
		}
		free(path);
	} else {
		fp = stdin;
	}

	return fp;
}

char *fgets_noecho(char *buf, size_t len, FILE *stream)
{
	int err;
	struct termios params;
	char *ret = NULL;
	tcflag_t c_lflag;

	err = tcgetattr(fileno(stream), &params);
	if (err < 0) {
		log_error("tcgetattr failed: %m");
		return NULL;
	}

	c_lflag = params.c_lflag;

	params.c_lflag &= ~ECHO;
	params.c_lflag |= ECHONL;

	err = tcsetattr(fileno(stream), TCSAFLUSH, &params);
	if (err < 0) {
		log_error("tcsetattr failed: %m");
		return NULL;
	}

	ret = fgets(buf, len, stream);

	params.c_lflag = c_lflag;

	err = tcsetattr(fileno(stream), TCSAFLUSH, &params);
	if (err < 0) {
		log_error("tcsetattr failed: %m");
		return NULL;
	}

	return ret;
}

struct auth_callback_data {
	char user[128];
	char passwd[128];
	char realm[128];
};

int auth_callback(auth_client_request_t request, char **result, int fields, void *arg)
{
	struct auth_callback_data *data = (struct auth_callback_data *) arg;
	char *cp;
	int i;

	for (i = 0; i < fields; i++) {
		if (request[i].flags & AUTH_PASS) {
			if (!Global.smtp.passwd) {
				printf("Password: ");
				fflush(stdout);

				cp = fgets_noecho(data->passwd,
						sizeof(data->passwd), stdin);
				if (!cp) {
					*data->passwd = '\0';
				}

				cp = strrchr(data->passwd, '\n');
				if (cp) {
					*cp = '\0';
				}

				result[i] = data->passwd;
			} else {
				result[i] = Global.smtp.passwd;
			}
		} else if (request[i].flags & AUTH_USER) {
			if (!Global.smtp.user) {
				printf("Username: ");
				fflush(stdout);

				cp = fgets(data->user, sizeof(data->user), stdin);
				if (!cp) {
					*data->user = '\0';
				}

				cp = strrchr(data->user, '\n');
				if (cp) {
					*cp = '\0';
				}

				result[i] = data->user;
			} else {
				result[i] = Global.smtp.user;
			}
		} else if (request[i].flags & AUTH_REALM) {
			printf("Realm: ");
			fflush(stdout);

			cp = fgets(data->realm, sizeof(data->realm), stdin);
			if (!cp) {
				*data->realm = '\0';
			}

			cp = strrchr(data->realm, '\n');
			if (cp) {
				*cp = '\0';
			}

			result[i] = data->realm;
		} else {
			result[i] = NULL;
		}
	}

	return 1;
}

int tls_callback(char *buf, int len, int flag, void *arg)
{
	char *cp;

	printf("Certificate Password: ");
	fflush(stdout);

	cp = fgets_noecho(buf, len, stdin);
	if (!cp) {
		*buf = '\0';
	}
	cp = strrchr(buf, '\n');
	if (cp) {
		*cp = '\0';
	}

	return strlen(buf);
}

void event_callback(smtp_session_t session, int event, void *args, ...)
{
	va_list ap;
	long long_arg;
	int *ok;

	va_start(ap, args);

	switch (event) {
	case SMTP_EV_CONNECT:
	case SMTP_EV_MAILSTATUS:
	case SMTP_EV_RCPTSTATUS:
	case SMTP_EV_MESSAGEDATA:
	case SMTP_EV_MESSAGESENT:
	case SMTP_EV_DISCONNECT:
		break;
	case SMTP_EV_WEAK_CIPHER:
		long_arg = va_arg(ap, long);
		log_debug("SMTP_EV_WEAK_CIPHER: bits: %ld", long_arg);
		ok = va_arg(ap, int *);
		*ok = 1;
		break;

	case SMTP_EV_STARTTLS_OK:
		log_debug("SMTP_EV_STARTTLS_OK");
		break;

	case SMTP_EV_INVALID_PEER_CERTIFICATE:
		long_arg = va_arg(ap, long);
		log_debug("SMTP_EV_INVALID_PEER_CERTIFICATE: error: %ld", long_arg);
		ok = va_arg(ap, int *);
		*ok = 1;
		break;

	case SMTP_EV_NO_PEER_CERTIFICATE:
		log_debug("SMTP_EV_NO_PEER_CERTIFICATE");
		ok = va_arg(ap, int *);
		*ok = 1;
		break;

	case SMTP_EV_WRONG_PEER_CERTIFICATE:
		log_debug("SMTP_EV_WRONG_PEER_CERTIFICATE");
		ok = va_arg(ap, int *);
		*ok = 1;
		break;

	case SMTP_EV_NO_CLIENT_CERTIFICATE:
		log_debug("SMTP_EV_NO_CLIENT_CERTIFICATE");
		ok = va_arg(ap, int *);
		*ok = 1;
		break;

	default:
		log_debug("unknown event %d", event);
	}

	va_end(ap);
}

static void print_recipient_status(smtp_recipient_t recipient,
				const char *mailbox, void *arg)
{
	const smtp_status_t *status;

	status = smtp_recipient_status(recipient);

	printf("  - addr: %s\n", mailbox);
	printf("    code: %d\n", status->code);
	printf("    message: %s\n", status->text);
}

static int do_send_email(struct send_options *options, int argc, char **argv)
{
	char buf[1024];
	struct sigaction sa;
	struct auth_callback_data auth_arg;
	FILE *fp = NULL;
	int ret = false;
	int tmp;
	int i;

	smtp_session_t session = NULL;
	smtp_message_t message;
	auth_context_t authctx = NULL;
	const smtp_status_t *status;

	auth_client_init();

	if (argc < 1) {
		log_error("at least one recipient addr is required");
		goto done;
	}

	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGPIPE, &sa, NULL);

	if (!Global.smtp.server) {
		log_error("smtp config missing");
		goto done;
	}

	session = smtp_create_session();
	message = smtp_add_message(session);

	if (Global.smtp.encryption && !strcmp(Global.smtp.encryption, "tls")) {
		smtp_starttls_enable(session, Starttls_REQUIRED);
	}

	if (strchr(Global.smtp.server, ':') || !Global.smtp.port) {
		snprintf(buf, sizeof(buf), "%s", Global.smtp.server);
	} else {
		snprintf(buf, sizeof(buf), "%s:%d",
				Global.smtp.server, Global.smtp.port);
	}
	smtp_set_server(session, buf);

	authctx = auth_create_context();
	if (!authctx) {
		log_error("auth_create_context failed");
		goto done;
	}
	auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0);
	auth_set_interact_cb(authctx, auth_callback, &auth_arg);
	smtp_starttls_set_password_cb(tls_callback, NULL);
	smtp_set_eventcb(session, event_callback, NULL);
	smtp_auth_set_context(session, authctx);

	if (Global.user.email) {
		smtp_set_reverse_path(message, Global.user.email);
		smtp_set_header(message, "From", Global.user.name,
				Global.user.email);
	}
	if (options->subject) {
		smtp_set_header(message, "Subject", options->subject);
	}

	fp = open_message_file(options);
	if (!fp) {
		log_error("opening sms message file failed");
		goto done;
	}

	tmp = smtp_set_message_fp(message, fp);

	int nr_rcpts = 0;
	for (i = 0; i < argc; i++) {
		if (strchr(argv[i], '@')) {
			smtp_add_recipient(message, argv[i]);
			nr_rcpts++;
		} else if (Global.send_email.domain) {
			snprintf(buf, sizeof(buf), "%s@%s",
					argv[i], Global.send_email.domain);
			smtp_add_recipient(message, buf);
			nr_rcpts++;
		} else {
			log_notice("skipping recipient %s", argv[i]);
		}
	}

	if (!nr_rcpts) {
		log_error("email contains no recipients");
		goto done;
	}

	tmp = smtp_start_session(session);
	if (!tmp) {
		smtp_strerror(smtp_errno(), buf, sizeof(buf));
		log_error("smtp_start_session: %s", buf);
		goto done;
	}

	status = smtp_message_transfer_status(message);
	printf("transfer-status:\n");
	printf("  - code: %d\n", status->code);
	printf("    message: %s\n", status->text ?: "");
	printf("recipient-status:\n");

	smtp_enumerate_recipients(message, print_recipient_status, NULL);

	if (status->code == 250 && Global.core.interactive && Global.core.edit_file) {
		systemf("rm -f %s", Global.core.edit_file);
	}

	ret = true;

done:
	if (authctx) {
		auth_destroy_context(authctx);
	}
	if (session) {
		smtp_destroy_session(session);
	}
	if (fp) {
		fclose(fp);
	}

	auth_client_exit();

	return ret;
}
#else
static int do_send_email(struct send_options *options, int argc, char **argv)
{
	return false;
}
#endif

static char *short_options = __CMD_OPT_FILE;
static struct option long_options[] = {
	{"file", 1, NULL, CMD_OPT_FILE},
	{"subject", 1, NULL, CMD_OPT_SUBJECT},
	{NULL, 0, NULL, 0},
};

void sms_send_email_help(FILE *out) {
	fprintf(out, "usage: send-email [ OPTIONS ... ] <number>\n");
	fprintf(out, "where  OPTIONS := { \n");
	fprintf(out, "         -f, --file <input-file> |\n");
	fprintf(out, "         --subject <email-subject>\n");
	fprintf(out, "       }\n");
	fprintf(out, "\n");
}

int sms_send_email(int argc, char **argv)
{
	int i;
	int option_index;
	int ret;

	struct send_options options;
	options.file = NULL;
	options.subject = NULL;

	while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) {
		switch (i) {
		case 0:
			break;

		case CMD_OPT_FILE:
			options.file = optarg;
			break;

		case CMD_OPT_SUBJECT:
			options.subject = optarg;
			break;

		default:
			sms_send_email_help(stderr);
			return false;
		}
	}

	if (optind >= argc) {
		sms_send_email_help(stderr);
		return false;
	}
	argc -= optind;
	argv += optind;

	ret = do_send_email(&options, argc, argv);

	return ret;
}