#ifdef HAVE_CONFIG_H #include "config.h" #else #define POTD_LOGFILE "/tmp/potd.log" #endif #include #include #include #include #include #include #include #include #include #include "options.h" typedef enum opt_type { OT_INVALID = 0, OT_NOARG, OT_L, OT_LL, OT_STR, OT_PATH } opt_type; struct opt_list; typedef union opt_ptr { long int l; long long int ll; const char *str; char *str_dup; struct opt_list *list; } opt_ptr; typedef struct opt_list { opt_ptr value; struct opt_list *next; } opt_list; struct opt { opt_type type; opt_ptr value; opt_ptr def_value; int used; int is_list; const char *arg_name; const char *short_help; const char *help; }; #define OPT(type, def_value, arg, short_help, help) \ { type, {0}, {def_value}, 0, 0, arg, short_help, help } #define OPT_LIST(type, def_values, arg, short_help, help) \ { type, {0}, {def_values}, 0, 1, arg, short_help, help } #define OPT_NOARG(arg, short_help, help) \ OPT(OT_NOARG, .ll = 0, arg, short_help, help) static struct opt options[OPT_MAX+1] = { OPT_NOARG("log-to-file", "log to the default logfile path\n", NULL), OPT(OT_PATH, .str = POTD_LOGFILE, "log-file", "specify a logfile path\n", NULL), OPT(OT_STR, .str = "notice", "log-level", "set the loglevel\n", "error - log only errors\n" "warning - log errors,warnings\n" "notice - log errors,warnings,notices\n" "debug - log all messages\n"), OPT_NOARG("daemon", "fork into background if possible\n", NULL), OPT_LIST(OT_STR, .str = NULL, "redirect", "setup redirector service\n", "format [listen]:[forward-to-protocol]\n" "where [listen] contains [listen-addr]:[listen-port]\n" "and [forward-to-protocol] contains [forward-addr]:[forward-port]\n" "Example: 0.0.0.0:2222:127.0.0.1:22222\n"), OPT_LIST(OT_STR, .str = NULL, "protocol", "setup (ssh) protocol service\n", "format [listen]:[forward-to-jail]\n" "where [listen] contains [listen-addr]:[listen-port]\n" "and [forward-to-jail] contains [forward-addr]:[forward-port]\n" "Example: 127.0.0.1:22222:127.0.0.1:33333\n"), OPT_LIST(OT_STR, .str = NULL, "jail", "setup jail service\n", "format [listen]\n" "where [listen] contains [listen-addr]:[listen-port]\n" "Example: 127.0.0.1:33333\n"), OPT(OT_PATH, .str = POTD_DEFROOT, "rootfs", "path to root directory/image\n", NULL), OPT(OT_PATH, .str = POTD_NETNS_RUN_DIR, "netns-rundir", "set the network namespace run directory\n", NULL), OPT_NOARG("seccomp-minimal", "use a minimal seccomp ruleset\n", "instead of setting an allowed syscall ruleset\n" "use a minimal set of blocked syscalls e.g.\n" "mount, umount, ptrace, kernel module syscalls\n" "and some io syscalls\n" "(use this if you acknowledge errors on some platforms)\n"), OPT_NOARG("help", "this\n", NULL), OPT(OT_INVALID, .ll = 0, NULL, NULL, NULL) }; static int opt_convert(opt_type t, opt_ptr *d); static int setopt_list(struct opt *o, const char *optarg); static int setopt(struct opt *o, const char *optarg); static size_t snprint_multilined_ljust(const char *prefix, const char *multiline, char *buf, size_t siz); static void usage(const char *arg0, int print_copyright); static int parse_path(opt_ptr *d, char *some_path) { int rc = 1; char path[PATH_MAX]; char *dir, *base; d->str_dup = realpath(some_path, NULL); if (!d->str_dup && errno == ENOENT) { snprintf(path, sizeof path, "%s", some_path); dir = dirname(path); if (!dir) return 1; dir = realpath(dir, NULL); if (!dir) return 1; snprintf(path, sizeof path, "%s", some_path); base = basename(path); if (!base) goto error; snprintf(path, sizeof path, "%s/%s", dir, base); d->str_dup = strndup(path, strnlen(path, sizeof path)); error: free(dir); } if (d->str_dup) rc = 0; return rc; } static int opt_convert(opt_type t, opt_ptr *d) { char *endptr = NULL; switch (t) { case OT_L: d->l = strtol(optarg, &endptr, 10); break; case OT_LL: d->ll = strtoll(optarg, &endptr, 10); break; case OT_STR: d->str_dup = strdup(optarg); break; case OT_PATH: if (parse_path(d, optarg)) return 1; break; case OT_NOARG: case OT_INVALID: return 1; } if (endptr && *endptr != 0) return 1; return 0; } static int setopt_list(struct opt *o, const char *optarg) { opt_list **l; assert(o && o->type != OT_INVALID); if (!optarg || o->type == OT_NOARG || !o->is_list) return 1; l = &o->value.list; while (*l) l = &(*l)->next; *l = (opt_list *) calloc(1, sizeof **l); if (opt_convert(o->type, &(*l)->value)) return 1; o->used = 1; return 0; } static int setopt(struct opt *o, const char *optarg) { assert(o && o->type != OT_INVALID); if (o->used && !o->is_list) return 1; if (!optarg || o->type == OT_NOARG) goto noarg; if (opt_convert(o->type, &o->value)) return 1; noarg: o->used = 1; return 0; } static size_t snprint_multilined_ljust(const char *prefix, const char *multiline, char *buf, size_t siz) { const char sep[] = "\n"; const char *start, *end; size_t off; off = 0; start = multiline; end = NULL; do { if (start) { end = strstr(start, sep); if (end) { off += snprintf(buf + off, siz - off, "%s%.*s\n", prefix, (int) (end-start), start); start = end + strlen(sep); } } } while (start && end); return off; } static void usage(const char *arg0, int print_copyright) { int i, has_default; size_t off; char spaces[6]; char spaces_long[28]; char buf_arg[64]; char buf_shorthelp[BUFSIZ]; char buf_help[BUFSIZ]; char value[32]; #ifdef HAVE_CONFIG_H if (print_copyright) fprintf(stderr, "\n%s (C) 2018 Toni Uhlig <%s>\n\n", PACKAGE_STRING, PACKAGE_BUGREPORT); else #endif if (1) fprintf(stderr, "%s", "\n"); memset(spaces, ' ', sizeof spaces); spaces[sizeof spaces - 1] = 0; memset(spaces_long, ' ', sizeof spaces_long); spaces_long[sizeof spaces_long - 1] = 0; for (i = 0; i < OPT_MAX; ++i) { snprintf(buf_arg, sizeof buf_arg, "--%s", options[i].arg_name); memset(buf_shorthelp, 0, sizeof buf_shorthelp); if (options[i].short_help) snprint_multilined_ljust(spaces, options[i].short_help, buf_shorthelp, sizeof buf_shorthelp); memset(buf_help, 0, sizeof buf_help); off = 0; if (options[i].help) off = snprint_multilined_ljust(spaces_long, options[i].help, buf_help, sizeof buf_help); has_default = 0; switch (options[i].type) { case OT_L: snprintf(value, sizeof value, "default: %lld\n", options[i].def_value.ll); has_default = 1; break; case OT_LL: snprintf(value, sizeof value, "default: %ld\n", options[i].def_value.l); has_default = 1; break; case OT_STR: case OT_PATH: if (options[i].def_value.str) { snprintf(value, sizeof value, "default: %s\n", options[i].def_value.str); has_default = 1; } break; case OT_INVALID: case OT_NOARG: default: break; } if (has_default) snprint_multilined_ljust(&spaces_long[sizeof spaces_long / 2], value, buf_help + off, sizeof buf_help - off); fprintf(stderr, "%16s %s" "%s\n", buf_arg, buf_shorthelp, buf_help); } fprintf(stderr, "For example: %s \\\n" " --redirect 0.0.0.0:2222:127.0.0.1:22222\n" " --protocol 127.0.0.1:22222:127.0.0.1:33333\n" " --jail 127.0.0.1:33333\n" " will process/filter all incoming traffic\n" " at 0.0.0.0:2222 and redirect it\n" " to 127.0.0.1:22222 (libssh) which will redirect it finally\n" " to 127.0.0.1:33333 (jail service).\n\n", arg0); } int parse_cmdline(int argc, char **argv) { int rc, i, option, option_index; struct option *o = (struct option *) calloc(OPT_MAX+1, sizeof *o); assert(o); for (i = 0; i < OPT_MAX; ++i) { o[i].name = options[i].arg_name; if (options[i].def_value.ll) o[i].has_arg = required_argument; else o[i].has_arg = (options[i].type == OT_NOARG ? no_argument : required_argument); } rc = 0; while (1) { option_index = -1; option = getopt_long_only(argc, argv, "", o, &option_index); if (option_index == -1 && option != -1) { rc = 1; continue; } if (option == -1) break; if (!option) { if (!setopt_list(&options[option_index], optarg)) { } else if (setopt(&options[option_index], optarg)) { rc = 1; goto error; } } else { fprintf(stderr, "%s: unknown option '%c' [0x%X]\n", argv[0], option, option); } } error: free(o); if (rc) usage(argv[0], 0); else if (getopt_used(OPT_HELP)) { usage(argv[0], 1); exit(EXIT_SUCCESS); } return rc; } int getopt_used(opt_name on) { return options[on].used; } char * getopt_str(opt_name on) { char *str; assert(options[on].type == OT_STR || options[on].type == OT_PATH); assert(getopt_used(on) || options[on].def_value.str); str = options[on].value.str_dup; if (!str) str = options[on].def_value.str_dup; assert(str); return str; } char * getopt_strlist(opt_name on, opt_list **ol) { opt_list *o; assert(options[on].is_list && ol); assert(options[on].type == OT_STR || options[on].type == OT_PATH); assert(getopt_used(on) && !options[on].def_value.str); if (*ol) { o = (*ol)->next; } else { o = options[on].value.list; } *ol = o; return (o ? o->value.str_dup : NULL); }