/* $Id: parse-fn.c,v 1.27 2009/05/17 19:20:09 nicm Exp $ */

/*
 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <ctype.h>
#include <pwd.h>
#include <netdb.h>
#include <fnmatch.h>
#include <string.h>

#include "fdm.h"
#include "fetch.h"
#include "match.h"

void
free_replstrs(struct replstrs *rsp)
{
	free_strings((struct strings *) rsp); /* XXX */
}

char *
fmt_replstrs(const char *prefix, struct replstrs *rsp)
{
	return (fmt_strings(prefix, (struct strings *) rsp)); /* XXX */
}

void
free_strings(struct strings *sp)
{
	u_int	i;

	for (i = 0; i < ARRAY_LENGTH(sp); i++) {
		xfree(ARRAY_ITEM(sp, i));
	}
	ARRAY_FREE(sp);
}

char *
fmt_strings(const char *prefix, struct strings *sp)
{
	char	*buf, *s;
	size_t	 len;
	ssize_t	 off;
	u_int	 i;

	len = BUFSIZ;
	buf = xmalloc(len);

	ENSURE_SIZE(buf, len, strlen(prefix) + 1);
	off = xsnprintf(buf, len, "%s", prefix);

	for (i = 0; i < ARRAY_LENGTH(sp); i++) {
		s = ARRAY_ITEM(sp, i);
		ENSURE_SIZE(buf, len, off + strlen(s) + 4);
		off += xsnprintf(buf + off, len - off, "\"%s\" ", s);
	}

	if (off == 0) {
		ENSURE_SIZE(buf, len, off + 1);
		buf[off] = '\0';
	} else
		buf[off - 1] = '\0';
	return (buf);
}

struct account *
find_account(char *name)
{
	struct account	*a;

	TAILQ_FOREACH(a, &conf.accounts, entry) {
		if (strcmp(a->name, name) == 0)
			return (a);
	}
	return (NULL);
}

int
have_accounts(char *name)
{
	struct account	*a;

	TAILQ_FOREACH(a, &conf.accounts, entry) {
		if (account_match(name, a->name))
			return (1);
	}

	return (0);
}

struct action *
find_action(char *name)
{
	struct action	*t;

	TAILQ_FOREACH(t, &conf.actions, entry) {
		if (strcmp(t->name, name) == 0)
			return (t);
	}
	return (NULL);
}

struct actions *
match_actions(const char *name)
{
	struct action	*t;
	struct actions	*ta;

	ta = xmalloc(sizeof *ta);
	ARRAY_INIT(ta);

	TAILQ_FOREACH(t, &conf.actions, entry) {
		if (action_match(name, t->name))
			ARRAY_ADD(ta, t);
	}

	return (ta);
}

struct macro *
extract_macro(char *s)
{
	struct macro	*macro;
	char		*ptr;
	const char	*errstr;

	ptr = strchr(s, '=');
	if (ptr != NULL)
		*ptr++ = '\0';
	if (strlen(s) > MAXNAMESIZE)
		yyerror("macro name too long: %s", s);

	macro = xmalloc(sizeof *macro);
	strlcpy(macro->name, s, sizeof macro->name);

	switch (*s) {
	case '$':
		macro->type = MACRO_STRING;
		if (ptr == NULL)
			macro->value.str = xstrdup("");
		else
			macro->value.str = xstrdup(ptr);
		break;
	case '%':
		macro->type = MACRO_NUMBER;
		if (ptr == NULL)
			macro->value.num = 0;
		else {
			macro->value.num = strtonum(ptr, 0, LLONG_MAX, &errstr);
			if (errstr != NULL)
				yyerror("number is %s: %s", errstr, ptr);
		}
		break;
	default:
		yyerror("invalid macro: %s", s);
	}

	return (macro);
}

struct macro *
find_macro(const char *name)
{
	struct macro	*macro;

	TAILQ_FOREACH(macro, &parse_macros, entry) {
		if (strcmp(macro->name, name) == 0)
			return (macro);
	}

	return (NULL);
}

