AmendHub

Download

jcs

/

subtext

/

settings.c

 

(View History)

jcs   settings: Plug some leaks Latest amendment: 652 on 2026-09-22

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdio.h>
18 #include <string.h>
19
20 #include "ansi.h"
21 #include "subtext.h"
22 #include "db.h"
23 #include "focusable.h"
24 #include "main_menu.h"
25 #include "settings.h"
26 #include "tcp.h" /* for long2ip/ip2long */
27 #include "util.h"
28
29 short
30 struct_editor(struct session *s, const struct struct_field *fields,
31 const size_t nfields, const struct session_menu_option *extra_opts,
32 size_t nextra_opts, void *data, size_t dsize, void **result,
33 char *title, char *prompt)
34 {
35 static const struct session_menu_option opts[] = {
36 { '#', "", "Edit setting [#]" },
37 { 's', "Ss", "Save and return to main menu" },
38 { 'q', "QqXx", "Discard changes and return to main menu" },
39 { '?', "?", "List menu options" },
40 };
41 struct session_menu_option *dopts = NULL;
42 const struct struct_field *sf;
43 long lval;
44 char initial[20];
45 char *input = NULL, *new_data;
46 size_t nopts;
47 short n, i, sval, on, ret;
48 bool any_changes = false, done, show_list;
49 unsigned short c;
50
51 new_data = xmalloc(dsize);
52 if (new_data == NULL)
53 goto done;
54 memcpy(new_data, data, dsize);
55
56 nopts = nitems(opts) + nextra_opts;
57 dopts = xcalloc(sizeof(struct session_menu_option), nopts);
58 if (dopts == NULL)
59 goto done;
60 if (extra_opts != NULL)
61 memcpy(dopts, extra_opts,
62 sizeof(struct session_menu_option) * nextra_opts);
63 memcpy(dopts + nextra_opts, opts, sizeof(opts));
64
65 session_printf(s, "{{B}}Editor: %s{{/B}}\r\n", title);
66 session_flush(s);
67
68 any_changes = false;
69 show_list = true;
70 done = false;
71 ret = -1;
72
73 while (!done && !s->ending) {
74 if (show_list) {
75 for (n = 0; n < nfields; n++) {
76 if ((n + 1) % (s->terminal_lines - 2) == 0) {
77 session_printf(s, "-- More --");
78 session_flush(s);
79 sval = session_input_char(s);
80 if (s->vt100)
81 session_printf(s, "\r%s",
82 ansi(s, ANSI_ERASE_LINE, ANSI_END));
83 else
84 session_output(s, "\r", 1);
85
86 if (sval == 'q' || sval == 'Q') {
87 /* abort pagination, like less */
88 show_list = false;
89 break;
90 }
91 }
92
93 sf = &fields[n];
94
95 session_printf(s, "{{B}}%d{{/B}}: %s [", n + 1, sf->name);
96
97 switch (sf->type) {
98 case CONFIG_TYPE_STRING:
99 session_printf(s, "%s", new_data + sf->off);
100 break;
101 case CONFIG_TYPE_PASSWORD:
102 i = strlen(new_data + sf->off);
103 while (i) {
104 session_output(s, "*", 1);
105 i--;
106 }
107 break;
108 case CONFIG_TYPE_SHORT:
109 sval = CHARS_TO_SHORT(new_data[sf->off],
110 new_data[sf->off + 1]);
111 session_printf(s, "%d", sval);
112 break;
113 case CONFIG_TYPE_LONG:
114 lval = CHARS_TO_LONG(new_data[sf->off],
115 new_data[sf->off + 1], new_data[sf->off + 2],
116 new_data[sf->off + 3]);
117 session_printf(s, "%ld", lval);
118 break;
119 case CONFIG_TYPE_BOOLEAN:
120 session_printf(s, "%s",
121 new_data[sf->off] == 0 ? "false" : "true");
122 break;
123 case CONFIG_TYPE_IP: {
124 char ip_str[16];
125 lval = CHARS_TO_LONG(new_data[sf->off],
126 new_data[sf->off + 1], new_data[sf->off + 2],
127 new_data[sf->off + 3]);
128 if (lval == 0)
129 session_printf(s, "0.0.0.0");
130 else {
131 long2ip(lval, ip_str);
132 session_printf(s, "%s", ip_str);
133 }
134 break;
135 }
136 }
137
138 session_printf(s, "]\r\n");
139 }
140 }
141
142 c = session_menu(s, NULL, prompt, NULL, dopts, nopts, show_list,
143 "Option #", &on);
144 show_list = false;
145
146 switch (c) {
147 case 's':
148 goto done;
149 case 'q':
150 any_changes = false;
151 goto done;
152 case '?':
153 show_list = true;
154 break;
155 case '#':
156 if (on < 1 || on > nfields) {
157 session_printf(s, "Invalid option\r\n");
158 session_flush(s);
159 break;
160 }
161
162 sf = &fields[on - 1];
163
164 get_input:
165 session_printf(s, "{{B}}%s:{{/B}} ", sf->name);
166 session_flush(s);
167
168 switch (sf->type) {
169 case CONFIG_TYPE_STRING:
170 case CONFIG_TYPE_PASSWORD:
171 input = session_field_input(s, sf->max, 50,
172 new_data + sf->off, false,
173 (sf->type == CONFIG_TYPE_PASSWORD ? '*' : 0));
174 session_output(s, "\r\n", 2);
175 session_flush(s);
176 if (input == NULL || s->ending) {
177 if (input != NULL)
178 xfree(&input);
179 break;
180 }
181
182 if (strlen(input) >= sf->max) {
183 session_printf(s,
184 "%s is too long (%d max, ^C to cancel)", sf->name,
185 sf->max - 1);
186 xfree(&input);
187 goto get_input;
188 }
189 strlcpy(new_data + sf->off, input, sf->max);
190 xfree(&input);
191 any_changes = true;
192 break;
193 case CONFIG_TYPE_SHORT:
194 case CONFIG_TYPE_LONG:
195 if (sf->type == CONFIG_TYPE_LONG) {
196 lval = CHARS_TO_LONG(new_data[sf->off],
197 new_data[sf->off + 1], new_data[sf->off + 2],
198 new_data[sf->off + 3]);
199 snprintf(initial, sizeof(initial), "%ld", lval);
200 } else {
201 sval = CHARS_TO_SHORT(new_data[sf->off],
202 new_data[sf->off + 1]);
203 snprintf(initial, sizeof(initial), "%d", sval);
204 }
205 input = session_field_input(s, 10, 10, initial, false, 0);
206 session_output(s, "\r\n", 2);
207 session_flush(s);
208 if (input == NULL || s->ending) {
209 if (input != NULL)
210 xfree(&input);
211 break;
212 }
213
214 lval = atol(input);
215 xfree(&input);
216 if (lval < sf->min) {
217 session_printf(s,
218 "%s must be %ld or higher (^C to cancel)\r\n",
219 sf->name, sf->min);
220 goto get_input;
221 }
222 if (lval > sf->max) {
223 session_printf(s,
224 "%s must be %ld or less (^C to cancel)\r\n",
225 sf->name, sf->max);
226 goto get_input;
227 }
228 if (sf->type == CONFIG_TYPE_LONG) {
229 new_data[sf->off] = (lval >> 24) & 0xff;
230 new_data[sf->off + 1] = (lval >> 16) & 0xff;
231 new_data[sf->off + 2] = (lval >> 8) & 0xff;
232 new_data[sf->off + 3] = lval & 0xff;
233 } else {
234 sval = lval;
235 new_data[sf->off] = (sval >> 8) & 0xff;
236 new_data[sf->off + 1] = sval & 0xff;
237 }
238 any_changes = true;
239 break;
240 case CONFIG_TYPE_BOOLEAN:
241 if (new_data[sf->off])
242 session_printf(s, "[Y/n] ");
243 else
244 session_printf(s, "[y/N] ");
245 session_flush(s);
246
247 for (;;) {
248 c = session_input_char(s);
249 if (s->ending)
250 break;
251 if (c == '\r' && new_data[sf->off])
252 c = 'y';
253 else if (c == '\r' && new_data[sf->off] == 0)
254 c = 'n';
255 if (c == 'y' || c == 'Y') {
256 new_data[sf->off] = 1;
257 any_changes = true;
258 session_printf(s, "%c\r\n", c);
259 break;
260 }
261 if (c == 'n' || c == 'N') {
262 new_data[sf->off] = 0;
263 any_changes = true;
264 session_printf(s, "%c\r\n", c);
265 break;
266 }
267 }
268 break;
269 case CONFIG_TYPE_IP: {
270 char ip_str[16];
271 lval = CHARS_TO_LONG(new_data[sf->off],
272 new_data[sf->off + 1], new_data[sf->off + 2],
273 new_data[sf->off + 3]);
274 if (lval == 0)
275 snprintf(initial, sizeof(initial), "0.0.0.0");
276 else {
277 long2ip(lval, ip_str);
278 snprintf(initial, sizeof(initial), "%s", ip_str);
279 }
280
281 input = session_field_input(s, 16, 16, initial, false, 0);
282 session_output(s, "\r\n", 2);
283 session_flush(s);
284 if (input == NULL || s->ending) {
285 if (input != NULL)
286 xfree(&input);
287 break;
288 }
289
290 if (input[0] == '\0') {
291 lval = 0;
292 xfree(&input);
293 } else {
294 lval = ip2long(input);
295 xfree(&input);
296 if (lval == 0) {
297 session_printf(s,
298 "Invalid IP address (^C to cancel)\r\n");
299 goto get_input;
300 }
301 }
302 new_data[sf->off] = (lval >> 24) & 0xff;
303 new_data[sf->off + 1] = (lval >> 16) & 0xff;
304 new_data[sf->off + 2] = (lval >> 8) & 0xff;
305 new_data[sf->off + 3] = lval & 0xff;
306 any_changes = true;
307 break;
308 }
309 }
310 break;
311 default:
312 ret = c;
313 done = true;
314 break;
315 }
316
317 if (s->ending) {
318 any_changes = false;
319 ret = -1;
320 goto done;
321 }
322 }
323
324 done:
325 if (dopts != NULL)
326 xfree(&dopts);
327
328 if (any_changes) {
329 *result = new_data;
330 return 0;
331 } else {
332 if (new_data)
333 xfree(&new_data);
334 *result = NULL;
335 return ret;
336 }
337 }