/*
** Copyright 2001-2011 Double Precision, Inc.  See COPYING for
** distribution information.
*/


/*
*/
#include	"sqwebmail.h"
#include	"config.h"
#include	"gpg.h"
#include	"pref.h"
#include	"cgi/cgi.h"
#include	"gpglib/gpglib.h"
#include	"unicode/unicode.h"
#include	"numlib/numlib.h"
#include	"rfc822/rfc822.h"
#include	"htmllibdir.h"
#include	<stdio.h>
#include	<string.h>
#include	<errno.h>
#if HAVE_SYS_WAIT_H
#include	<sys/wait.h>
#endif
#if HAVE_FCNTL_H
#include	<fcntl.h>
#endif

#ifndef WEXITSTATUS
#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

extern void output_scriptptrget();
extern void print_attrencodedlen(const char *, size_t, int, FILE *);
extern void print_safe(const char *);
const char *sqwebmail_content_charset;


static char gpgerrbuf[1024];
static size_t gpgerrcnt=0;

static void gpginiterr()
{
	gpgerrcnt=0;
}

static int gpg_error(const char *p, size_t l, void *dummy)
{
	while (l && gpgerrcnt < sizeof(gpgerrbuf)-1)
	{
		gpgerrbuf[gpgerrcnt++]= *p++;
		--l;
	}
	return (0);
}

static void gpg_error_save(const char *errmsg, void *dummy)
{
	gpg_error(errmsg, strlen(errmsg), dummy);
}

int gpgbadarg(const char *p)
{
	for ( ; *p; p++)
	{
		int c=(unsigned char)*p;

		if (c < ' ' || strchr("\",'`;*?()<>", c))
			return (1);
	}
	return (0);
}

static void dump_error()
{
	if (gpgerrcnt >= 0)
	{
		printf("<span style=\"color: #e00000\"><pre class=\"gpgerroutput\">");
		print_attrencodedlen (gpgerrbuf, gpgerrcnt, 1, stdout);
		printf("</pre></span>\n");
	}
}

struct listinfo {
	int issecret;
	const char *default_key;
} ;

static int show_key(const char *fingerprint, const char *shortname,
                    const char *key, int invalid,
		    struct gpg_list_info *gli)
{
	struct listinfo *li=(struct listinfo *)gli->voidarg;

	printf("<tr valign=\"middle\" class=\"%s\"><td>"
	       "<input type=\"radio\" name=\"%s\" value=\"",
	       li->issecret ? "gpgseckey":"gpgpubkey",
	       li->issecret ? "seckeyname":"pubkeyname");

	print_attrencodedlen(fingerprint, strlen(fingerprint), 0, stdout);
	printf("\"%s /></td><td><span class=\"tt\">",
	       li->default_key && strcmp(li->default_key, fingerprint) == 0
	       ? " checked=\"checked\"":"");
	print_safe(key);
	printf("</span></td></tr>\n");
	return (0);
}

static void listpubsec(int flag,
		       int (*callback_func)(const char *,
					    const char *,
					    const char *,
					    int,
					    struct gpg_list_info *),
		       const char *default_key
		       )
{
	int rc;
	struct gpg_list_info gli;
	struct listinfo li;

	li.issecret=flag;

	li.default_key=default_key;

	memset(&gli, 0, sizeof(gli));
	gli.charset=sqwebmail_content_charset;

	gli.disabled_msg=getarg("DISABLED");
	gli.revoked_msg=getarg("REVOKED");
	gli.expired_msg=getarg("EXPIRED");
	gli.voidarg= &li;

	gpginiterr();

	rc=libmail_gpg_listkeys(GPGDIR, flag, callback_func, gpg_error, &gli);

	if (rc)
	{
		dump_error();
	}
}

void gpglistpub()
{
	printf("<table width=\"100%%\" border=\"0\" cellspacing=\"2\" cellpadding=\"0\" class=\"gpgpubkeys\">");
	listpubsec(0, show_key, NULL);
	printf("</table>");
}

void gpglistsec()
{
	printf("<table width=\"100%%\" border=\"0\" cellspacing=\"2\" cellpadding=\"0\" class=\"gpgseckeys\">");
	listpubsec(1, show_key, NULL);
	printf("</table>");
}

static int select_key(const char *fingerprint, const char *shortname,
		      const char *key,
		      struct gpg_list_info *gli,
		      int is_select)
{
	printf("<option value=\"");
	print_attrencodedlen(fingerprint, strlen(fingerprint), 0, stdout);
	printf("\"%s>", is_select ? " selected='selected'":"");

	print_safe(shortname);
	printf("</option>");
	return (0);
}