void
print_rule(struct rule *r)
{
	struct expritem	*ei;
	char		 s[BUFSIZ], *su, *ss, desc[DESCBUFSIZE];

	*s = '\0';
	TAILQ_FOREACH(ei, r->expr, entry) {
		switch (ei->op) {
		case OP_AND:
			strlcat(s, "and ", sizeof s);
			break;
		case OP_OR:
			strlcat(s, "or ", sizeof s);
			break;
		case OP_NONE:
			break;
		}
		if (ei->inverted)
			strlcat(s, "not ", sizeof s);
		ei->match->desc(ei, desc, sizeof desc);
		strlcat(s, desc, sizeof s);
		strlcat(s, " ", sizeof s);
	}
	if (r->users != NULL)
		su = fmt_replstrs(" users=", r->users);
	else
		su = xstrdup("");
	if (r->lambda != NULL) {
		make_actlist(r->lambda->list, desc, sizeof desc);
		log_debug2("added rule %u:%s matches=%slambda=%s", r->idx,
		    su, s, desc);
	} else if (r->actions != NULL) {
		ss = fmt_replstrs("", r->actions);
		log_debug2("added rule %u:%s matches=%sactions=%s", r->idx,
		    su, s, ss);
		xfree(ss);
	} else
		log_debug2("added rule %u: matches=%snested", r->idx, s);
	xfree(su);
}

void
print_action(struct action *t)
{
	char		 s[BUFSIZ], *su;
	size_t		 off;

	if (t->users != NULL)
		su = fmt_replstrs(" users=", t->users);
	else
		su = xstrdup("");
	off = xsnprintf(s, sizeof s, "added action \"%s\":%s deliver=",
	    t->name, su);
	xfree(su);

	make_actlist(t->list, s + off, (sizeof s) - off);
	log_debug2("%s", s);
}

void
make_actlist(struct actlist *tl, char *buf, size_t len)
{
	struct actitem			*ti;
	struct deliver_action_data	*data;
	char				 desc[DESCBUFSIZE], *s;
	size_t		 		 off;

	off = 0;
	TAILQ_FOREACH(ti, tl, entry) {
		if (ti->deliver != NULL)
			ti->deliver->desc(ti, desc, sizeof desc);
		else {
			data = ti->data;
			s = fmt_replstrs("", data->actions);
			xsnprintf(desc, sizeof desc, "action %s", s);
			xfree(s);
		}
		off += xsnprintf(buf + off, len - off, "%u:%s ", ti->idx, desc);
		if (off >= len)
			break;
	}
}

void
free_action(struct action *t)
{
	struct actitem	*ti;

	if (t->users != NULL) {
		free_replstrs(t->users);
		ARRAY_FREEALL(t->users);
	}

	while (!TAILQ_EMPTY(t->list)) {
		ti = TAILQ_FIRST(t->list);
		TAILQ_REMOVE(t->list, ti, entry);

		free_actitem(ti);
	}
	xfree(t->list);

	xfree(t);
}

void
free_actitem(struct actitem *ti)
{
	if (ti->deliver == &deliver_pipe) {
		struct deliver_pipe_data		*data = ti->data;
		xfree(data->cmd.str);
	} else if (ti->deliver == &deliver_rewrite) {
		struct deliver_rewrite_data		*data = ti->data;
		xfree(data->cmd.str);
	} else if (ti->deliver == &deliver_write) {
		struct deliver_write_data		*data = ti->data;
		xfree(data->path.str);
	} else if (ti->deliver == &deliver_maildir) {
		struct deliver_maildir_data		*data = ti->data;
		xfree(data->path.str);
	} else if (ti->deliver == &deliver_remove_header) {
		struct deliver_remove_header_data	*data = ti->data;
		free_replstrs(data->hdrs);
		ARRAY_FREEALL(data->hdrs);
	} else if (ti->deliver == &deliver_add_header) {
		struct deliver_add_header_data		*data = ti->data;
		xfree(data->hdr.str);
		xfree(data->value.str);
	} else if (ti->deliver == &deliver_mbox) {
		struct deliver_mbox_data		*data = ti->data;
		xfree(data->path.str);
	} else if (ti->deliver == &deliver_tag) {
		struct deliver_tag_data			*data = ti->data;
		xfree(data->key.str);
		if (data->value.str != NULL)
			xfree(data->value.str);
	} else if (ti->deliver == &deliver_add_to_cache) {
		struct deliver_add_to_cache_data	*data = ti->data;
		xfree(data->key.str);
		xfree(data->path);
	} else if (ti->deliver == &deliver_remove_from_cache) {
		struct deliver_remove_from_cache_data	*data = ti->data;
		xfree(data->key.str);
		xfree(data->path);
	} else if (ti->deliver == &deliver_smtp) {
		struct deliver_smtp_data		*data = ti->data;
		if (data->to.str != NULL)
			xfree(data->to.str);
		if (data->from.str != NULL)
			xfree(data->from.str);
		xfree(data->server.host);
		xfree(data->server.port);
		if (data->server.ai != NULL)
			freeaddrinfo(data->server.ai);
	} else if (ti->deliver == &deliver_imap) {
		struct deliver_imap_data		*data = ti->data;
		if (data->user != NULL)
			xfree(data->user);
		if (data->pass != NULL)
			xfree(data->pass);
		xfree(data->folder.str);
		xfree(data->server.host);
		xfree(data->server.port);
		if (data->server.ai != NULL)
			freeaddrinfo(data->server.ai);
	} else if (ti->deliver == NULL) {
		struct deliver_action_data		*data = ti->data;
		free_replstrs(data->actions);
		ARRAY_FREEALL(data->actions);
	}
	if (ti->data != NULL)
		xfree(ti->data);

	xfree(ti);
}

