/*
* Copyright (c) 2017 Eric Leblond <eric@regit.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <nftables/libnftables.h>
#include <erec.h>
#include <mnl.h>
#include <parser.h>
#include <utils.h>
#include <iface.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
static int nft_netlink(struct nft_ctx *nft,
struct list_head *cmds, struct list_head *msgs,
struct mnl_socket *nf_sock)
{
uint32_t batch_seqnum, seqnum = 0, num_cmds = 0;
struct netlink_ctx ctx = {
.nft = nft,
.msgs = msgs,
.list = LIST_HEAD_INIT(ctx.list),
.batch = mnl_batch_init(),
};
struct cmd *cmd;
struct mnl_err *err, *tmp;
LIST_HEAD(err_list);
int ret = 0;
if (list_empty(cmds))
goto out;
batch_seqnum = mnl_batch_begin(ctx.batch, mnl_seqnum_alloc(&seqnum));
list_for_each_entry(cmd, cmds, list) {
ctx.seqnum = cmd->seqnum = mnl_seqnum_alloc(&seqnum);
ret = do_command(&ctx, cmd);
if (ret < 0) {
netlink_io_error(&ctx, &cmd->location,
"Could not process rule: %s",
strerror(errno));
goto out;
}
num_cmds++;
}
if (!nft->check)
mnl_batch_end(ctx.batch, mnl_seqnum_alloc(&seqnum));
if (!mnl_batch_ready(ctx.batch))
goto out;
ret = mnl_batch_talk(&ctx, &err_list, num_cmds);
if (ret < 0) {
netlink_io_error(&ctx, NULL,
"Could not process rule: %s", strerror(errno));
goto out;
}
if (!list_empty(&err_list))
ret = -1;
list_for_each_entry_safe(err, tmp, &err_list, head) {
list_for_each_entry(cmd, cmds, list) {
if (err->seqnum == cmd->seqnum ||
err->seqnum == batch_seqnum) {
netlink_io_error(&ctx, &cmd->location,
"Could not process rule: %s",
strerror(err->err));
errno = err->err;
if (err->seqnum == cmd->seqnum) {
mnl_err_list_free(err);
break;
}
}
}
}
out:
mnl_batch_reset(ctx.batch);
return ret;
}
static void nft_init(struct nft_ctx *ctx)
{
mark_table_init(ctx);
realm_table_rt_init(ctx);
devgroup_table_init(ctx);
ct_label_table_init(ctx);
}
static void nft_exit(struct nft_ctx *ctx)
{
ct_label_table_exit(ctx);
realm_table_rt_exit(ctx);
devgroup_table_exit(ctx);
mark_table_exit(ctx);
}
EXPORT_SYMBOL(nft_ctx_add_include_path);
int nft_ctx_add_include_path(struct nft_ctx *ctx, const char *path)
{
char **tmp;
int pcount = ctx->num_include_paths;
tmp = realloc(ctx->include_paths, (pcount + 1) * sizeof(char *));
if (!tmp)
return -1;
ctx->include_paths = tmp;
if (asprintf(&ctx->include_paths[pcount], "%s", path) < 0)
return -1;
ctx->num_include_paths++;
return 0;
}
EXPORT_SYMBOL(nft_ctx_clear_include_paths);
void nft_ctx_clear_include_paths(struct nft_ctx *ctx)
{
while (ctx->num_include_paths)
xfree(ctx->include_paths[--ctx->num_include_paths]);
xfree(ctx->include_paths);
ctx->include_paths = NULL;
}
static void nft_ctx_netlink_init(struct nft_ctx *ctx)
{
ctx->nf_sock = nft_mnl_socket_open();
}
EXPORT_SYMBOL(nft_ctx_new);
struct nft_ctx *nft_ctx_new(uint32_t flags)
{
static bool init_once;
struct nft_ctx *ctx;
if (!init_once) {
init_once = true;
gmp_init();
#ifdef HAVE_LIBXTABLES
xt_init();
#endif
}
ctx = xzalloc(sizeof(struct nft_ctx));
nft_init(ctx);
ctx->state = xzalloc(sizeof(struct parser_state));
nft_ctx_add_include_path(ctx, DEFAULT_INCLUDE_PATH);
ctx->parser_max_errors = 10;
init_list_head(&ctx->cache.list);
ctx->top_scope = scope_alloc();
ctx->flags = flags;
ctx->output.output_fp = stdout;
ctx->output.error_fp = stderr;
if (flags == NFT_CTX_DEFAULT)
nft_ctx_netlink_init(ctx);
return ctx;
}
static ssize_t cookie_write(void *cptr, const char *buf, size_t buflen)
{
struct cookie *cookie = cptr;
if (!cookie->buflen) {
cookie->buflen = buflen + 1;
cookie->buf = xmalloc(cookie->buflen);
} else if (cookie->pos + buflen >= cookie->buflen) {
size_t newlen = cookie->buflen * 2;
while (newlen <= cookie->pos + buflen)
newlen *= 2;
cookie->buf = xrealloc(cookie->buf, newlen);
cookie->buflen = newlen;
}
memcpy(cookie->buf + cookie->pos, buf, buflen);
cookie->pos += buflen;
cookie->buf[cookie->pos] = '\0';
return buflen;
}
static int init_cookie(struct cookie *cookie)
{
cookie_io_functions_t cookie_fops = {
.write = cookie_write,
};
if (cookie->orig_fp) { /* just rewind buffer */
if (cookie->buflen) {
cookie->pos = 0;
cookie->buf[0] = '\0';
}
return 0;
}
cookie->orig_fp = cookie->fp;
cookie->fp = fopencookie(cookie, "w", cookie_fops);
if (!cookie->fp) {
cookie->fp = cookie->orig_fp;
cookie->orig_fp = NULL;
return 1;
}
return 0;
}
static int exit_cookie(struct cookie *cookie)
{
if (!cookie->orig_fp)
return 1;
fclose(cookie->fp);
cookie->fp = cookie->orig_fp;
cookie->orig_fp = NULL;
free(cookie->buf);
cookie->buf = NULL;
cookie->buflen = 0;
cookie->pos = 0;
return 0;
}
EXPORT_SYMBOL(nft_ctx_buffer_output);
int nft_ctx_buffer_output(struct nft_ctx *ctx)
{
return init_cookie(&ctx->output.output_cookie);
}
EXPORT_SYMBOL(nft_ctx_unbuffer_output);
int nft_ctx_unbuffer_output(struct nft_ctx *ctx)
{
return exit_cookie(&ctx->output.output_cookie);
}
EXPORT_SYMBOL(nft_ctx_buffer_error);
int nft_ctx_buffer_error(struct nft_ctx *ctx)
{
return init_cookie(&ctx->output.error_cookie);
}
EXPORT_SYMBOL(nft_ctx_unbuffer_error);
int nft_ctx_unbuffer_error(struct nft_ctx *ctx)
{
return exit_cookie(&ctx->output.error_cookie);
}
static const char *get_cookie_buffer(struct cookie *cookie)
{
fflush(cookie->fp);
/* This is a bit tricky: Rewind the buffer for future use and return
* the old content at the same time. Therefore return an empty string
* if buffer position is zero, otherwise just rewind buffer position
* and return the unmodified buffer. */
if (!cookie->pos)
return "";
cookie->pos = 0;
return cookie->buf;
}
EXPORT_SYMBOL(nft_ctx_get_output_buffer);
const char *nft_ctx_get_output_buffer(struct nft_ctx *ctx)
{
return get_cookie_buffer(&ctx->output.output_cookie);
}
EXPORT_SYMBOL(nft_ctx_get_error_buffer);
const char *nft_ctx_get_error_buffer(struct nft_ctx *ctx)
{
return get_cookie_buffer(&ctx->output.error_cookie);
}
EXPORT_SYMBOL(nft_ctx_free);
void nft_ctx_free(struct nft_ctx *ctx)
{
if (ctx->nf_sock)
mnl_socket_close(ctx->nf_sock);
exit_cookie(&ctx->output.output_cookie);
exit_cookie(&ctx->output.error_cookie);
iface_cache_release();
cache_release(&ctx->cache);
nft_ctx_clear_include_paths(ctx);
scope_free(ctx->top_scope);
xfree(ctx->state);
nft_exit(ctx);
xfree(ctx);
}
EXPORT_SYMBOL(nft_ctx_set_output);
FILE *nft_ctx_set_output(struct nft_ctx *ctx, FILE *fp)
{
FILE *old = ctx->output.output_fp;
if (!fp || ferror(fp))
return NULL;
ctx->output.output_fp = fp;
return old;
}
EXPORT_SYMBOL(nft_ctx_set_error);
FILE *nft_ctx_set_error(struct nft_ctx *ctx, FILE *fp)
{
FILE *old = ctx->output.error_fp;
if (!fp || ferror(fp))
return NULL;
ctx->output.error_fp = fp;
return old;
}
EXPORT_SYMBOL(nft_ctx_get_dry_run);
bool nft_ctx_get_dry_run(struct nft_ctx *ctx)
{
return ctx->check;
}
EXPORT_SYMBOL(nft_ctx_set_dry_run);
void nft_ctx_set_dry_run(struct nft_ctx *ctx, bool dry)
{
ctx->check = dry;
}
EXPORT_SYMBOL(nft_ctx_output_get_flags);
unsigned int nft_ctx_output_get_flags(struct nft_ctx *ctx)
{
return ctx->output.flags;
}
EXPORT_SYMBOL(nft_ctx_output_set_flags);
void nft_ctx_output_set_flags(struct nft_ctx *ctx, unsigned int flags)
{
ctx->output.flags = flags;
}
EXPORT_SYMBOL(nft_ctx_output_get_debug);
unsigned int nft_ctx_output_get_debug(struct nft_ctx *ctx)
{
return ctx->debug_mask;
}
EXPORT_SYMBOL(nft_ctx_output_set_debug);
void nft_ctx_output_set_debug(struct nft_ctx *ctx, unsigned int mask)
{
ctx->debug_mask = mask;
}
static const struct input_descriptor indesc_cmdline = {
.type = INDESC_BUFFER,
.name = "<cmdline>",
};
static int nft_parse_bison_buffer(struct nft_ctx *nft, const char *buf,
struct list_head *msgs, struct list_head *cmds)
{
int ret;
parser_init(nft, nft->state, msgs, cmds, nft->top_scope);
nft->scanner = scanner_init(nft->state);
scanner_push_buffer(nft->scanner, &indesc_cmdline, buf);
ret = nft_parse(nft, nft->scanner, nft->state);
if (ret != 0 || nft->state->nerrs > 0)
return -1;
return 0;
}
static int nft_parse_bison_filename(struct nft_ctx *nft, const char *filename,
struct list_head *msgs, struct list_head *cmds)
{
int ret;
parser_init(nft, nft->state, msgs, cmds, nft->top_scope);
nft->scanner = scanner_init(nft->state);
if (scanner_read_file(nft, filename, &internal_location) < 0)
return -1;
ret = nft_parse(nft, nft->scanner, nft->state);
if (ret != 0 || nft->state->nerrs > 0)
return -1;
return 0;
}
static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs,
struct list_head *cmds)
{
unsigned int flags;
struct cmd *cmd;
flags = cache_evaluate(nft, cmds);
if (cache_update(nft, flags, msgs) < 0)
return -1;
list_for_each_entry(cmd, cmds, list) {
struct eval_ctx ectx = {
.nft = nft,
.msgs = msgs,
};
if (cmd_evaluate(&ectx, cmd) < 0 &&
++nft->state->nerrs == nft->parser_max_errors)
return -1;
}
if (nft->state->nerrs)
return -1;
list_for_each_entry(cmd, cmds, list)
nft_cmd_expand(cmd);
return 0;
}
EXPORT_SYMBOL(nft_run_cmd_from_buffer);
int nft_run_cmd_from_buffer(struct nft_ctx *nft, const char *buf)
{
int rc = -EINVAL, parser_rc;
struct cmd *cmd, *next;
LIST_HEAD(msgs);
LIST_HEAD(cmds);
char *nlbuf;
nlbuf = xzalloc(strlen(buf) + 2);
sprintf(nlbuf, "%s\n", buf);
if (nft_output_json(&nft->output))
rc = nft_parse_json_buffer(nft, nlbuf, &msgs, &cmds);
if (rc == -EINVAL)
rc = nft_parse_bison_buffer(nft, nlbuf, &msgs, &cmds);
parser_rc = rc;
rc = nft_evaluate(nft, &msgs, &cmds);
if (rc < 0)
goto err;
if (parser_rc) {
rc = parser_rc;
goto err;
}
if (nft_netlink(nft, &cmds, &msgs, nft->nf_sock) != 0)
rc = -1;
err:
erec_print_list(&nft->output, &msgs, nft->debug_mask);
list_for_each_entry_safe(cmd, next, &cmds, list) {
list_del(&cmd->list);
cmd_free(cmd);
}
iface_cache_release();
if (nft->scanner) {
scanner_destroy(nft);
nft->scanner = NULL;
}
free(nlbuf);
if (!rc &&
nft_output_json(&nft->output) &&
nft_output_echo(&nft->output))
json_print_echo(nft);
if (rc)
cache_release(&nft->cache);
return rc;
}
EXPORT_SYMBOL(nft_run_cmd_from_filename);
int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename)
{
struct cmd *cmd, *next;
int rc, parser_rc;
LIST_HEAD(msgs);
LIST_HEAD(cmds);
if (!strcmp(filename, "-"))
filename = "/dev/stdin";
rc = -EINVAL;
if (nft_output_json(&nft->output))
rc = nft_parse_json_filename(nft, filename, &msgs, &cmds);
if (rc == -EINVAL)
rc = nft_parse_bison_filename(nft, filename, &msgs, &cmds);
parser_rc = rc;
rc = nft_evaluate(nft, &msgs, &cmds);
if (rc < 0)
goto err;
if (parser_rc) {
rc = parser_rc;
goto err;
}
if (nft_netlink(nft, &cmds, &msgs, nft->nf_sock) != 0)
rc = -1;
err:
erec_print_list(&nft->output, &msgs, nft->debug_mask);
list_for_each_entry_safe(cmd, next, &cmds, list) {
list_del(&cmd->list);
cmd_free(cmd);
}
iface_cache_release();
if (nft->scanner) {
scanner_destroy(nft);
nft->scanner = NULL;
}
if (!rc &&
nft_output_json(&nft->output) &&
nft_output_echo(&nft->output))
json_print_echo(nft);
if (rc)
cache_release(&nft->cache);
return rc;
}