static int select_key_default(const char *fingerprint, const char *shortname,
			      const char *key,
			      int invalid,
			      struct gpg_list_info *gli)
{
	struct listinfo *li=(struct listinfo *)gli->voidarg;

	return (select_key(fingerprint, shortname, key, gli,
			   li->default_key && strcmp(li->default_key,
						     fingerprint)
			   == 0));
}

void gpgselectkey()
{
	char *default_key=pref_getdefaultgpgkey();

	listpubsec(1, select_key_default, default_key);

	if (default_key)
		free(default_key);
}

void gpgselectpubkey()
{
	listpubsec(0, select_key_default, NULL);
}

void gpgselectprivkey()
{
	listpubsec(1, select_key_default, NULL);
}

/*
** Check if this encryption key address is included in the list of recipients
** of the message.
*/

static int knownkey(const char *shortname, const char *known_keys)
{
	struct rfc822t *t=rfc822t_alloc_new(shortname, NULL, NULL);
	struct rfc822a *a;
	int i;

	if (!t)
		return (0);

	a=rfc822a_alloc(t);

	if (!a)
	{
		rfc822t_free(t);
		return (0);
	}

	for (i=0; i<a->naddrs; i++)
	{
		char *p=rfc822_getaddr(a, i);
		int plen;
		const char *q;

		if (!p)
			continue;

		if (!*p)
		{
			free(p);
			continue;
		}

		plen=strlen(p);

		for (q=known_keys; *q; )
		{
			if (strncasecmp(q, p, plen) == 0 && q[plen] == '\n')
			{
				free(p);
				rfc822a_free(a);
				rfc822t_free(t);
				return (1);
			}

			while (*q)
				if (*q++ == '\n')
					break;
		}
		free(p);
	}
	rfc822a_free(a);
	rfc822t_free(t);
	return (0);
}

static int encrypt_key_default(const char *fingerprint, const char *shortname,
			       const char *key,
			       int invalid,
			       struct gpg_list_info *gli)
{
	struct listinfo *li=(struct listinfo *)gli->voidarg;

	if (invalid)
		return (0);

	return (select_key(fingerprint, shortname, key, gli,
			   knownkey(shortname, li->default_key)));
}

void gpgencryptkeys(const char *select_keys)
{
	listpubsec(0, encrypt_key_default, select_keys);
}


/*
** Create a new key
*/

static int dump_func(const char *p, size_t l, void *vp)
{
	int *ip=(int *)vp;

	while (l)
	{
		if (*ip >= 80)
		{
			printf("\n");
			*ip=0;
		}

		++*ip;

		switch (*p) {
		case '<':
			printf("&lt;");
			break;
		case '>':
			printf("&gt;");
			break;
		case '\n':
			*ip=0;
			/* FALLTHROUGH */
		default:
			putchar(*p);
			break;
		}

		++p;
		--l;
	}
	fflush(stdout);
	return (0);
}

static int timeout_func(void *vp)
{
	return (dump_func("*", 1, vp));
}

void gpgcreate()
{
	int linelen;

	const char *newname=cgi("newname");
	const char *newaddress=cgi("newaddress");
	const char *newcomment=cgi("newcomment");
	unsigned skl=atoi(cgi("skeylength"));
	unsigned ekl=atoi(cgi("ekeylength"));
	unsigned newexpire=atoi(cgi("newexpire"));
	char newexpirewhen=*cgi("newexpirewhen");
	const char *passphrase, *p;

	if (*newname == 0 || *newaddress == 0 || strchr(newaddress, '@') == 0
	    || gpgbadarg(newname) || gpgbadarg(newaddress)
	    || gpgbadarg(newcomment)
	    || ekl < 512 || ekl > 2048 || skl < 512 || skl > 1024)
	{
		printf("%s\n", getarg("BADARGS"));
		return;
	}
	passphrase=cgi("passphrase");
	if (strcmp(passphrase, cgi("passphrase2")))
	{
		printf("%s\n", getarg("PASSPHRASEFAIL"));
		return;
	}

	for (p=passphrase; *p; p++)
	{
		if ((int)(unsigned char)*p < ' ')
		{
			printf("%s\n", getarg("PASSPHRASEFAIL"));
			return;
		}
	}

	printf("<pre class=\"gpgcreate\">");

	linelen=0;

	libmail_gpg_genkey(GPGDIR, sqwebmail_content_charset,
			   newname, newaddress, newcomment,
			   skl, ekl,
			   newexpire, newexpirewhen,
			   passphrase,
			   &dump_func,
			   &timeout_func,
			   &linelen);
	printf("</pre>");
}