void
free_rule(struct rule *r)
{
	struct rule	*rr;
	struct expritem	*ei;

	if (r->users != NULL) {
		free_replstrs(r->users);
		ARRAY_FREEALL(r->users);
	}
	if (r->actions != NULL) {
		free_replstrs(r->actions);
		ARRAY_FREEALL(r->actions);
	}

	if (r->lambda != NULL)
		free_action(r->lambda);

	while (!TAILQ_EMPTY(&r->rules)) {
		rr = TAILQ_FIRST(&r->rules);
		TAILQ_REMOVE(&r->rules, rr, entry);
		free_rule(rr);
	}
	if (r->expr == NULL) {
		xfree(r);
		return;
	}

	while (!TAILQ_EMPTY(r->expr)) {
		ei = TAILQ_FIRST(r->expr);
		TAILQ_REMOVE(r->expr, ei, entry);

		if (ei->match == &match_regexp) {
			struct match_regexp_data	*data = ei->data;
			re_free(&data->re);
		} else if (ei->match == &match_account) {
			struct match_account_data	*data = ei->data;
			free_replstrs(data->accounts);
			ARRAY_FREEALL(data->accounts);
		} else if (ei->match == &match_command) {
			struct match_command_data	*data = ei->data;
			xfree(data->cmd.str);
			if (data->re.str != NULL)
				re_free(&data->re);
		} else if (ei->match == &match_tagged) {
			struct match_tagged_data	*data = ei->data;
			xfree(data->tag.str);
		} else if (ei->match == &match_string) {
			struct match_string_data	*data = ei->data;
			xfree(data->str.str);
			re_free(&data->re);
		} else if (ei->match == &match_in_cache) {
			struct match_in_cache_data	*data = ei->data;
			xfree(data->key.str);
			xfree(data->path);
		} else if (ei->match == &match_attachment) {
			struct match_attachment_data	*data = ei->data;
			if (data->op == ATTACHOP_ANYTYPE ||
			    data->op == ATTACHOP_ANYNAME)
				xfree(data->value.str.str);
		}
		if (ei->data != NULL)
			xfree(ei->data);

		xfree(ei);
	}
	xfree(r->expr);

	xfree(r);
}

void
free_cache(struct cache *cache)
{
	xfree(cache->path);
	xfree(cache);
}

void
free_account(struct account *a)
{
	if (a->users != NULL) {
		free_replstrs(a->users);
		ARRAY_FREEALL(a->users);
	}

	if (a->fetch == &fetch_pop3) {
		struct fetch_pop3_data		*data = a->data;
 		if (data->path != NULL)
			xfree(data->path);
		xfree(data->user);
		xfree(data->pass);
		xfree(data->server.host);
		xfree(data->server.port);
		if (data->server.ai != NULL)
			freeaddrinfo(data->server.ai);
	} else if (a->fetch == &fetch_pop3pipe) {
		struct fetch_pop3_data		*data = a->data;
 		if (data->path != NULL)
			xfree(data->path);
		xfree(data->user);
		xfree(data->pass);
		xfree(data->pipecmd);
	} else if (a->fetch == &fetch_imap) {
		struct fetch_imap_data		*data = a->data;
		xfree(data->user);
		xfree(data->pass);
		free_strings(data->folders);
 		ARRAY_FREEALL(data->folders);
		xfree(data->server.host);
		xfree(data->server.port);
		if (data->server.ai != NULL)
			freeaddrinfo(data->server.ai);
	} else if (a->fetch == &fetch_imappipe) {
		struct fetch_imap_data		*data = a->data;
		if (data->user != NULL)
			xfree(data->user);
		if (data->pass != NULL)
			xfree(data->pass);
		free_strings(data->folders);
 		ARRAY_FREEALL(data->folders);
		xfree(data->pipecmd);
	} else if (a->fetch == &fetch_maildir) {
		struct fetch_maildir_data	*data = a->data;
		free_strings(data->maildirs);
		ARRAY_FREEALL(data->maildirs);
	} else if (a->fetch == &fetch_mbox) {
		struct fetch_mbox_data	*data = a->data;
		free_strings(data->mboxes);
		ARRAY_FREEALL(data->mboxes);
	} else if (a->fetch == &fetch_nntp) {
		struct fetch_nntp_data		*data = a->data;
		free_strings(data->names);
		ARRAY_FREEALL(data->names);
		xfree(data->path);
		xfree(data->server.host);
		xfree(data->server.port);
		if (data->server.ai != NULL)
			freeaddrinfo(data->server.ai);
	}
	if (a->data != NULL)
		xfree(a->data);

	xfree(a);
}

