/* * Copyright (c) 2021-2022 joshua stein * * 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 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 #include #include "ansi.h" #include "subtext.h" #include "db.h" #include "focusable.h" #include "main_menu.h" #include "settings.h" #include "tcp.h" /* for long2ip/ip2long */ #include "util.h" short struct_editor(struct session *s, const struct struct_field *fields, const size_t nfields, const struct session_menu_option *extra_opts, size_t nextra_opts, void *data, size_t dsize, void **result, char *title, char *prompt) { static const struct session_menu_option opts[] = { { '#', "", "Edit setting [#]" }, { 's', "Ss", "Save and return to main menu" }, { 'q', "QqXx", "Discard changes and return to main menu" }, { '?', "?", "List menu options" }, }; struct session_menu_option *dopts = NULL; const struct struct_field *sf; long lval; char initial[20]; char *input = NULL, *new_data; size_t nopts; short n, i, sval, on, ret; bool any_changes = false, done, show_list; unsigned short c; new_data = xmalloc(dsize); if (new_data == NULL) goto done; memcpy(new_data, data, dsize); nopts = nitems(opts) + nextra_opts; dopts = xcalloc(sizeof(struct session_menu_option), nopts); if (dopts == NULL) goto done; if (extra_opts != NULL) memcpy(dopts, extra_opts, sizeof(struct session_menu_option) * nextra_opts); memcpy(dopts + nextra_opts, opts, sizeof(opts)); session_printf(s, "{{B}}Editor: %s{{/B}}\r\n", title); session_flush(s); any_changes = false; show_list = true; done = false; ret = -1; while (!done && !s->ending) { if (show_list) { for (n = 0; n < nfields; n++) { if ((n + 1) % (s->terminal_lines - 2) == 0) { session_printf(s, "-- More --"); session_flush(s); sval = session_input_char(s); if (s->vt100) session_printf(s, "\r%s", ansi(s, ANSI_ERASE_LINE, ANSI_END)); else session_output(s, "\r", 1); if (sval == 'q' || sval == 'Q') { /* abort pagination, like less */ show_list = false; break; } } sf = &fields[n]; session_printf(s, "{{B}}%d{{/B}}: %s [", n + 1, sf->name); switch (sf->type) { case CONFIG_TYPE_STRING: session_printf(s, "%s", new_data + sf->off); break; case CONFIG_TYPE_PASSWORD: i = strlen(new_data + sf->off); while (i) { session_output(s, "*", 1); i--; } break; case CONFIG_TYPE_SHORT: sval = CHARS_TO_SHORT(new_data[sf->off], new_data[sf->off + 1]); session_printf(s, "%d", sval); break; case CONFIG_TYPE_LONG: lval = CHARS_TO_LONG(new_data[sf->off], new_data[sf->off + 1], new_data[sf->off + 2], new_data[sf->off + 3]); session_printf(s, "%ld", lval); break; case CONFIG_TYPE_BOOLEAN: session_printf(s, "%s", new_data[sf->off] == 0 ? "false" : "true"); break; case CONFIG_TYPE_IP: { char ip_str[16]; lval = CHARS_TO_LONG(new_data[sf->off], new_data[sf->off + 1], new_data[sf->off + 2], new_data[sf->off + 3]); if (lval == 0) session_printf(s, "0.0.0.0"); else { long2ip(lval, ip_str); session_printf(s, "%s", ip_str); } break; } } session_printf(s, "]\r\n"); } } c = session_menu(s, NULL, prompt, NULL, dopts, nopts, show_list, "Option #", &on); show_list = false; switch (c) { case 's': goto done; case 'q': any_changes = false; goto done; case '?': show_list = true; break; case '#': if (on < 1 || on > nfields) { session_printf(s, "Invalid option\r\n"); session_flush(s); break; } sf = &fields[on - 1]; get_input: session_printf(s, "{{B}}%s:{{/B}} ", sf->name); session_flush(s); switch (sf->type) { case CONFIG_TYPE_STRING: case CONFIG_TYPE_PASSWORD: input = session_field_input(s, sf->max, 50, new_data + sf->off, false, (sf->type == CONFIG_TYPE_PASSWORD ? '*' : 0)); session_output(s, "\r\n", 2); session_flush(s); if (input == NULL || s->ending) break; if (strlen(input) >= sf->max) { session_printf(s, "%s is too long (%d max, ^C to cancel)", sf->name, sf->max - 1); xfree(&input); goto get_input; } strlcpy(new_data + sf->off, input, sf->max); any_changes = true; break; case CONFIG_TYPE_SHORT: case CONFIG_TYPE_LONG: if (sf->type == CONFIG_TYPE_LONG) { lval = CHARS_TO_LONG(new_data[sf->off], new_data[sf->off + 1], new_data[sf->off + 2], new_data[sf->off + 3]); snprintf(initial, sizeof(initial), "%ld", lval); } else { sval = CHARS_TO_SHORT(new_data[sf->off], new_data[sf->off + 1]); snprintf(initial, sizeof(initial), "%d", sval); } input = session_field_input(s, 10, 10, initial, false, 0); session_output(s, "\r\n", 2); session_flush(s); if (input == NULL || s->ending) break; lval = atol(input); if (lval < sf->min) { session_printf(s, "%s must be %ld or higher (^C to cancel)\r\n", sf->name, sf->min); xfree(&input); goto get_input; } if (lval > sf->max) { session_printf(s, "%s must be %ld or less (^C to cancel)\r\n", sf->name, sf->max); xfree(&input); goto get_input; } if (sf->type == CONFIG_TYPE_LONG) { new_data[sf->off] = (lval >> 24) & 0xff; new_data[sf->off + 1] = (lval >> 16) & 0xff; new_data[sf->off + 2] = (lval >> 8) & 0xff; new_data[sf->off + 3] = lval & 0xff; } else { sval = lval; new_data[sf->off] = (sval >> 8) & 0xff; new_data[sf->off + 1] = sval & 0xff; } any_changes = true; break; case CONFIG_TYPE_BOOLEAN: if (new_data[sf->off]) session_printf(s, "[Y/n] "); else session_printf(s, "[y/N] "); session_flush(s); for (;;) { c = session_input_char(s); if (s->ending) break; if (c == '\r' && new_data[sf->off]) c = 'y'; else if (c == '\r' && new_data[sf->off] == 0) c = 'n'; if (c == 'y' || c == 'Y') { new_data[sf->off] = 1; any_changes = true; session_printf(s, "%c\r\n", c); break; } if (c == 'n' || c == 'N') { new_data[sf->off] = 0; any_changes = true; session_printf(s, "%c\r\n", c); break; } } break; case CONFIG_TYPE_IP: { char ip_str[16]; lval = CHARS_TO_LONG(new_data[sf->off], new_data[sf->off + 1], new_data[sf->off + 2], new_data[sf->off + 3]); if (lval == 0) snprintf(initial, sizeof(initial), "0.0.0.0"); else { long2ip(lval, ip_str); snprintf(initial, sizeof(initial), "%s", ip_str); } input = session_field_input(s, 16, 16, initial, false, 0); session_output(s, "\r\n", 2); session_flush(s); if (input == NULL || s->ending) break; if (input[0] == '\0') { lval = 0; xfree(&input); } else { lval = ip2long(input); xfree(&input); if (lval == 0) { session_printf(s, "Invalid IP address (^C to cancel)\r\n"); goto get_input; } } new_data[sf->off] = (lval >> 24) & 0xff; new_data[sf->off + 1] = (lval >> 16) & 0xff; new_data[sf->off + 2] = (lval >> 8) & 0xff; new_data[sf->off + 3] = lval & 0xff; any_changes = true; break; } } break; default: ret = c; done = true; break; } if (s->ending) { any_changes = false; ret = -1; goto done; } } done: xfree(&dopts); if (any_changes) { *result = new_data; return 0; } else { if (new_data) xfree(&new_data); *result = NULL; return ret; } }