static void delkey(const char *keyname, int flag)
{
	int rc;

	if (gpgbadarg(keyname))
		return;

	gpginiterr();

	rc=libmail_gpg_deletekey(GPGDIR, flag, keyname, gpg_error, NULL);

	if (rc)
	{
		printf("<div class=\"indent\">%s\n", getarg("DELETEFAIL"));
		dump_error();
		printf("</div>\n");
	}
}

static FILE *passphrasefp()
{
	FILE *fp=NULL;
	const char *passphrase;

	passphrase=cgi("passphrase");
	if (*passphrase)
	{
		fp=tmpfile();
		if (fp)
		{
			fprintf(fp, "%s", passphrase);
			if (fflush(fp) || ferror(fp)
			    || lseek(fileno(fp), 0L, SEEK_SET) < 0
			    || fcntl(fileno(fp), F_SETFD, 0) < 0)
			{
				fclose(fp);
				fp=NULL;
			}
		}
	}
	return (fp);
}

static void signkey(const char *signthis, const char *signwith,
		    const char *trustlevel)
{
	int rc;
	FILE *fp=NULL;

	int n=atoi(trustlevel);

	if (n < 0 || n > 9)
		n=0;

	if (gpgbadarg(signthis) || gpgbadarg(signwith))
		return;

	gpginiterr();


	fp=passphrasefp();

	rc=libmail_gpg_signkey(GPGDIR, signthis, signwith,
			       fp ? fileno(fp):-1, gpg_error, n, NULL);

	if (fp)
		fclose(fp);

	if (rc)
	{
		printf("<div class=\"indent\">%s\n", getarg("SIGNFAIL"));
		dump_error();
		printf("</div>\n");
	}
}

static void setdefault(const char *def)
{
	if (gpgbadarg(def))
		return;

	pref_setdefaultgpgkey(def);
}

void gpgdo()
{
	if (*cgi("delpub"))
		delkey(cgi("pubkeyname"), 0);
	else if (*cgi("delsec") && *cgi("really"))
		delkey(cgi("seckeyname"), 1);
	else if (*cgi("sign"))
		signkey(cgi("pubkeyname"), cgi("seckeyname"),
			cgi("signlevel"));
	else if (*cgi("setdefault"))
		setdefault(cgi("seckeyname"));
}

static char gpgerrbuf[1024];

static int read_fd(char *buf, size_t cnt, void *vp)
{
	FILE *fp=(FILE *)vp;
	size_t i;
	int c;

	if (cnt == 0)
		return -1;

	--cnt;

	for (i=0; i<cnt; i++)
	{
		if ((c=getc(fp)) == EOF)
		{
			if (i == 0)
				return -1;
			break;
		}
		buf[i]=c;

		if (c == '\n')
		{
			++i;
			break;
		}
	}
	buf[i]=0;
	return 0;
}

static void write_fd(const char *p, size_t n, void *dummy)
{
	if (n == 0)
		return;

	if (fwrite(p, n, 1, (FILE *)dummy) != 1)
		exit(1);
}