char *
expand_path(const char *path, const char *home)
{
	const char	*src;
	char		*ptr;
	struct passwd	*pw;

	src = path;
	while (isspace((u_char) *src))
		src++;
	if (src[0] != '~')
		return (NULL);

	/* ~ */
	if (src[1] == '\0')
		return (xstrdup(home));

	/* ~/ */
	if (src[1] == '/') {
		xasprintf(&ptr, "%s/%s", home, src + 2);
		return (ptr);
	}

	/* ~user or ~user/ */
	ptr = strchr(src + 1, '/');
	if (ptr != NULL)
		*ptr = '\0';

	pw = getpwnam(src + 1);
	if (pw == NULL || pw->pw_dir == NULL || *pw->pw_dir == '\0') {
		endpwent();
		return (NULL);
	}

	if (ptr == NULL)
		ptr = xstrdup(pw->pw_dir);
	else
		xasprintf(&ptr, "%s/%s", pw->pw_dir, ptr + 1);

	endpwent();

	return (ptr);
}

void
find_netrc(const char *host, char **user, char **pass)
{
	char	*cause;

	if (find_netrc1(host, user, pass, &cause) != 0)
		yyerror("%s", cause);
}

int
find_netrc1(const char *host, char **user, char **pass, char **cause)
{
	FILE	*f;

	if ((f = netrc_open(conf.user_home, cause)) == NULL)
		return (-1);

	if (netrc_lookup(f, host, user, pass) != 0) {
		xasprintf(cause, "error reading .netrc");
		return (-1);
	}

	if (user != NULL) {
		if (*user == NULL) {
			xasprintf(cause,
			    "can't find user for \"%s\" in .netrc", host);
			goto bad;
		}
		if (**user == '\0') {
			xasprintf(cause, "invalid user");
			goto bad;
		}
	}
	if (pass != NULL) {
		if (*pass == NULL) {
			xasprintf(cause,
			    "can't find pass for \"%s\" in .netrc", host);
			goto bad;
		}
		if (**pass == '\0') {
			xasprintf(cause, "invalid pass");
			goto bad;
		}
	}

	fclose(f);
	return (0);

bad:
	fclose(f);
	return (-1);
}

char *
run_command(const char *s, const char *file)
{
	struct cmd	*cmd;
	char		*lbuf, *sbuf;
	size_t		 llen, slen;
	char		*cause, *out, *err;
	int		 status;

	if (*s == '\0')
		yyerror("empty command");

	log_debug3("running command: %s", s);
	if ((cmd = cmd_start(s, CMD_OUT, NULL, 0, &cause)) == NULL)
		yyerror("%s: %s", s, cause);

	llen = IO_LINESIZE;
	lbuf = xmalloc(llen);

	slen = 1;
	sbuf = xmalloc(slen);

	*sbuf = '\0';
	do {
		status = cmd_poll(
		    cmd, &out, &err, &lbuf, &llen, DEFTIMEOUT, &cause);
		if (status == -1) {
			cmd_free(cmd);
			yyerror("%s: %s", s, cause);
		}
		if (status == 0) {
			if (err != NULL) {
				log_warnx("%s: %s: %s", file, s, err);
			}
			if (out != NULL) {
				slen += strlen(out) + 1;
				sbuf = xrealloc(sbuf, 1, slen);
				strlcat(sbuf, out, slen);
				strlcat(sbuf, "\n", slen);
			}
		}
	} while (status == 0);
	status--;

	xfree(lbuf);

	if (status != 0) {
		cmd_free(cmd);
		yyerror("%s: command returned %d", s, status);
	}

	cmd_free(cmd);

	slen--;
	while (slen > 0 && sbuf[slen - 1] == '\n') {
		sbuf[slen - 1] = '\0';
		slen--;
	}
	return (sbuf);
}
