Line | Count | Source (jump to first uncovered line) |
1 | | /* $OpenBSD$ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <sys/types.h> |
20 | | |
21 | | #include <ctype.h> |
22 | | #include <pwd.h> |
23 | | #include <stdlib.h> |
24 | | #include <string.h> |
25 | | #include <time.h> |
26 | | #include <unistd.h> |
27 | | |
28 | | #include "tmux.h" |
29 | | |
30 | | /* Command queue flags. */ |
31 | 16.8k | #define CMDQ_FIRED 0x1 |
32 | 8.85k | #define CMDQ_WAITING 0x2 |
33 | | |
34 | | /* Command queue item type. */ |
35 | | enum cmdq_type { |
36 | | CMDQ_COMMAND, |
37 | | CMDQ_CALLBACK, |
38 | | }; |
39 | | |
40 | | /* Command queue item. */ |
41 | | struct cmdq_item { |
42 | | char *name; |
43 | | struct cmdq_list *queue; |
44 | | struct cmdq_item *next; |
45 | | |
46 | | struct client *client; |
47 | | struct client *target_client; |
48 | | |
49 | | enum cmdq_type type; |
50 | | u_int group; |
51 | | |
52 | | u_int number; |
53 | | time_t time; |
54 | | |
55 | | int flags; |
56 | | |
57 | | struct cmdq_state *state; |
58 | | struct cmd_find_state source; |
59 | | struct cmd_find_state target; |
60 | | |
61 | | struct cmd_list *cmdlist; |
62 | | struct cmd *cmd; |
63 | | |
64 | | cmdq_cb cb; |
65 | | void *data; |
66 | | |
67 | | TAILQ_ENTRY(cmdq_item) entry; |
68 | | }; |
69 | | TAILQ_HEAD(cmdq_item_list, cmdq_item); |
70 | | |
71 | | /* |
72 | | * Command queue state. This is the context for commands on the command queue. |
73 | | * It holds information about how the commands were fired (the key and flags), |
74 | | * any additional formats for the commands, and the current default target. |
75 | | * Multiple commands can share the same state and a command may update the |
76 | | * default target. |
77 | | */ |
78 | | struct cmdq_state { |
79 | | int references; |
80 | | int flags; |
81 | | |
82 | | struct format_tree *formats; |
83 | | |
84 | | struct key_event event; |
85 | | struct cmd_find_state current; |
86 | | }; |
87 | | |
88 | | /* Command queue. */ |
89 | | struct cmdq_list { |
90 | | struct cmdq_item *item; |
91 | | struct cmdq_item_list list; |
92 | | }; |
93 | | |
94 | | /* Get command queue name. */ |
95 | | static const char * |
96 | | cmdq_name(struct client *c) |
97 | 20.5k | { |
98 | 20.5k | static char s[256]; |
99 | | |
100 | 20.5k | if (c == NULL) |
101 | 20.5k | return ("<global>"); |
102 | 0 | if (c->name != NULL) |
103 | 0 | xsnprintf(s, sizeof s, "<%s>", c->name); |
104 | 0 | else |
105 | 0 | xsnprintf(s, sizeof s, "<%p>", c); |
106 | 0 | return (s); |
107 | 20.5k | } |
108 | | |
109 | | /* Get command queue from client. */ |
110 | | static struct cmdq_list * |
111 | | cmdq_get(struct client *c) |
112 | 28.9k | { |
113 | 28.9k | static struct cmdq_list *global_queue; |
114 | | |
115 | 28.9k | if (c == NULL) { |
116 | 28.9k | if (global_queue == NULL) |
117 | 1 | global_queue = cmdq_new(); |
118 | 28.9k | return (global_queue); |
119 | 28.9k | } |
120 | 0 | return (c->queue); |
121 | 28.9k | } |
122 | | |
123 | | /* Create a queue. */ |
124 | | struct cmdq_list * |
125 | | cmdq_new(void) |
126 | 1 | { |
127 | 1 | struct cmdq_list *queue; |
128 | | |
129 | 1 | queue = xcalloc(1, sizeof *queue); |
130 | 1 | TAILQ_INIT (&queue->list); |
131 | 1 | return (queue); |
132 | 1 | } |
133 | | |
134 | | /* Free a queue. */ |
135 | | void |
136 | | cmdq_free(struct cmdq_list *queue) |
137 | 0 | { |
138 | 0 | if (!TAILQ_EMPTY(&queue->list)) |
139 | 0 | fatalx("queue not empty"); |
140 | 0 | free(queue); |
141 | 0 | } |
142 | | |
143 | | /* Get item name. */ |
144 | | const char * |
145 | | cmdq_get_name(struct cmdq_item *item) |
146 | 0 | { |
147 | 0 | return (item->name); |
148 | 0 | } |
149 | | |
150 | | /* Get item client. */ |
151 | | struct client * |
152 | | cmdq_get_client(struct cmdq_item *item) |
153 | 0 | { |
154 | 0 | return (item->client); |
155 | 0 | } |
156 | | |
157 | | /* Get item target client. */ |
158 | | struct client * |
159 | | cmdq_get_target_client(struct cmdq_item *item) |
160 | 0 | { |
161 | 0 | return (item->target_client); |
162 | 0 | } |
163 | | |
164 | | /* Get item state. */ |
165 | | struct cmdq_state * |
166 | | cmdq_get_state(struct cmdq_item *item) |
167 | 0 | { |
168 | 0 | return (item->state); |
169 | 0 | } |
170 | | |
171 | | /* Get item target. */ |
172 | | struct cmd_find_state * |
173 | | cmdq_get_target(struct cmdq_item *item) |
174 | 0 | { |
175 | 0 | return (&item->target); |
176 | 0 | } |
177 | | |
178 | | /* Get item source. */ |
179 | | struct cmd_find_state * |
180 | | cmdq_get_source(struct cmdq_item *item) |
181 | 0 | { |
182 | 0 | return (&item->source); |
183 | 0 | } |
184 | | |
185 | | /* Get state event. */ |
186 | | struct key_event * |
187 | | cmdq_get_event(struct cmdq_item *item) |
188 | 0 | { |
189 | 0 | return (&item->state->event); |
190 | 0 | } |
191 | | |
192 | | /* Get state current target. */ |
193 | | struct cmd_find_state * |
194 | | cmdq_get_current(struct cmdq_item *item) |
195 | 0 | { |
196 | 0 | return (&item->state->current); |
197 | 0 | } |
198 | | |
199 | | /* Get state flags. */ |
200 | | int |
201 | | cmdq_get_flags(struct cmdq_item *item) |
202 | 0 | { |
203 | 0 | return (item->state->flags); |
204 | 0 | } |
205 | | |
206 | | /* Create a new state. */ |
207 | | struct cmdq_state * |
208 | | cmdq_new_state(struct cmd_find_state *current, struct key_event *event, |
209 | | int flags) |
210 | 8.43k | { |
211 | 8.43k | struct cmdq_state *state; |
212 | | |
213 | 8.43k | state = xcalloc(1, sizeof *state); |
214 | 8.43k | state->references = 1; |
215 | 8.43k | state->flags = flags; |
216 | | |
217 | 8.43k | if (event != NULL) |
218 | 0 | memcpy(&state->event, event, sizeof state->event); |
219 | 8.43k | else |
220 | 8.43k | state->event.key = KEYC_NONE; |
221 | 8.43k | if (current != NULL && cmd_find_valid_state(current)) |
222 | 0 | cmd_find_copy_state(&state->current, current); |
223 | 8.43k | else |
224 | 8.43k | cmd_find_clear_state(&state->current, 0); |
225 | | |
226 | 8.43k | return (state); |
227 | 8.43k | } |
228 | | |
229 | | /* Add a reference to a state. */ |
230 | | struct cmdq_state * |
231 | | cmdq_link_state(struct cmdq_state *state) |
232 | 0 | { |
233 | 0 | state->references++; |
234 | 0 | return (state); |
235 | 0 | } |
236 | | |
237 | | /* Make a copy of a state. */ |
238 | | struct cmdq_state * |
239 | | cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current) |
240 | 0 | { |
241 | 0 | if (current != NULL) |
242 | 0 | return (cmdq_new_state(current, &state->event, state->flags)); |
243 | 0 | return (cmdq_new_state(&state->current, &state->event, state->flags)); |
244 | 0 | } |
245 | | |
246 | | /* Free a state. */ |
247 | | void |
248 | | cmdq_free_state(struct cmdq_state *state) |
249 | 8.43k | { |
250 | 8.43k | if (--state->references != 0) |
251 | 0 | return; |
252 | | |
253 | 8.43k | if (state->formats != NULL) |
254 | 0 | format_free(state->formats); |
255 | 8.43k | free(state); |
256 | 8.43k | } |
257 | | |
258 | | /* Add a format to command queue. */ |
259 | | void |
260 | | cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...) |
261 | 0 | { |
262 | 0 | va_list ap; |
263 | 0 | char *value; |
264 | |
|
265 | 0 | va_start(ap, fmt); |
266 | 0 | xvasprintf(&value, fmt, ap); |
267 | 0 | va_end(ap); |
268 | |
|
269 | 0 | if (state->formats == NULL) |
270 | 0 | state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); |
271 | 0 | format_add(state->formats, key, "%s", value); |
272 | |
|
273 | 0 | free(value); |
274 | 0 | } |
275 | | |
276 | | /* Add formats to command queue. */ |
277 | | void |
278 | | cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft) |
279 | 0 | { |
280 | 0 | if (state->formats == NULL) |
281 | 0 | state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); |
282 | 0 | format_merge(state->formats, ft); |
283 | 0 | } |
284 | | |
285 | | /* Merge formats from item. */ |
286 | | void |
287 | | cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) |
288 | 0 | { |
289 | 0 | const struct cmd_entry *entry; |
290 | |
|
291 | 0 | if (item->cmd != NULL) { |
292 | 0 | entry = cmd_get_entry(item->cmd); |
293 | 0 | format_add(ft, "command", "%s", entry->name); |
294 | 0 | } |
295 | 0 | if (item->state->formats != NULL) |
296 | 0 | format_merge(ft, item->state->formats); |
297 | 0 | } |
298 | | |
299 | | /* Append an item. */ |
300 | | struct cmdq_item * |
301 | | cmdq_append(struct client *c, struct cmdq_item *item) |
302 | 8.43k | { |
303 | 8.43k | struct cmdq_list *queue = cmdq_get(c); |
304 | 8.43k | struct cmdq_item *next; |
305 | | |
306 | 8.43k | do { |
307 | 8.43k | next = item->next; |
308 | 8.43k | item->next = NULL; |
309 | | |
310 | 8.43k | if (c != NULL) |
311 | 0 | c->references++; |
312 | 8.43k | item->client = c; |
313 | | |
314 | 8.43k | item->queue = queue; |
315 | 8.43k | TAILQ_INSERT_TAIL(&queue->list, item, entry); |
316 | 8.43k | log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); |
317 | | |
318 | 8.43k | item = next; |
319 | 8.43k | } while (item != NULL); |
320 | 8.43k | return (TAILQ_LAST(&queue->list, cmdq_item_list)); |
321 | 8.43k | } |
322 | | |
323 | | /* Insert an item. */ |
324 | | struct cmdq_item * |
325 | | cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) |
326 | 0 | { |
327 | 0 | struct client *c = after->client; |
328 | 0 | struct cmdq_list *queue = after->queue; |
329 | 0 | struct cmdq_item *next; |
330 | |
|
331 | 0 | do { |
332 | 0 | next = item->next; |
333 | 0 | item->next = after->next; |
334 | 0 | after->next = item; |
335 | |
|
336 | 0 | if (c != NULL) |
337 | 0 | c->references++; |
338 | 0 | item->client = c; |
339 | |
|
340 | 0 | item->queue = queue; |
341 | 0 | TAILQ_INSERT_AFTER(&queue->list, after, item, entry); |
342 | 0 | log_debug("%s %s: %s after %s", __func__, cmdq_name(c), |
343 | 0 | item->name, after->name); |
344 | |
|
345 | 0 | after = item; |
346 | 0 | item = next; |
347 | 0 | } while (item != NULL); |
348 | 0 | return (after); |
349 | 0 | } |
350 | | |
351 | | /* Insert a hook. */ |
352 | | void |
353 | | cmdq_insert_hook(struct session *s, struct cmdq_item *item, |
354 | | struct cmd_find_state *current, const char *fmt, ...) |
355 | 0 | { |
356 | 0 | struct cmdq_state *state = item->state; |
357 | 0 | struct cmd *cmd = item->cmd; |
358 | 0 | struct args *args = cmd_get_args(cmd); |
359 | 0 | struct args_entry *ae; |
360 | 0 | struct args_value *av; |
361 | 0 | struct options *oo; |
362 | 0 | va_list ap; |
363 | 0 | char *name, tmp[32], flag, *arguments; |
364 | 0 | u_int i; |
365 | 0 | const char *value; |
366 | 0 | struct cmdq_item *new_item; |
367 | 0 | struct cmdq_state *new_state; |
368 | 0 | struct options_entry *o; |
369 | 0 | struct options_array_item *a; |
370 | 0 | struct cmd_list *cmdlist; |
371 | |
|
372 | 0 | if (item->state->flags & CMDQ_STATE_NOHOOKS) |
373 | 0 | return; |
374 | 0 | if (s == NULL) |
375 | 0 | oo = global_s_options; |
376 | 0 | else |
377 | 0 | oo = s->options; |
378 | |
|
379 | 0 | va_start(ap, fmt); |
380 | 0 | xvasprintf(&name, fmt, ap); |
381 | 0 | va_end(ap); |
382 | |
|
383 | 0 | o = options_get(oo, name); |
384 | 0 | if (o == NULL) { |
385 | 0 | free(name); |
386 | 0 | return; |
387 | 0 | } |
388 | 0 | log_debug("running hook %s (parent %p)", name, item); |
389 | | |
390 | | /* |
391 | | * The hooks get a new state because they should not update the current |
392 | | * target or formats for any subsequent commands. |
393 | | */ |
394 | 0 | new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); |
395 | 0 | cmdq_add_format(new_state, "hook", "%s", name); |
396 | |
|
397 | 0 | arguments = args_print(args); |
398 | 0 | cmdq_add_format(new_state, "hook_arguments", "%s", arguments); |
399 | 0 | free(arguments); |
400 | |
|
401 | 0 | for (i = 0; i < args_count(args); i++) { |
402 | 0 | xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); |
403 | 0 | cmdq_add_format(new_state, tmp, "%s", args_string(args, i)); |
404 | 0 | } |
405 | 0 | flag = args_first(args, &ae); |
406 | 0 | while (flag != 0) { |
407 | 0 | value = args_get(args, flag); |
408 | 0 | if (value == NULL) { |
409 | 0 | xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); |
410 | 0 | cmdq_add_format(new_state, tmp, "1"); |
411 | 0 | } else { |
412 | 0 | xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); |
413 | 0 | cmdq_add_format(new_state, tmp, "%s", value); |
414 | 0 | } |
415 | |
|
416 | 0 | i = 0; |
417 | 0 | av = args_first_value(args, flag); |
418 | 0 | while (av != NULL) { |
419 | 0 | xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); |
420 | 0 | cmdq_add_format(new_state, tmp, "%s", av->string); |
421 | 0 | i++; |
422 | 0 | av = args_next_value(av); |
423 | 0 | } |
424 | |
|
425 | 0 | flag = args_next(&ae); |
426 | 0 | } |
427 | |
|
428 | 0 | a = options_array_first(o); |
429 | 0 | while (a != NULL) { |
430 | 0 | cmdlist = options_array_item_value(a)->cmdlist; |
431 | 0 | if (cmdlist != NULL) { |
432 | 0 | new_item = cmdq_get_command(cmdlist, new_state); |
433 | 0 | if (item != NULL) |
434 | 0 | item = cmdq_insert_after(item, new_item); |
435 | 0 | else |
436 | 0 | item = cmdq_append(NULL, new_item); |
437 | 0 | } |
438 | 0 | a = options_array_next(a); |
439 | 0 | } |
440 | |
|
441 | 0 | cmdq_free_state(new_state); |
442 | 0 | free(name); |
443 | 0 | } |
444 | | |
445 | | /* Continue processing command queue. */ |
446 | | void |
447 | | cmdq_continue(struct cmdq_item *item) |
448 | 0 | { |
449 | 0 | item->flags &= ~CMDQ_WAITING; |
450 | 0 | } |
451 | | |
452 | | /* Remove an item. */ |
453 | | static void |
454 | | cmdq_remove(struct cmdq_item *item) |
455 | 8.43k | { |
456 | 8.43k | if (item->client != NULL) |
457 | 0 | server_client_unref(item->client); |
458 | 8.43k | if (item->cmdlist != NULL) |
459 | 0 | cmd_list_free(item->cmdlist); |
460 | 8.43k | cmdq_free_state(item->state); |
461 | | |
462 | 8.43k | TAILQ_REMOVE(&item->queue->list, item, entry); |
463 | | |
464 | 8.43k | free(item->name); |
465 | 8.43k | free(item); |
466 | 8.43k | } |
467 | | |
468 | | /* Remove all subsequent items that match this item's group. */ |
469 | | static void |
470 | | cmdq_remove_group(struct cmdq_item *item) |
471 | 0 | { |
472 | 0 | struct cmdq_item *this, *next; |
473 | |
|
474 | 0 | if (item->group == 0) |
475 | 0 | return; |
476 | 0 | this = TAILQ_NEXT(item, entry); |
477 | 0 | while (this != NULL) { |
478 | 0 | next = TAILQ_NEXT(this, entry); |
479 | 0 | if (this->group == item->group) |
480 | 0 | cmdq_remove(this); |
481 | 0 | this = next; |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | | /* Empty command callback. */ |
486 | | static enum cmd_retval |
487 | | cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data) |
488 | 0 | { |
489 | 0 | return (CMD_RETURN_NORMAL); |
490 | 0 | } |
491 | | |
492 | | /* Get a command for the command queue. */ |
493 | | struct cmdq_item * |
494 | | cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) |
495 | 0 | { |
496 | 0 | struct cmdq_item *item, *first = NULL, *last = NULL; |
497 | 0 | struct cmd *cmd; |
498 | 0 | const struct cmd_entry *entry; |
499 | 0 | int created = 0; |
500 | |
|
501 | 0 | if ((cmd = cmd_list_first(cmdlist)) == NULL) |
502 | 0 | return (cmdq_get_callback(cmdq_empty_command, NULL)); |
503 | | |
504 | 0 | if (state == NULL) { |
505 | 0 | state = cmdq_new_state(NULL, NULL, 0); |
506 | 0 | created = 1; |
507 | 0 | } |
508 | |
|
509 | 0 | while (cmd != NULL) { |
510 | 0 | entry = cmd_get_entry(cmd); |
511 | |
|
512 | 0 | item = xcalloc(1, sizeof *item); |
513 | 0 | xasprintf(&item->name, "[%s/%p]", entry->name, item); |
514 | 0 | item->type = CMDQ_COMMAND; |
515 | |
|
516 | 0 | item->group = cmd_get_group(cmd); |
517 | 0 | item->state = cmdq_link_state(state); |
518 | |
|
519 | 0 | item->cmdlist = cmdlist; |
520 | 0 | item->cmd = cmd; |
521 | |
|
522 | 0 | cmdlist->references++; |
523 | 0 | log_debug("%s: %s group %u", __func__, item->name, item->group); |
524 | |
|
525 | 0 | if (first == NULL) |
526 | 0 | first = item; |
527 | 0 | if (last != NULL) |
528 | 0 | last->next = item; |
529 | 0 | last = item; |
530 | |
|
531 | 0 | cmd = cmd_list_next(cmd); |
532 | 0 | } |
533 | |
|
534 | 0 | if (created) |
535 | 0 | cmdq_free_state(state); |
536 | 0 | return (first); |
537 | 0 | } |
538 | | |
539 | | /* Fill in flag for a command. */ |
540 | | static enum cmd_retval |
541 | | cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, |
542 | | const struct cmd_entry_flag *flag) |
543 | 0 | { |
544 | 0 | const char *value; |
545 | |
|
546 | 0 | if (flag->flag == 0) { |
547 | 0 | cmd_find_from_client(fs, item->target_client, 0); |
548 | 0 | return (CMD_RETURN_NORMAL); |
549 | 0 | } |
550 | | |
551 | 0 | value = args_get(cmd_get_args(item->cmd), flag->flag); |
552 | 0 | if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { |
553 | 0 | cmd_find_clear_state(fs, 0); |
554 | 0 | return (CMD_RETURN_ERROR); |
555 | 0 | } |
556 | 0 | return (CMD_RETURN_NORMAL); |
557 | 0 | } |
558 | | |
559 | | /* Add message with command. */ |
560 | | static void |
561 | | cmdq_add_message(struct cmdq_item *item) |
562 | 0 | { |
563 | 0 | struct client *c = item->client; |
564 | 0 | struct cmdq_state *state = item->state; |
565 | 0 | const char *key; |
566 | 0 | char *tmp; |
567 | 0 | uid_t uid; |
568 | 0 | struct passwd *pw; |
569 | 0 | char *user = NULL; |
570 | |
|
571 | 0 | tmp = cmd_print(item->cmd); |
572 | 0 | if (c != NULL) { |
573 | 0 | uid = proc_get_peer_uid(c->peer); |
574 | 0 | if (uid != (uid_t)-1 && uid != getuid()) { |
575 | 0 | if ((pw = getpwuid(uid)) != NULL) |
576 | 0 | xasprintf(&user, "[%s]", pw->pw_name); |
577 | 0 | else |
578 | 0 | user = xstrdup("[unknown]"); |
579 | 0 | } else |
580 | 0 | user = xstrdup(""); |
581 | 0 | if (c->session != NULL && state->event.key != KEYC_NONE) { |
582 | 0 | key = key_string_lookup_key(state->event.key, 0); |
583 | 0 | server_add_message("%s%s key %s: %s", c->name, user, |
584 | 0 | key, tmp); |
585 | 0 | } else { |
586 | 0 | server_add_message("%s%s command: %s", c->name, user, |
587 | 0 | tmp); |
588 | 0 | } |
589 | 0 | free(user); |
590 | 0 | } else |
591 | 0 | server_add_message("command: %s", tmp); |
592 | 0 | free(tmp); |
593 | 0 | } |
594 | | |
595 | | /* Fire command on command queue. */ |
596 | | static enum cmd_retval |
597 | | cmdq_fire_command(struct cmdq_item *item) |
598 | 0 | { |
599 | 0 | const char *name = cmdq_name(item->client); |
600 | 0 | struct cmdq_state *state = item->state; |
601 | 0 | struct cmd *cmd = item->cmd; |
602 | 0 | struct args *args = cmd_get_args(cmd); |
603 | 0 | const struct cmd_entry *entry = cmd_get_entry(cmd); |
604 | 0 | struct client *tc, *saved = item->client; |
605 | 0 | enum cmd_retval retval; |
606 | 0 | struct cmd_find_state *fsp, fs; |
607 | 0 | int flags, quiet = 0; |
608 | 0 | char *tmp; |
609 | |
|
610 | 0 | if (cfg_finished) |
611 | 0 | cmdq_add_message(item); |
612 | 0 | if (log_get_level() > 1) { |
613 | 0 | tmp = cmd_print(cmd); |
614 | 0 | log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); |
615 | 0 | free(tmp); |
616 | 0 | } |
617 | |
|
618 | 0 | flags = !!(state->flags & CMDQ_STATE_CONTROL); |
619 | 0 | cmdq_guard(item, "begin", flags); |
620 | |
|
621 | 0 | if (item->client == NULL) |
622 | 0 | item->client = cmd_find_client(item, NULL, 1); |
623 | |
|
624 | 0 | if (entry->flags & CMD_CLIENT_CANFAIL) |
625 | 0 | quiet = 1; |
626 | 0 | if (entry->flags & CMD_CLIENT_CFLAG) { |
627 | 0 | tc = cmd_find_client(item, args_get(args, 'c'), quiet); |
628 | 0 | if (tc == NULL && !quiet) { |
629 | 0 | retval = CMD_RETURN_ERROR; |
630 | 0 | goto out; |
631 | 0 | } |
632 | 0 | } else if (entry->flags & CMD_CLIENT_TFLAG) { |
633 | 0 | tc = cmd_find_client(item, args_get(args, 't'), quiet); |
634 | 0 | if (tc == NULL && !quiet) { |
635 | 0 | retval = CMD_RETURN_ERROR; |
636 | 0 | goto out; |
637 | 0 | } |
638 | 0 | } else |
639 | 0 | tc = cmd_find_client(item, NULL, 1); |
640 | 0 | item->target_client = tc; |
641 | |
|
642 | 0 | retval = cmdq_find_flag(item, &item->source, &entry->source); |
643 | 0 | if (retval == CMD_RETURN_ERROR) |
644 | 0 | goto out; |
645 | 0 | retval = cmdq_find_flag(item, &item->target, &entry->target); |
646 | 0 | if (retval == CMD_RETURN_ERROR) |
647 | 0 | goto out; |
648 | | |
649 | 0 | retval = entry->exec(cmd, item); |
650 | 0 | if (retval == CMD_RETURN_ERROR) |
651 | 0 | goto out; |
652 | | |
653 | 0 | if (entry->flags & CMD_AFTERHOOK) { |
654 | 0 | if (cmd_find_valid_state(&item->target)) |
655 | 0 | fsp = &item->target; |
656 | 0 | else if (cmd_find_valid_state(&item->state->current)) |
657 | 0 | fsp = &item->state->current; |
658 | 0 | else if (cmd_find_from_client(&fs, item->client, 0) == 0) |
659 | 0 | fsp = &fs; |
660 | 0 | else |
661 | 0 | goto out; |
662 | 0 | cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); |
663 | 0 | } |
664 | | |
665 | 0 | out: |
666 | 0 | item->client = saved; |
667 | 0 | if (retval == CMD_RETURN_ERROR) { |
668 | 0 | fsp = NULL; |
669 | 0 | if (cmd_find_valid_state(&item->target)) |
670 | 0 | fsp = &item->target; |
671 | 0 | else if (cmd_find_valid_state(&item->state->current)) |
672 | 0 | fsp = &item->state->current; |
673 | 0 | else if (cmd_find_from_client(&fs, item->client, 0) == 0) |
674 | 0 | fsp = &fs; |
675 | 0 | cmdq_insert_hook(fsp != NULL ? fsp->s : NULL, item, fsp, |
676 | 0 | "command-error"); |
677 | 0 | cmdq_guard(item, "error", flags); |
678 | 0 | } else |
679 | 0 | cmdq_guard(item, "end", flags); |
680 | 0 | return (retval); |
681 | 0 | } |
682 | | |
683 | | /* Get a callback for the command queue. */ |
684 | | struct cmdq_item * |
685 | | cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) |
686 | 8.43k | { |
687 | 8.43k | struct cmdq_item *item; |
688 | | |
689 | 8.43k | item = xcalloc(1, sizeof *item); |
690 | 8.43k | xasprintf(&item->name, "[%s/%p]", name, item); |
691 | 8.43k | item->type = CMDQ_CALLBACK; |
692 | | |
693 | 8.43k | item->group = 0; |
694 | 8.43k | item->state = cmdq_new_state(NULL, NULL, 0); |
695 | | |
696 | 8.43k | item->cb = cb; |
697 | 8.43k | item->data = data; |
698 | | |
699 | 8.43k | return (item); |
700 | 8.43k | } |
701 | | |
702 | | /* Generic error callback. */ |
703 | | static enum cmd_retval |
704 | | cmdq_error_callback(struct cmdq_item *item, void *data) |
705 | 0 | { |
706 | 0 | char *error = data; |
707 | |
|
708 | 0 | cmdq_error(item, "%s", error); |
709 | 0 | free(error); |
710 | |
|
711 | 0 | return (CMD_RETURN_NORMAL); |
712 | 0 | } |
713 | | |
714 | | /* Get an error callback for the command queue. */ |
715 | | struct cmdq_item * |
716 | | cmdq_get_error(const char *error) |
717 | 0 | { |
718 | 0 | return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); |
719 | 0 | } |
720 | | |
721 | | /* Fire callback on callback queue. */ |
722 | | static enum cmd_retval |
723 | | cmdq_fire_callback(struct cmdq_item *item) |
724 | 8.43k | { |
725 | 8.43k | return (item->cb(item, item->data)); |
726 | 8.43k | } |
727 | | |
728 | | /* Process next item on command queue. */ |
729 | | u_int |
730 | | cmdq_next(struct client *c) |
731 | 12.1k | { |
732 | 12.1k | struct cmdq_list *queue = cmdq_get(c); |
733 | 12.1k | const char *name = cmdq_name(c); |
734 | 12.1k | struct cmdq_item *item; |
735 | 12.1k | enum cmd_retval retval; |
736 | 12.1k | u_int items = 0; |
737 | 12.1k | static u_int number; |
738 | | |
739 | 12.1k | if (TAILQ_EMPTY(&queue->list)) { |
740 | 11.6k | log_debug("%s %s: empty", __func__, name); |
741 | 11.6k | return (0); |
742 | 11.6k | } |
743 | 424 | if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) { |
744 | 0 | log_debug("%s %s: waiting", __func__, name); |
745 | 0 | return (0); |
746 | 0 | } |
747 | | |
748 | 424 | log_debug("%s %s: enter", __func__, name); |
749 | 8.85k | for (;;) { |
750 | 8.85k | item = queue->item = TAILQ_FIRST(&queue->list); |
751 | 8.85k | if (item == NULL) |
752 | 424 | break; |
753 | 8.43k | log_debug("%s %s: %s (%d), flags %x", __func__, name, |
754 | 8.43k | item->name, item->type, item->flags); |
755 | | |
756 | | /* |
757 | | * Any item with the waiting flag set waits until an external |
758 | | * event clears the flag (for example, a job - look at |
759 | | * run-shell). |
760 | | */ |
761 | 8.43k | if (item->flags & CMDQ_WAITING) |
762 | 0 | goto waiting; |
763 | | |
764 | | /* |
765 | | * Items are only fired once, once the fired flag is set, a |
766 | | * waiting flag can only be cleared by an external event. |
767 | | */ |
768 | 8.43k | if (~item->flags & CMDQ_FIRED) { |
769 | 8.43k | item->time = time(NULL); |
770 | 8.43k | item->number = ++number; |
771 | | |
772 | 8.43k | switch (item->type) { |
773 | 0 | case CMDQ_COMMAND: |
774 | 0 | retval = cmdq_fire_command(item); |
775 | | |
776 | | /* |
777 | | * If a command returns an error, remove any |
778 | | * subsequent commands in the same group. |
779 | | */ |
780 | 0 | if (retval == CMD_RETURN_ERROR) |
781 | 0 | cmdq_remove_group(item); |
782 | 0 | break; |
783 | 8.43k | case CMDQ_CALLBACK: |
784 | 8.43k | retval = cmdq_fire_callback(item); |
785 | 8.43k | break; |
786 | 0 | default: |
787 | 0 | retval = CMD_RETURN_ERROR; |
788 | 0 | break; |
789 | 8.43k | } |
790 | 8.43k | item->flags |= CMDQ_FIRED; |
791 | | |
792 | 8.43k | if (retval == CMD_RETURN_WAIT) { |
793 | 0 | item->flags |= CMDQ_WAITING; |
794 | 0 | goto waiting; |
795 | 0 | } |
796 | 8.43k | items++; |
797 | 8.43k | } |
798 | 8.43k | cmdq_remove(item); |
799 | 8.43k | } |
800 | 424 | queue->item = NULL; |
801 | | |
802 | 424 | log_debug("%s %s: exit (empty)", __func__, name); |
803 | 424 | return (items); |
804 | | |
805 | 0 | waiting: |
806 | 0 | log_debug("%s %s: exit (wait)", __func__, name); |
807 | 0 | return (items); |
808 | 424 | } |
809 | | |
810 | | /* Get running item if any. */ |
811 | | struct cmdq_item * |
812 | | cmdq_running(struct client *c) |
813 | 8.43k | { |
814 | 8.43k | struct cmdq_list *queue = cmdq_get(c); |
815 | | |
816 | 8.43k | if (queue->item == NULL) |
817 | 8.43k | return (NULL); |
818 | 0 | if (queue->item->flags & CMDQ_WAITING) |
819 | 0 | return (NULL); |
820 | 0 | return (queue->item); |
821 | 0 | } |
822 | | |
823 | | /* Print a guard line. */ |
824 | | void |
825 | | cmdq_guard(struct cmdq_item *item, const char *guard, int flags) |
826 | 0 | { |
827 | 0 | struct client *c = item->client; |
828 | 0 | long t = item->time; |
829 | 0 | u_int number = item->number; |
830 | |
|
831 | 0 | if (c != NULL && (c->flags & CLIENT_CONTROL)) |
832 | 0 | control_write(c, "%%%s %ld %u %d", guard, t, number, flags); |
833 | 0 | } |
834 | | |
835 | | /* Show message from command. */ |
836 | | void |
837 | | cmdq_print_data(struct cmdq_item *item, int parse, struct evbuffer *evb) |
838 | 0 | { |
839 | 0 | server_client_print(item->client, parse, evb); |
840 | 0 | } |
841 | | |
842 | | /* Show message from command. */ |
843 | | void |
844 | | cmdq_print(struct cmdq_item *item, const char *fmt, ...) |
845 | 0 | { |
846 | 0 | va_list ap; |
847 | 0 | struct evbuffer *evb; |
848 | |
|
849 | 0 | evb = evbuffer_new(); |
850 | 0 | if (evb == NULL) |
851 | 0 | fatalx("out of memory"); |
852 | | |
853 | 0 | va_start(ap, fmt); |
854 | 0 | evbuffer_add_vprintf(evb, fmt, ap); |
855 | 0 | va_end(ap); |
856 | |
|
857 | 0 | cmdq_print_data(item, 0, evb); |
858 | 0 | evbuffer_free(evb); |
859 | 0 | } |
860 | | |
861 | | /* Show error from command. */ |
862 | | void |
863 | | cmdq_error(struct cmdq_item *item, const char *fmt, ...) |
864 | 0 | { |
865 | 0 | struct client *c = item->client; |
866 | 0 | struct cmd *cmd = item->cmd; |
867 | 0 | va_list ap; |
868 | 0 | char *msg, *tmp; |
869 | 0 | const char *file; |
870 | 0 | u_int line; |
871 | |
|
872 | 0 | va_start(ap, fmt); |
873 | 0 | xvasprintf(&msg, fmt, ap); |
874 | 0 | va_end(ap); |
875 | |
|
876 | 0 | log_debug("%s: %s", __func__, msg); |
877 | |
|
878 | 0 | if (c == NULL) { |
879 | 0 | cmd_get_source(cmd, &file, &line); |
880 | 0 | cfg_add_cause("%s:%u: %s", file, line, msg); |
881 | 0 | } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { |
882 | 0 | server_add_message("%s message: %s", c->name, msg); |
883 | 0 | if (~c->flags & CLIENT_UTF8) { |
884 | 0 | tmp = msg; |
885 | 0 | msg = utf8_sanitize(tmp); |
886 | 0 | free(tmp); |
887 | 0 | } |
888 | 0 | if (c->flags & CLIENT_CONTROL) |
889 | 0 | control_write(c, "%s", msg); |
890 | 0 | else |
891 | 0 | file_error(c, "%s\n", msg); |
892 | 0 | c->retval = 1; |
893 | 0 | } else { |
894 | 0 | *msg = toupper((u_char) *msg); |
895 | 0 | status_message_set(c, -1, 1, 0, "%s", msg); |
896 | 0 | } |
897 | |
|
898 | 0 | free(msg); |
899 | 0 | } |