int gpgdomsg(int in_fd, int out_fd, const char *signkey,
	     const char *encryptkeys)
{
	char *k=strdup(encryptkeys ? encryptkeys:"");
	int n;
	int i;
	char *p;
	char **argvec;
	FILE *passfd=NULL;
	char passfd_buf[NUMBUFSIZE];
	struct libmail_gpg_info gi;

	int in_dup, out_dup;
	FILE *in_fp, *out_fp;

	gpginiterr();

	if (!k)
	{
		enomem();
		return 1;
	}

	if ((in_dup=dup(in_fd)) < 0 || (in_fp=fdopen(in_dup, "r")) == NULL)
	{
		if (in_dup >= 0)
			close(in_dup);
		free(k);
		enomem();
		return 1;
	}

	if ((out_dup=dup(out_fd)) < 0 || (out_fp=fdopen(out_dup, "w")) == NULL)
	{
		if (out_dup >= 0)
			close(out_dup);
		fclose(in_fp);
		close(in_dup);
		free(k);
		enomem();
		return 1;
	}

	passfd=passphrasefp();

	n=0;
	for (p=k; (p=strtok(p, " ")) != NULL; p=NULL)
		++n;

	argvec=malloc((n * 2 + 22)*sizeof(char *));
	if (!argvec)
	{
		fclose(out_fp);
		close(out_dup);
		fclose(in_fp);
		close(in_dup);
		free(k);
		enomem();
		return 1;
	}

	memset(&gi, 0, sizeof(gi));

	gi.gnupghome=GPGDIR;
	if (passfd)
	{
		gi.passphrase_fd=libmail_str_size_t(fileno(passfd),
						    passfd_buf);
	}

	gi.input_func= read_fd;
	gi.input_func_arg= in_fp;
	gi.output_func= write_fd;
	gi.output_func_arg= out_fp;
	gi.errhandler_func= gpg_error_save;
	gi.errhandler_arg= NULL;


	i=0;
	argvec[i++] = "--no-tty";
	if (signkey)
	{
		argvec[i++]="--default-key";
		argvec[i++]=(char *)signkey;
	}

	argvec[i++]="--always-trust";

	for (p=strcpy(k, encryptkeys ? encryptkeys:"");
	     (p=strtok(p, " ")) != NULL; p=NULL)
	{
		argvec[i++]="-r";
		argvec[i++]=p;
	}
	argvec[i]=0;
	gi.argc=i;
	gi.argv=argvec;

	i=libmail_gpg_signencode(signkey ? 1:0,
				 n > 0 ? LIBMAIL_GPG_ENCAPSULATE:0,
				 &gi);

	free(argvec);
	fclose(out_fp);
	close(out_dup);
	fclose(in_fp);
	close(in_dup);
	free(k);
	if (passfd)
		fclose(passfd);

	return i;
}

void sent_gpgerrtxt()
{
	const char *p;

	for (p=gpgerrbuf; *p; p++)
	{
		switch (*p) {
		case '<':
			printf("&lt;");
			break;
		case '>':
			printf("&gt;");
			break;
		default:
			putchar((int)(unsigned char)*p);
			break;
		}
	}
}

void sent_gpgerrresume()
{
	output_scriptptrget();
	printf("&form=newmsg&pos=%s&draft=%s", cgi("pos"),
	       cgi("draftmessage"));
}

int gpgdecode(int in_fd, int out_fd)
{
	char passfd_buf[NUMBUFSIZE];
	FILE *fp=passphrasefp();
	int in_dup, out_dup;
	FILE *in_fp, *out_fp;
	struct libmail_gpg_info gi;
	char *argvec[2];
	int i;

	gpginiterr();

	if ((in_dup=dup(in_fd)) < 0 || (in_fp=fdopen(in_dup, "r")) == NULL)
	{
		if (in_dup >= 0)
			close(in_dup);
		fclose(fp);
		enomem();
		return 1;
	}

	if ((out_dup=dup(out_fd)) < 0 || (out_fp=fdopen(out_dup, "w")) == NULL)
	{
		if (out_dup >= 0)
			close(out_dup);
		fclose(in_fp);
		close(in_dup);
		fclose(fp);
		enomem();
		return 1;
	}

	memset(&gi, 0, sizeof(gi));

	gi.gnupghome=GPGDIR;
	if (fp)
	{
		gi.passphrase_fd=libmail_str_size_t(fileno(fp), passfd_buf);
	}

	gi.input_func= read_fd;
	gi.input_func_arg= in_fp;
	gi.output_func= write_fd;
	gi.output_func_arg= out_fp;
	gi.errhandler_func= gpg_error_save;
	gi.errhandler_arg= NULL;

	argvec[0] = "--no-tty";
	argvec[1]=NULL;
	gi.argc=1;
	gi.argv=argvec;

	i=libmail_gpg_decode(LIBMAIL_GPG_UNENCRYPT|LIBMAIL_GPG_CHECKSIGN,
			     &gi);
	fclose(out_fp);
	close(out_dup);
	fclose(in_fp);
	close(in_dup);
	if (fp)
		fclose(fp);

	if (i)
	{
		printf("<div class=\"indent\"><pre style=\"color: red;\">");
		sent_gpgerrtxt();
		printf("</pre></div>\n");
	}
	return (i);
}

int gpgexportkey(const char *fingerprint, int issecret,
		 int (*func)(const char *, size_t, void *),
		 void *arg)
{
	gpginiterr();

	return (libmail_gpg_exportkey(GPGDIR, issecret, fingerprint,
				      func,
				      gpg_error,
				      arg));
}

