+static int prepare_getopt_args(const char *str, struct parse_context *ctx)
+{
+ char **tokens;
+ int i;
+
+ tokens = g_strsplit_set(str, " ", -1);
+
+ i = g_strv_length(tokens);
+
+ /* Add space for the argv[0] value */
+ ctx->argc = i + 1;
+
+ /* Don't forget the last NULL entry */
+ ctx->argv = g_try_malloc0((ctx->argc + 1) * sizeof(char *));
+ if (!ctx->argv) {
+ g_strfreev(tokens);
+ return -ENOMEM;
+ }
+
+ /*
+ * getopt_long() jumps over the first token; we need to add some
+ * random argv[0] entry.
+ */
+ ctx->argv[0] = g_strdup("argh");
+ for (i = 1; i < ctx->argc; i++)
+ ctx->argv[i] = tokens[i - 1];
+
+ g_free(tokens);
+
+ return 0;
+}
+
+static int parse_xt_modules(int c, bool invert,
+ struct parse_context *ctx)
+{
+ struct xtables_match *m;
+ struct xtables_rule_match *rm;
+ struct ipt_entry fw;
+ struct ip6t_entry fw6;
+ int err;
+
+ switch (ctx->type) {
+ case AF_INET:
+ memset(&fw, 0, sizeof(fw));
+
+ /* The SNAT parser wants to know the protocol. */
+ if (ctx->proto == 0)
+ ctx->proto = IPPROTO_IP;
+
+ fw.ip.proto = ctx->proto;
+ break;
+ case AF_INET6:
+ memset(&fw6, 0, sizeof(fw6));
+
+ if (ctx->proto == 0)
+ ctx->proto = IPPROTO_IPV6;
+
+ fw6.ipv6.proto = ctx->proto;
+
+ /* Flags must be set for IPv6 if protocol is set. */
+ fw6.ipv6.flags |= IP6T_F_PROTO;
+
+ break;
+ default:
+ return 0;
+ }
+
+ for (rm = ctx->xt_rm; rm; rm = rm->next) {
+ if (rm->completed != 0)
+ continue;
+
+ m = rm->match;
+
+ if (!m->x6_parse && !m->parse)
+ continue;
+
+ if (c < (int) m->option_offset ||
+ c >= (int) m->option_offset
+ + XT_OPTION_OFFSET_SCALE)
+ continue;
+
+ enable_jmp();
+
+ if ((err = setjmp(env_state)) != 0) {
+ DBG("setjmp() called by longjmp() with value %d", err);
+ disable_jmp();
+ return -EINVAL;
+ }
+
+ switch (ctx->type) {
+ case AF_INET:
+ xtables_option_mpcall(c, ctx->argv, invert, m, &fw);
+ break;
+ case AF_INET6:
+ xtables_option_mpcall(c, ctx->argv, invert, m, &fw6);
+ break;
+ }
+
+ disable_jmp();
+ }
+
+ if (!ctx->xt_t)
+ return 0;
+
+ if (!ctx->xt_t->x6_parse && !ctx->xt_t->parse)
+ return 0;
+
+ if (c < (int) ctx->xt_t->option_offset ||
+ c >= (int) ctx->xt_t->option_offset
+ + XT_OPTION_OFFSET_SCALE)
+ return 0;
+
+ enable_jmp();
+
+ if ((err = setjmp(env_state)) != 0) {
+ DBG("setjmp() called by longjmp() with value %d", err);
+ disable_jmp();
+ return -EINVAL;
+ }
+
+ switch (ctx->type) {
+ case AF_INET:
+ xtables_option_tpcall(c, ctx->argv, invert, ctx->xt_t, &fw);
+ break;
+ case AF_INET6:
+ xtables_option_tpcall(c, ctx->argv, invert, ctx->xt_t, &fw6);
+ break;
+ }
+
+ disable_jmp();
+
+ return 0;
+}
+
+static int final_check_xt_modules(struct parse_context *ctx)
+{
+ struct xtables_rule_match *rm;
+ int err;
+
+ for (rm = ctx->xt_rm; rm; rm = rm->next) {
+ enable_jmp();
+
+ if ((err = setjmp(env_state)) != 0) {
+ DBG("setjmp() called by longjmp() with value %d", err);
+ disable_jmp();
+ return -EINVAL;
+ }
+
+ xtables_option_mfcall(rm->match);
+
+ disable_jmp();
+ }
+
+ enable_jmp();
+
+ if ((err = setjmp(env_state)) != 0) {
+ DBG("setjmp() called by longjmp() with value %d", err);
+ disable_jmp();
+ return -EINVAL;
+ }
+
+ if (ctx->xt_t)
+ xtables_option_tfcall(ctx->xt_t);
+
+ disable_jmp();
+
+ return 0;
+}
+
+static int parse_rule_spec(struct connman_iptables *table,
+ struct parse_context *ctx)
+{
+ /*
+ * How the parser works:
+ *
+ * - If getopt finds 's', 'd', 'i', 'o'.
+ * just extract the information.
+ * - if '!' is found, set the invert flag to true and
+ * removes the '!' from the optarg string and jumps
+ * back to getopt to reparse the current optarg string.
+ * After reparsing the invert flag is reset to false.
+ * - If 'm' or 'j' is found then call either
+ * prepare_matches() or prepare_target(). Those function
+ * will modify (extend) the longopts for getopt_long.
+ * That means getopt will change its matching context according
+ * the loaded target.
+ *
+ * Here an example with iptables-test
+ *
+ * argv[0] = ./tools/iptables-test
+ * argv[1] = -t
+ * argv[2] = filter
+ * argv[3] = -A
+ * argv[4] = INPUT
+ * argv[5] = -m
+ * argv[6] = mark
+ * argv[7] = --mark
+ * argv[8] = 999
+ * argv[9] = -j
+ * argv[10] = LOG
+ *
+ * getopt found 'm' then the optarg is "mark" and optind 7
+ * The longopts array containts before hitting the `case 'm'`
+ *
+ * val A has_arg 1 name append
+ * val C has_arg 1 name compare
+ * val D has_arg 1 name delete
+ * val F has_arg 1 name flush-chain
+ * val I has_arg 1 name insert
+ * val L has_arg 2 name list
+ * val N has_arg 1 name new-chain
+ * val P has_arg 1 name policy
+ * val X has_arg 1 name delete-chain
+ * val d has_arg 1 name destination
+ * val i has_arg 1 name in-interface
+ * val j has_arg 1 name jump
+ * val m has_arg 1 name match
+ * val o has_arg 1 name out-interface
+ * val s has_arg 1 name source
+ * val t has_arg 1 name table
+ *
+ * After executing the `case 'm'` block longopts is
+ *
+ * val A has_arg 1 name append
+ * val C has_arg 1 name compare
+ * val D has_arg 1 name delete
+ * val F has_arg 1 name flush-chain
+ * val I has_arg 1 name insert
+ * val L has_arg 2 name list
+ * val N has_arg 1 name new-chain
+ * val P has_arg 1 name policy
+ * val X has_arg 1 name delete-chain
+ * val d has_arg 1 name destination
+ * val i has_arg 1 name in-interface
+ * val j has_arg 1 name jump
+ * val m has_arg 1 name match
+ * val o has_arg 1 name out-interface
+ * val s has_arg 1 name source
+ * val t has_arg 1 name table
+ * val has_arg 1 name mark
+ *
+ * So the 'mark' matcher has added the 'mark' options
+ * and getopt will then return c '256' optarg "999" optind 9
+ * And we will hit the 'default' statement which then
+ * will call the matchers parser (xt_m->parser() or
+ * xtables_option_mpcall() depending on which version
+ * of libxtables is found.
+ */
+ struct xtables_match *xt_m;
+ bool invert = false;
+ int len, c, err;
+
+ if (ctx->type != table->type) {
+ DBG("ctx->type %d does not match table->type %d", ctx->type,
+ table->type);
+ return -EINVAL;
+ }
+
+ switch (ctx->type) {
+ case AF_INET:
+ ctx->ip = g_try_new0(struct ipt_ip, 1);
+ if (!ctx->ip)
+ return -ENOMEM;
+
+ break;
+ case AF_INET6:
+ ctx->ipv6 = g_try_new0(struct ip6t_ip6, 1);
+ if (!ctx->ipv6)
+ return -ENOMEM;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Tell getopt_long not to generate error messages for unknown
+ * options and also reset optind back to 0.
+ */
+ opterr = 0;
+ optind = 0;
+
+ while ((c = getopt_long(ctx->argc, ctx->argv,
+ "-:d:i:o:s:m:j:p:",
+ ctx->type == AF_INET ?
+ iptables_globals.opts :
+ ip6tables_globals.opts,
+ NULL)) != -1) {
+ switch (c) {
+ case 's':
+ if (ctx->type == AF_INET) {
+ /* Source specification */
+ if (!parse_ip_and_mask(optarg,
+ &ctx->ip->src,
+ &ctx->ip->smsk))
+ break;
+
+ if (invert)
+ ctx->ip->invflags |= IPT_INV_SRCIP;
+ }
+
+ if (ctx->type == AF_INET6) {
+ if (!parse_ipv6_and_mask(optarg,
+ &ctx->ipv6->src,
+ &ctx->ipv6->smsk))
+ break;
+
+ if (invert)
+ ctx->ipv6->invflags |= IP6T_INV_SRCIP;
+ }
+
+ break;
+ case 'd':
+ if (ctx->type == AF_INET) {
+ /* Destination specification */
+ if (!parse_ip_and_mask(optarg,
+ &ctx->ip->dst,
+ &ctx->ip->dmsk))
+ break;
+
+ if (invert)
+ ctx->ip->invflags |= IPT_INV_DSTIP;
+ }
+
+ if (ctx->type == AF_INET6) {
+ /* Destination specification */
+ if (!parse_ipv6_and_mask(optarg,
+ &ctx->ipv6->dst,
+ &ctx->ipv6->dmsk))
+ break;
+
+ if (invert)
+ ctx->ip->invflags |= IP6T_INV_DSTIP;
+ }
+
+ break;
+ case 'i':
+ /* In interface specification */
+ len = strlen(optarg);
+
+ if (len + 1 > IFNAMSIZ)
+ break;
+
+ if (ctx->type == AF_INET) {
+ g_stpcpy(ctx->ip->iniface, optarg);
+ memset(ctx->ip->iniface_mask, 0xff, len + 1);
+
+ if (invert)
+ ctx->ip->invflags |= IPT_INV_VIA_IN;
+ }
+
+ if (ctx->type == AF_INET6) {
+ g_stpcpy(ctx->ipv6->iniface, optarg);
+ memset(ctx->ipv6->iniface_mask, 0xff, len + 1);
+
+ if (invert)
+ ctx->ipv6->invflags |= IP6T_INV_VIA_IN;
+ }
+
+ break;
+ case 'o':
+ /* Out interface specification */
+ len = strlen(optarg);
+
+ if (len + 1 > IFNAMSIZ)
+ break;
+
+ if (ctx->type == AF_INET) {
+ g_stpcpy(ctx->ip->outiface, optarg);
+ memset(ctx->ip->outiface_mask, 0xff, len + 1);
+
+ if (invert)
+ ctx->ip->invflags |= IPT_INV_VIA_OUT;
+ }
+
+ if (ctx->type == AF_INET6) {
+ g_stpcpy(ctx->ipv6->outiface, optarg);
+ memset(ctx->ipv6->outiface_mask, 0xff, len + 1);
+
+ if (invert)
+ ctx->ipv6->invflags |= IP6T_INV_VIA_OUT;
+ }
+
+ break;
+ case 'm':
+ /* Matches */
+ xt_m = prepare_matches(table, &ctx->xt_rm, optarg);
+ if (!xt_m) {
+ err = -EINVAL;
+ goto out;
+ }
+ ctx->xt_m = g_list_append(ctx->xt_m, xt_m);
+
+ break;
+ case 'p':
+ enable_jmp();
+
+ if ((err = setjmp(env_state)) != 0) {
+ DBG("setjmp() called by longjmp() with value "
+ "%d", err);
+ disable_jmp();
+
+ /* Errors from parse_rule_spec are negative */
+ err = -EINVAL;
+ goto out;
+ }
+
+ ctx->proto = xtables_parse_protocol(optarg);
+
+ disable_jmp();
+
+ /*
+ * If protocol was set add it to ipt_ip.
+ * xtables_parse_protocol() returns 0 or
+ * UINT16_MAX (-1) on error
+ */
+ if (ctx->proto > 0 && ctx->proto < UINT16_MAX) {
+ if (ctx->type == AF_INET)
+ ctx->ip->proto = ctx->proto;
+
+ if (ctx->type == AF_INET6) {
+ ctx->ipv6->proto = ctx->proto;
+
+ /*
+ * Flags must be set for IPv6 if
+ * protocol is set.
+ */
+ ctx->ipv6->flags |= IP6T_F_PROTO;
+ }
+ }
+ break;
+ case 'j':
+ /* Target */
+ ctx->xt_t = prepare_target(table, optarg);
+ if (!ctx->xt_t) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ break;
+ case 1:
+ if (optarg[0] == '!' && optarg[1] == '\0') {
+ invert = true;
+
+ /* Remove the '!' from the optarg */
+ optarg[0] = '\0';
+
+ /*
+ * And recall getopt_long without resetting
+ * invert.
+ */
+ continue;
+ }
+
+ break;
+ default:
+ err = parse_xt_modules(c, invert, ctx);
+ if (err == 1)
+ continue;
+ else if (err == -EINVAL)
+ goto out;
+
+ break;
+ }
+
+ invert = false;
+ }