/* $Id$ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* 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);
}