chiark / gitweb /
Upgrade licence to GPLv3+.
[tripe] / server / admin.c
... / ...
CommitLineData
1/* -*-c-*-
2 *
3 * Admin interface for configuration
4 *
5 * (c) 2001 Straylight/Edgeware
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of Trivial IP Encryption (TrIPE).
11 *
12 * TrIPE is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
16 *
17 * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26/*----- Header files ------------------------------------------------------*/
27
28#include "tripe.h"
29
30/*----- Global variables --------------------------------------------------*/
31
32#ifndef NTRACE
33
34const trace_opt tr_opts[] = {
35 { 't', T_TUNNEL, "tunnel events" },
36 { 'r', T_PEER, "peer events" },
37 { 'a', T_ADMIN, "admin interface" },
38 { 's', T_KEYSET, "symmetric keyset management" },
39 { 'x', T_KEYEXCH, "key exchange" },
40 { 'm', T_KEYMGMT, "key management" },
41 { 'l', T_CHAL, "challenge management" },
42 { 'v', T_PRIVSEP, "privilege separation" },
43 { 'p', T_PACKET, "packet contents" },
44 { 'c', T_CRYPTO, "crypto details" },
45 { 'A', T_ALL, "all of the above" },
46 { 0, 0, 0 }
47};
48
49unsigned tr_flags = 0;
50#endif
51
52static const trace_opt w_opts[] = {
53#ifndef NTRACE
54 { 't', AF_TRACE, "trace messages" },
55#endif
56 { 'n', AF_NOTE, "asynchronous notifications" },
57 { 'w', AF_WARN, "warnings" },
58 { 'A', AF_ALLMSGS, "all of the above" },
59 { 0, 0, 0 }
60};
61
62/*----- Static variables --------------------------------------------------*/
63
64static admin *admins;
65static admin *a_dead;
66static sel_file sock;
67static const char *sockname;
68static sym_table a_svcs;
69static unsigned flags = 0;
70static admin *a_stdin = 0;
71static sig s_term, s_int, s_hup;
72
73#define F_DAEMON 1u
74#define F_INIT 2u
75#define F_FOREGROUND 4u
76
77#define T_RESOLVE SEC(30)
78#define T_PING SEC(5)
79
80static void a_destroy(admin */*a*/);
81
82#define BOOL(x) ((x) ? "t" : "nil")
83
84/*----- Output functions --------------------------------------------------*/
85
86/* --- @trywrite@ --- *
87 *
88 * Arguments: @admin *a@ = pointer to an admin block
89 * @const char *p@ = pointer to buffer to write
90 * @size_t sz@ = size of data to write
91 *
92 * Returns: The number of bytes written, or less than zero on error.
93 *
94 * Use: Attempts to write data to a client.
95 */
96
97static ssize_t trywrite(admin *a, const char *p, size_t sz)
98{
99 ssize_t n, done = 0;
100
101again:
102 if (!sz)
103 return (done);
104 n = write(a->w.fd, p, sz);
105 if (n > 0) {
106 done += n;
107 p += n;
108 sz -= n;
109 goto again;
110 }
111 if (n < 0) {
112 if (errno == EINTR)
113 goto again;
114 if (errno != EAGAIN && errno != EWOULDBLOCK) {
115 a_destroy(a);
116 a_warn("ADMIN", "client-write-error", "?ERRNO", A_END);
117 return (-1);
118 }
119 }
120 return (done);
121}
122
123/* --- @doqueue@ -- *
124 *
125 * Arguments: @oqueue *q@ = pointer to output queue
126 * @const char *p@ = pointer to buffer to write
127 * @size_t sz@ = size of buffer
128 *
129 * Returns: Nonzero if the queue was previously empty.
130 *
131 * Use: Queues data to be written later.
132 */
133
134static int doqueue(oqueue *q, const char *p, size_t sz)
135{
136 obuf *o;
137 int rc = 0;
138 size_t n;
139
140 o = q->tl;
141 if (!o)
142 rc = 1;
143 else if (o->p_in < o->buf + OBUFSZ)
144 goto noalloc;
145
146 do {
147 o = xmalloc(sizeof(obuf));
148 o->next = 0;
149 o->p_in = o->p_out = o->buf;
150 if (q->tl)
151 q->tl->next = o;
152 else
153 q->hd = o;
154 q->tl = o;
155
156 noalloc:
157 n = o->buf + OBUFSZ - o->p_in;
158 if (n > sz)
159 n = sz;
160 memcpy(o->p_in, p, n);
161 o->p_in += n;
162 p += n;
163 sz -= n;
164 } while (sz);
165
166 return (rc);
167}
168
169/* --- @dosend@ --- *
170 *
171 * Arguemnts: @admin *a@ = pointer to an admin block
172 * @const char *p@ = pointer to buffer to write
173 * @size_t sz@ = size of data to write
174 *
175 * Returns: ---
176 *
177 * Use: Sends data to an admin client.
178 */
179
180static void dosend(admin *a, const char *p, size_t sz)
181{
182 ssize_t n;
183
184 if (a->f & AF_DEAD)
185 return;
186 if (!a->out.hd) {
187 if ((n = trywrite(a, p, sz)) < 0)
188 return;
189 p += n;
190 sz -= n;
191 if (!sz)
192 return;
193 }
194 if (doqueue(&a->out, p, sz))
195 sel_addfile(&a->w);
196}
197
198/* --- @a_flush@ --- *
199 *
200 * Arguments: @int fd@ = file descriptor
201 * @unsigned mode@ = what's happening
202 * @void *v@ = pointer to my admin block
203 *
204 * Returns: ---
205 *
206 * Use: Flushes buffers when a client is ready to read again.
207 */
208
209static void a_flush(int fd, unsigned mode, void *v)
210{
211 admin *a = v;
212 obuf *o, *oo;
213 ssize_t n;
214
215 o = a->out.hd;
216 while (o) {
217 if ((n = trywrite(a, o->p_out, o->p_in - o->p_out)) < 0)
218 return;
219 o->p_out += n;
220 if (o->p_in < o->p_out)
221 break;
222 oo = o;
223 o = o->next;
224 xfree(oo);
225 }
226 a->out.hd = o;
227 if (!o) {
228 a->out.tl = 0;
229 sel_rmfile(&a->w);
230 }
231}
232
233/*----- Utility functions -------------------------------------------------*/
234
235/* --- @a_vformat@ --- *
236 *
237 * Arguments: @dstr *d@ = where to leave the formatted message
238 * @const char *fmt@ = pointer to format string
239 * @va_list *ap@ = arguments in list
240 *
241 * Returns: ---
242 *
243 * Use: Main message token formatting driver. The arguments are
244 * interleaved formatting tokens and their parameters, finally
245 * terminated by an entry @A_END@.
246 *
247 * Tokens recognized:
248 *
249 * * "*..." ... -- pretokenized @dstr_putf@-like string
250 *
251 * * "?ADDR" SOCKADDR -- a socket address, to be converted
252 *
253 * * "?B64" BUFFER SIZE -- binary data to be base64-encoded
254 *
255 * * "?TOKENS" VECTOR -- null-terminated vector of tokens
256 *
257 * * "?PEER" PEER -- peer's name
258 *
259 * * "?ERRNO" ERRNO -- system error code
260 *
261 * * "[!]..." ... -- @dstr_putf@-like string as single token
262 */
263
264void a_vformat(dstr *d, const char *fmt, va_list *ap)
265{
266 dstr dd = DSTR_INIT;
267
268 while (fmt) {
269 if (*fmt == '*') {
270 if (d->len) dstr_putc(d, ' ');
271 dstr_vputf(d, fmt + 1, ap);
272 } else if (*fmt == '?') {
273 if (strcmp(fmt, "?ADDR") == 0) {
274 const addr *a = va_arg(*ap, const addr *);
275 switch (a->sa.sa_family) {
276 case AF_INET:
277 u_quotify(d, "INET");
278 u_quotify(d, inet_ntoa(a->sin.sin_addr));
279 dstr_putf(d, " %u", (unsigned)ntohs(a->sin.sin_port));
280 break;
281 default:
282 abort();
283 }
284 } else if (strcmp(fmt, "?B64") == 0) {
285 const octet *p = va_arg(*ap, const octet *);
286 size_t n = va_arg(*ap, size_t);
287 base64_ctx b64;
288 dstr_putc(d, ' ');
289 base64_init(&b64);
290 b64.indent = "";
291 b64.maxline = 0;
292 base64_encode(&b64, p, n, d);
293 base64_encode(&b64, 0, 0, d);
294 while (d->len && d->buf[d->len - 1] == '=') d->len--;
295 } else if (strcmp(fmt, "?TOKENS") == 0) {
296 const char *const *av = va_arg(*ap, const char *const *);
297 while (*av) u_quotify(d, *av++);
298 } else if (strcmp(fmt, "?PEER") == 0)
299 u_quotify(d, p_name(va_arg(*ap, peer *)));
300 else if (strcmp(fmt, "?ERRNO") == 0) {
301 dstr_putf(d, " E%d", errno);
302 u_quotify(d, strerror(errno));
303 } else
304 abort();
305 } else {
306 if (*fmt == '!') fmt++;
307 DRESET(&dd);
308 dstr_vputf(&dd, fmt, ap);
309 u_quotify(d, dd.buf);
310 }
311 fmt = va_arg(*ap, const char *);
312 }
313 dstr_putz(d);
314
315 dstr_destroy(&dd);
316}
317
318/* --- @a_format@ --- *
319 *
320 * Arguments: @dstr *d@ = where to leave the formatted message
321 * @const char *fmt@ = pointer to format string
322 *
323 * Returns: ---
324 *
325 * Use: Writes a tokenized message into a string, for later
326 * presentation.
327 */
328
329void a_format(dstr *d, const char *fmt, ...)
330{
331 va_list ap;
332
333 va_start(ap, fmt);
334 a_vformat(d, fmt, &ap);
335 va_end(ap);
336}
337
338/* --- @a_write@, @a_vwrite@ --- *
339 *
340 * Arguments: @admin *a@ = admin connection to write to
341 * @const char *status@ = status code to report
342 * @const char *tag@ = tag string, or null
343 * @const char *fmt@ = pointer to format string
344 * @va_list *ap@ = arguments in list
345 * @...@ = other arguments
346 *
347 * Returns: ---
348 *
349 * Use: Sends a message to an admin connection.
350 */
351
352static void a_vwrite(admin *a, const char *status, const char *tag,
353 const char *fmt, va_list *ap)
354{
355 dstr d = DSTR_INIT;
356
357 if (tag) dstr_puts(&d, "BG");
358 dstr_puts(&d, status);
359 if (tag) u_quotify(&d, tag);
360 a_vformat(&d, fmt, ap);
361 dstr_putc(&d, '\n');
362 dosend(a, d.buf, d.len);
363 dstr_destroy(&d);
364}
365
366static void a_write(admin *a, const char *status, const char *tag,
367 const char *fmt, ...)
368{
369 va_list ap;
370
371 va_start(ap, fmt);
372 a_vwrite(a, status, tag, fmt, &ap);
373 va_end(ap);
374}
375
376/* --- @a_ok@, @a_fail@ --- *
377 *
378 * Arguments: @admin *a@ = connection
379 * @const char *fmt@ = format string
380 * @...@ = other arguments
381 *
382 * Returns: ---
383 *
384 * Use: Convenience functions for @a_write@.
385 */
386
387static void a_ok(admin *a) { a_write(a, "OK", 0, A_END); }
388
389static void a_fail(admin *a, const char *fmt, ...)
390{
391 va_list ap;
392
393 va_start(ap, fmt);
394 a_vwrite(a, "FAIL", 0, fmt, &ap);
395 va_end(ap);
396}
397
398/* --- @a_info@ --- *
399 *
400 * Arguments: @admin *a@ = connection
401 * @const char *fmt@ = format string
402 * @...@ = other arguments
403 *
404 * Returns: ---
405 *
406 * Use: Report information to an admin client.
407 */
408
409void a_info(admin *a, const char *fmt, ...)
410{
411 va_list ap;
412
413 va_start(ap, fmt);
414 a_vwrite(a, "INFO", 0, fmt, &ap);
415 va_end(ap);
416}
417
418/* --- @a_alert@, @a_valert@, @a_rawalert@ --- *
419 *
420 * Arguments: @unsigned f_and, f_eq@ = filter for connections
421 * @const char *status@ = status string
422 * @const char *fmt@ = pointer to format string
423 * @const char *p@ = pointer to raw string
424 * @size_t sz@ = size of raw string
425 * @va_list *ap@ = arguments in list
426 * @...@ = other arguments
427 *
428 * Returns: ---
429 *
430 * Use: Write a message to all admin connections matched by the given
431 * filter.
432 */
433
434static void a_rawalert(unsigned f_and, unsigned f_eq, const char *status,
435 const char *p, size_t sz)
436{
437 admin *a, *aa;
438 dstr d = DSTR_INIT;
439
440 if (!(flags & F_INIT))
441 return;
442 dstr_puts(&d, status);
443 if (p) {
444 dstr_putc(&d, ' ');
445 dstr_putm(&d, p, sz);
446 }
447 dstr_putc(&d, '\n');
448 p = d.buf;
449 sz = d.len;
450 for (a = admins; a; a = aa) {
451 aa = a->next;
452 if ((a->f & f_and) == f_eq)
453 dosend(a, d.buf, d.len);
454 }
455 dstr_destroy(&d);
456}
457
458static void a_valert(unsigned f_and, unsigned f_eq, const char *status,
459 const char *fmt, va_list *ap)
460{
461 dstr d = DSTR_INIT;
462
463 if (!(flags & F_INIT))
464 return;
465 a_vformat(&d, fmt, ap);
466 a_rawalert(f_and, f_eq, status, fmt ? d.buf : 0, fmt ? d.len : 0);
467 dstr_destroy(&d);
468}
469
470static void a_alert(unsigned f_and, unsigned f_eq, const char *status,
471 const char *fmt, ...)
472{
473 va_list ap;
474
475 va_start(ap, fmt);
476 a_valert(f_and, f_eq, status, fmt, &ap);
477 va_end(ap);
478}
479
480/* --- @a_warn@ --- *
481 *
482 * Arguments: @const char *fmt@ = pointer to format string
483 * @...@ = other arguments
484 *
485 * Returns: ---
486 *
487 * Use: Informs all admin connections of a warning.
488 */
489
490void a_warn(const char *fmt, ...)
491{
492 va_list ap;
493
494 va_start(ap, fmt);
495 if (flags & F_INIT)
496 a_valert(0, 0, "WARN", fmt, &ap);
497 else {
498 dstr d = DSTR_INIT;
499 fprintf(stderr, "%s: ", QUIS);
500 a_vformat(&d, fmt, &ap);
501 dstr_putc(&d, '\n');
502 dstr_write(&d, stderr);
503 dstr_destroy(&d);
504 }
505 va_end(ap);
506}
507
508/* --- @a_trace@ --- *
509 *
510 * Arguments: @const char *p@ = pointer to a buffer
511 * @size_t sz@ = size of the buffer
512 * @void *v@ = uninteresting pointer
513 *
514 * Returns: ---
515 *
516 * Use: Custom trace output handler. Sends trace messages to
517 * interested admin connections.
518 */
519
520#ifndef NTRACE
521static void a_trace(const char *p, size_t sz, void *v)
522 { a_rawalert(AF_TRACE, AF_TRACE, "TRACE", p, sz); }
523#endif
524
525/* --- @a_notify@ --- *
526 *
527 * Arguments: @const char *fmt@ = pointer to format string
528 * @...@ = other arguments
529 *
530 * Returns: ---
531 *
532 * Use: Sends a notification to interested admin connections.
533 */
534
535void a_notify(const char *fmt, ...)
536{
537 va_list ap;
538
539 va_start(ap, fmt);
540 a_valert(AF_NOTE, AF_NOTE, "NOTE", fmt, &ap);
541 va_end(ap);
542}
543
544/* --- @a_quit@ --- *
545 *
546 * Arguments: ---
547 *
548 * Returns: ---
549 *
550 * Use: Shuts things down nicely.
551 */
552
553void a_quit(void)
554{
555 close(sock.fd);
556 unlink(sockname);
557 FOREACH_PEER(p, { p_destroy(p); });
558 ps_quit();
559 exit(0);
560}
561
562/* --- @a_sigdie@ --- *
563 *
564 * Arguments: @int sig@ = signal number
565 * @void *v@ = an uninteresting argument
566 *
567 * Returns: ---
568 *
569 * Use Shuts down on receipt of a fatal signal.
570 */
571
572static void a_sigdie(int sig, void *v)
573{
574 char *p;
575 char buf[20];
576
577 switch (sig) {
578 case SIGTERM: p = "SIGTERM"; break;
579 case SIGINT: p = "SIGINT"; break;
580 default:
581 sprintf(buf, "%i", sig);
582 p = buf;
583 break;
584 }
585 a_warn("SERVER", "quit", "signal", "%s", p, A_END);
586 a_quit();
587}
588
589/* --- @a_sighup@ --- *
590 *
591 * Arguments: @int sig@ = signal number
592 * @void *v@ = an uninteresting argument
593 *
594 * Returns: ---
595 *
596 * Use Logs a message about SIGHUP not being useful.
597 */
598
599static void a_sighup(int sig, void *v)
600 { a_warn("SERVER", "ignore", "signal", "SIGHUP", A_END); }
601
602/* --- @a_parsetime@ --- *
603 *
604 * Arguments; @const char *p@ = time string to parse
605 *
606 * Returns: Time in seconds, or @< 0@ on error.
607 */
608
609static long a_parsetime(const char *p)
610{
611 char *q;
612 long t = strtol(p, &q, 0);
613
614 switch (*q) {
615 case 'd': t *= 24;
616 case 'h': t *= 60;
617 case 'm': t *= 60;
618 case 's': if (q[1] != 0)
619 default: t = -1;
620 case 0: break;
621 }
622 return (t);
623}
624
625/* --- @a_findpeer@ --- *
626 *
627 * Arguments: @admin *a@ = admin connection
628 * @const char *pn@ = peer name
629 *
630 * Returns: The peer, or null if not there.
631 *
632 * Use: Finds a peer, reporting an error if it failed.
633 */
634
635static peer *a_findpeer(admin *a, const char *pn)
636{
637 peer *p;
638
639 if ((p = p_find(pn)) == 0)
640 a_fail(a, "unknown-peer", "%s", pn, A_END);
641 return (p);
642}
643
644/*----- Backgrounded operations -------------------------------------------*/
645
646#define BGTAG(bg) \
647 (((admin_bgop *)(bg))->tag ? ((admin_bgop *)(bg))->tag : "<foreground>")
648
649/* --- @a_bgfind@ --- *
650 *
651 * Arguments: @admin *a@ = a client block
652 * @const char *tag@ = the requested tag
653 *
654 * Returns: The requested background job, or null.
655 */
656
657static admin_bgop *a_bgfind(admin *a, const char *tag)
658{
659 admin_bgop *bg;
660
661 for (bg = a->bg; bg; bg = bg->next) {
662 if (bg->tag && strcmp(tag, bg->tag) == 0)
663 return (bg);
664 }
665 return (0);
666}
667
668/* --- @a_bgrelease@ --- *
669 *
670 * Arguments: @admin_bgop *bg@ = backgrounded operation
671 *
672 * Returns: ---
673 *
674 * Use: Removes a backgrounded operation from the queue, since
675 * (presumably) it's done.
676 */
677
678static void a_bgrelease(admin_bgop *bg)
679{
680 admin *a = bg->a;
681
682 T( trace(T_ADMIN, "admin: release bgop %s", BGTAG(bg)); )
683 if (bg->tag) xfree(bg->tag);
684 else selbuf_enable(&a->b);
685 if (bg->next) bg->next->prev = bg->prev;
686 if (bg->prev) bg->prev->next = bg->next;
687 else a->bg = bg->next;
688 xfree(bg);
689 if (!a->bg && (a->f & AF_CLOSE)) a_destroy(a);
690}
691
692/* --- @a_bgok@, @a_bginfo@, @a_bgfail@ --- *
693 *
694 * Arguments: @admin_bgop *bg@ = backgrounded operation
695 * @const char *fmt@ = format string
696 * @...@ = other arguments
697 *
698 * Returns: ---
699 *
700 * Use: Convenience functions for @a_write@.
701 */
702
703static void a_bgok(admin_bgop *bg)
704 { a_write(bg->a, "OK", bg->tag, A_END); }
705
706static void a_bginfo(admin_bgop *bg, const char *fmt, ...)
707{
708 va_list ap;
709 va_start(ap, fmt);
710 a_vwrite(bg->a, "INFO", bg->tag, fmt, &ap);
711 va_end(ap);
712}
713
714static void a_bgfail(admin_bgop *bg, const char *fmt, ...)
715{
716 va_list ap;
717 va_start(ap, fmt);
718 a_vwrite(bg->a, "FAIL", bg->tag, fmt, &ap);
719 va_end(ap);
720}
721
722/* --- @a_bgadd@ --- *
723 *
724 * Arguments: @admin *a@ = administration connection
725 * @admin_bgop *bg@ = pointer to background operation
726 * @const char *tag@ = background tag, or null for foreground
727 * @void (*cancel)(admin_bgop *)@ = cancel function
728 *
729 * Returns: Zero for success, nonzero on failure.
730 *
731 * Use: Links a background job into the list.
732 */
733
734static int a_bgadd(admin *a, admin_bgop *bg, const char *tag,
735 void (*cancel)(admin_bgop *))
736{
737 if (tag) {
738 if (a_bgfind(a, tag)) {
739 a_fail(a, "tag-exists", "%s", tag, A_END);
740 return (-1);
741 }
742 bg->tag = xstrdup(tag);
743 }
744 else {
745 bg->tag = 0;
746 selbuf_disable(&a->b);
747 }
748 bg->a = a;
749 bg->cancel = cancel;
750 bg->next = a->bg;
751 bg->prev = 0;
752 if (a->bg) a->bg->prev = bg;
753 a->bg = bg;
754 T( trace(T_ADMIN, "admin: add bgop %s", BGTAG(bg)); )
755 if (tag) a_write(a, "DETACH", tag, A_END);
756 return (0);
757}
758
759/*----- Job table manipulation --------------------------------------------*/
760
761#define JOB_SHIFT 16
762#define JOB_INDEXMASK ((1ul << JOB_SHIFT) - 1)
763#define JOB_SEQMASK ((1ul << (32 - JOB_SHIFT)) - 1)
764
765#define JOB_END 0xfffffffful
766
767static unsigned long a_joboffset;
768
769/* --- @a_jobidencode@ --- *
770 *
771 * Arguments: @admin_svcop *svc@ = pointer to a service operation
772 *
773 * Returns: A jobid for this job, in an internal static buffer.
774 *
775 * Use: Constructs a jobid. In order to dissuade people from
776 * predicting jobids, we obfuscate them.
777 *
778 * A `raw' jobid consists of two 16-bit fields. The low 16 bits
779 * are an index into a big array. The high 16 bits are a
780 * sequence number recording how many times that slot has been
781 * reused.
782 *
783 * This `raw' jobid is then obfuscated by adding a randomly-
784 * generated offset, and multiplying (mod %$2^{32}$%) by a fixed
785 * odd constant.
786 */
787
788static const char *a_jobidencode(admin_svcop *svc)
789{
790 admin_jobtable *j = &svc->prov->j;
791 static char buf[10];
792 unsigned long pre;
793 unsigned seq;
794
795 assert(svc->index <= JOB_INDEXMASK);
796 seq = j->v[svc->index].seq;
797 assert(seq <= JOB_SEQMASK);
798 pre = (unsigned long)svc->index | ((unsigned long)seq << JOB_SHIFT);
799 sprintf(buf, "J%08lx", ((pre + a_joboffset) * 0x0f87a7a3ul) & 0xffffffff);
800 return (buf);
801}
802
803/* --- @a_jobiddecode@ --- *
804 *
805 * Arguments: @admin_jobtable *j@ = pointer to a job table
806 * @const char *jid@ = pointer to a jobid string
807 *
808 * Returns: A pointer to the job's @svcop@ structure.
809 */
810
811static admin_svcop *a_jobiddecode(admin_jobtable *j, const char *jid)
812{
813 unsigned i;
814 unsigned long pre;
815
816 if (jid[0] != 'J')
817 return (0);
818 for (i = 1; i < 9; i++) {
819 if (!isxdigit((unsigned char)jid[i]))
820 return (0);
821 }
822 if (jid[9] != 0)
823 return (0);
824 pre = strtoul(jid + 1, 0, 16);
825 pre = ((pre * 0xbd11c40bul) - a_joboffset) & 0xffffffff;
826 i = pre & JOB_INDEXMASK;
827 if (i >= j->n || j->v[i].seq != (pre >> JOB_SHIFT))
828 return (0);
829 return (j->v[i].u.op);
830}
831
832/* --- @a_jobcreate@ --- *
833 *
834 * Arguments: @admin *a@ = pointer to administration client
835 *
836 * Returns: A pointer to a freshly-allocated @svcop@, or null.
837 *
838 * Use: Allocates a fresh @svcop@ and links it into a job table.
839 */
840
841static admin_svcop *a_jobcreate(admin *a)
842{
843 admin_svcop *svc;
844 unsigned i;
845 unsigned sz;
846 admin_jobtable *j = &a->j;
847
848 if (j->free != JOB_END) {
849 i = j->free;
850 j->free = j->v[i].u.next;
851 } else {
852 if (j->n == j->sz) {
853 if (j->sz > JOB_INDEXMASK)
854 return (0);
855 sz = j->sz;
856 if (!sz) {
857 j->sz = 16;
858 j->v = xmalloc(j->sz * sizeof(*j->v));
859 } else {
860 j->sz = sz << 1;
861 j->v = xrealloc(j->v, j->sz * sizeof(*j->v), sz * sizeof(*j->v));
862 }
863 }
864 i = j->n++;
865 j->v[i].seq = 0;
866 }
867 svc = xmalloc(sizeof(*svc));
868 svc->index = i;
869 svc->prov = a;
870 svc->next = j->active;
871 svc->prev = 0;
872 if (j->active) j->active->prev = svc;
873 j->active = svc;
874 j->v[i].u.op = svc;
875 IF_TRACING(T_ADMIN, {
876 trace(T_ADMIN, "admin: created job %s (%u)", a_jobidencode(svc), i);
877 })
878 return (svc);
879}
880
881/* --- @a_jobdestroy@ --- *
882 *
883 * Arguments: @admin_svcop *svc@ = pointer to job block
884 *
885 * Returns: ---
886 *
887 * Use: Frees up a completed (or cancelled) job.
888 */
889
890static void a_jobdestroy(admin_svcop *svc)
891{
892 admin *a = svc->prov;
893 admin_jobtable *j = &a->j;
894 unsigned i = svc->index;
895
896 IF_TRACING(T_ADMIN, {
897 trace(T_ADMIN, "admin: destroying job %s (%u)", a_jobidencode(svc), i);
898 })
899 assert(j->v[i].u.op = svc);
900 j->v[i].u.next = j->free;
901 j->v[i].seq++;
902 j->free = i;
903 if (svc->next) svc->next->prev = svc->prev;
904 if (svc->prev) svc->prev->next = svc->next;
905 else j->active = svc->next;
906}
907
908/* --- @a_jobtableinit@ --- *
909 *
910 * Arguments: @admin_jobtable *j@ = pointer to job table
911 *
912 * Returns: ---
913 *
914 * Use: Initializes a job table.
915 */
916
917static void a_jobtableinit(admin_jobtable *j)
918{
919 if (!a_joboffset)
920 a_joboffset = GR_RANGE(&rand_global, 0xffffffff) + 1;
921 j->n = j->sz = 0;
922 j->active = 0;
923 j->free = JOB_END;
924 j->v = 0;
925}
926
927/* --- @a_jobtablefinal@ --- *
928 *
929 * Arguments: @admin_jobtable *j@ = pointer to job table
930 *
931 * Returns: ---
932 *
933 * Use: Closes down a job table.
934 */
935
936static void a_jobtablefinal(admin_jobtable *j)
937{
938 admin_svcop *svc, *ssvc;
939
940 for (svc = j->active; svc; svc = ssvc) {
941 ssvc = svc->next;
942 a_bgfail(&svc->bg, "provider-failed", A_END);
943 a_bgrelease(&svc->bg);
944 }
945 if (j->v) xfree(j->v);
946}
947
948/*----- Services infrastructure -------------------------------------------*/
949
950/* --- @a_svcfind@ --- *
951 *
952 * Arguments: @admin *a@ = the requesting client
953 * @const char *name@ = service name wanted
954 *
955 * Returns: The service requested, or null.
956 *
957 * Use: Finds a service; reports an error if the service couldn't be
958 * found.
959 */
960
961static admin_service *a_svcfind(admin *a, const char *name)
962{
963 admin_service *svc;
964
965 if ((svc = sym_find(&a_svcs, name, -1, 0, 0)) == 0) {
966 a_fail(a, "unknown-service", "%s", name, A_END);
967 return (0);
968 }
969 return (svc);
970}
971
972/* --- @a_svcunlink@ --- *
973 *
974 * Arguments: @admin_service *svc@ = pointer to service structure
975 *
976 * Returns: ---
977 *
978 * Use: Unlinks the service from its provider, without issuing a
979 * message or freeing the structure. The version string is
980 * freed, however.
981 */
982
983static void a_svcunlink(admin_service *svc)
984{
985 if (svc->next)
986 svc->next->prev = svc->prev;
987 if (svc->prev)
988 svc->prev->next = svc->next;
989 else
990 svc->prov->svcs = svc->next;
991 xfree(svc->version);
992}
993
994/* --- @a_svcrelease@ --- *
995 *
996 * Arguments: @admin_service *svc@ = pointer to service structure
997 *
998 * Returns: ---
999 *
1000 * Use: Releases a service and frees its structure.
1001 */
1002
1003static void a_svcrelease(admin_service *svc)
1004{
1005 a_notify("SVCRELEASE", "%s", SYM_NAME(svc), A_END);
1006 a_svcunlink(svc);
1007 sym_remove(&a_svcs, svc);
1008}
1009
1010/*----- Name resolution operations ----------------------------------------*/
1011
1012/* --- @a_resolved@ --- *
1013 *
1014 * Arguments: @struct hostent *h@ = pointer to resolved hostname
1015 * @void *v@ = pointer to resolver operation
1016 *
1017 * Returns: ---
1018 *
1019 * Use: Handles a completed name resolution.
1020 */
1021
1022static void a_resolved(struct hostent *h, void *v)
1023{
1024 admin_resop *r = v;
1025
1026 T( trace(T_ADMIN, "admin: resop %s resolved", BGTAG(r)); )
1027 QUICKRAND;
1028 if (!h) {
1029 a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
1030 r->func(r, ARES_FAIL);
1031 } else {
1032 memcpy(&r->sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
1033 r->func(r, ARES_OK);
1034 }
1035 sel_rmtimer(&r->t);
1036 xfree(r->addr);
1037 a_bgrelease(&r->bg);
1038}
1039
1040/* --- @a_restimer@ --- *
1041 *
1042 * Arguments: @struct timeval *tv@ = timer
1043 * @void *v@ = pointer to resolver operation
1044 *
1045 * Returns: ---
1046 *
1047 * Use: Times out a resolver.
1048 */
1049
1050static void a_restimer(struct timeval *tv, void *v)
1051{
1052 admin_resop *r = v;
1053
1054 T( trace(T_ADMIN, "admin: resop %s timeout", BGTAG(r)); )
1055 a_bgfail(&r->bg, "resolver-timeout", "%s", r->addr, A_END);
1056 r->func(r, ARES_FAIL);
1057 bres_abort(&r->r);
1058 xfree(r->addr);
1059 a_bgrelease(&r->bg);
1060}
1061
1062/* --- @a_rescancel@ --- *
1063 *
1064 * Arguments: @admin_bgop *bg@ = background operation
1065 *
1066 * Returns: ---
1067 *
1068 * Use: Cancels an add operation.
1069 */
1070
1071static void a_rescancel(admin_bgop *bg)
1072{
1073 admin_resop *r = (admin_resop *)bg;
1074
1075 T( trace(T_ADMIN, "admin: cancel resop %s", BGTAG(r)); )
1076 r->func(r, ARES_FAIL);
1077 sel_rmtimer(&r->t);
1078 xfree(r->addr);
1079 bres_abort(&r->r);
1080}
1081
1082/* --- @a_resolve@ --- *
1083 *
1084 * Arguments: @admin *a@ = administration connection
1085 * @admin_resop *r@ = resolver operation to run
1086 * @const char *tag@ = background operation tag
1087 * @void (*func)(struct admin_resop *, int@ = handler function
1088 * @unsigned ac@ = number of remaining arguments
1089 * @char *av[]@ = pointer to remaining arguments
1090 *
1091 * Returns: ---
1092 *
1093 * Use: Cranks up a resolver job.
1094 */
1095
1096static void a_resolve(admin *a, admin_resop *r, const char *tag,
1097 void (*func)(struct admin_resop *, int),
1098 unsigned ac, char *av[])
1099{
1100 struct timeval tv;
1101 unsigned long pt;
1102 char *p;
1103 int i = 0;
1104
1105 /* --- Fill in the easy bits of address --- */
1106
1107 r->bg.tag = "<starting>";
1108 r->addr = 0;
1109 r->func = func;
1110 if (mystrieq(av[i], "inet")) i++;
1111 if (ac - i != 1 && ac - i != 2) {
1112 a_fail(a, "bad-addr-syntax", "[inet] ADDRESS [PORT]", A_END);
1113 goto fail;
1114 }
1115 r->sa.sin.sin_family = AF_INET;
1116 r->sasz = sizeof(r->sa.sin);
1117 r->addr = xstrdup(av[i]);
1118 if (!av[i + 1])
1119 pt = TRIPE_PORT;
1120 else {
1121 pt = strtoul(av[i + 1], &p, 0);
1122 if (*p) {
1123 struct servent *s = getservbyname(av[i + 1], "udp");
1124 if (!s) {
1125 a_fail(a, "unknown-service", "%s", av[i + 1], A_END);
1126 goto fail;
1127 }
1128 pt = ntohs(s->s_port);
1129 }
1130 }
1131 if (pt == 0 || pt >= 65536) {
1132 a_fail(a, "invalid-port", "%lu", pt, A_END);
1133 goto fail;
1134 }
1135 r->sa.sin.sin_port = htons(pt);
1136
1137 /* --- Report backgrounding --- *
1138 *
1139 * Do this for consistency of interface, even if we're going to get the
1140 * answer straight away.
1141 */
1142
1143 if (a_bgadd(a, &r->bg, tag, a_rescancel))
1144 goto fail;
1145 T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s'",
1146 a->seq, BGTAG(r), r->addr); )
1147
1148 /* --- If the name is numeric, do it the easy way --- */
1149
1150 if (inet_aton(av[i], &r->sa.sin.sin_addr)) {
1151 T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
1152 func(r, ARES_OK);
1153 xfree(r->addr);
1154 a_bgrelease(&r->bg);
1155 return;
1156 }
1157
1158 /* --- Store everything for later and crank up the resolver --- */
1159
1160 gettimeofday(&tv, 0);
1161 tv.tv_sec += T_RESOLVE;
1162 sel_addtimer(&sel, &r->t, &tv, a_restimer, r);
1163 bres_byname(&r->r, r->addr, a_resolved, r);
1164 return;
1165
1166fail:
1167 func(r, ARES_FAIL);
1168 if (r->addr) xfree(r->addr);
1169 xfree(r);
1170}
1171
1172/*----- Option parsing ----------------------------------------------------*/
1173
1174#define OPTIONS(argc, argv, guts) do { \
1175 char **o_av = argv; \
1176 for (;; o_av++) { \
1177 if (!*o_av) \
1178 break; \
1179 if (mystrieq(*o_av, "--")) { \
1180 o_av++; \
1181 break; \
1182 } \
1183 guts \
1184 if (**o_av == '-') \
1185 goto bad_syntax; \
1186 break; \
1187 } \
1188 argc -= o_av - argv; \
1189 argv = o_av; \
1190} while (0)
1191
1192#define OPT(name, guts) if (mystrieq(*o_av, name)) { guts continue; }
1193
1194#define OPTARG(name, arg, guts) OPT(name, { \
1195 const char *arg; \
1196 arg = *++o_av; \
1197 if (!arg) goto bad_syntax; \
1198 guts \
1199})
1200
1201#define OPTTIME(name, arg, guts) OPTARG(name, o_arg, { \
1202 long arg; \
1203 if ((arg = a_parsetime(o_arg)) < 0) { \
1204 a_fail(a, "bad-time-spec", "%s", o_arg, A_END); \
1205 goto fail; \
1206 } \
1207 guts \
1208})
1209
1210/*----- Adding peers ------------------------------------------------------*/
1211
1212/* --- @a_doadd@ --- *
1213 *
1214 * Arguments: @admin_resop *r@ = resolver operation
1215 * @int rc@ = how it worked
1216 *
1217 * Returns: ---
1218 *
1219 * Use: Handles a completed resolution.
1220 */
1221
1222static void a_doadd(admin_resop *r, int rc)
1223{
1224 admin_addop *add = (admin_addop *)r;
1225
1226 T( trace(T_ADMIN, "admin: done add op %s", BGTAG(add)); )
1227
1228 if (rc == ARES_OK) {
1229 add->peer.sasz = add->r.sasz;
1230 add->peer.sa = add->r.sa;
1231 if (p_findbyaddr(&add->r.sa))
1232 a_bgfail(&add->r.bg, "peer-addr-exists", "?ADDR", &add->r.sa, A_END);
1233 else if (p_find(add->peer.name))
1234 a_bgfail(&add->r.bg, "peer-exists", "%s", add->peer.name, A_END);
1235 else if (!p_create(&add->peer))
1236 a_bgfail(&add->r.bg, "peer-create-fail", "%s", add->peer.name, A_END);
1237 else
1238 a_bgok(&add->r.bg);
1239 }
1240
1241 if (add->peer.tag) xfree(add->peer.tag);
1242 xfree(add->peer.name);
1243}
1244
1245/* --- @acmd_add@ --- *
1246 *
1247 * Arguments: @admin *a@ = connection which requested the addition
1248 * @unsigned ac@ = argument count
1249 * @char *av[]@ = pointer to the argument list
1250 *
1251 * Returns: ---
1252 *
1253 * Use: Adds a new peer.
1254 */
1255
1256static void acmd_add(admin *a, unsigned ac, char *av[])
1257{
1258 const char *tag = 0;
1259 admin_addop *add;
1260
1261 /* --- Set stuff up --- */
1262
1263 add = xmalloc(sizeof(*add));
1264 add->peer.name = 0;
1265 add->peer.tag = 0;
1266 add->peer.privtag = 0;
1267 add->peer.t_ka = 0;
1268 add->peer.tops = tun_default;
1269 add->peer.f = 0;
1270
1271 /* --- Parse options --- */
1272
1273 OPTIONS(ac, av, {
1274 OPTARG("-background", arg, { tag = arg; })
1275 OPTARG("-tunnel", arg, {
1276 unsigned i;
1277 for (i = 0;; i++) {
1278 if (!tunnels[i]) {
1279 a_fail(a, "unknown-tunnel", "%s", arg, A_END);
1280 goto fail;
1281 }
1282 if (mystrieq(arg, tunnels[i]->name)) {
1283 add->peer.tops = tunnels[i];
1284 break;
1285 }
1286 }
1287 })
1288 OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
1289 OPT("-cork", { add->peer.f |= KXF_CORK; })
1290 OPTARG("-key", arg, {
1291 if (add->peer.tag)
1292 xfree(add->peer.tag);
1293 add->peer.tag = xstrdup(arg);
1294 })
1295 OPT("-mobile", { add->peer.f |= PSF_MOBILE; })
1296 OPTARG("-priv", arg, {
1297 if (add->peer.privtag)
1298 xfree(add->peer.privtag);
1299 add->peer.privtag = xstrdup(arg);
1300 })
1301 });
1302
1303 /* --- Make sure someone's not got there already --- */
1304
1305 if (!av[0] || !av[1])
1306 goto bad_syntax;
1307 if (p_find(*av)) {
1308 a_fail(a, "peer-exists", "%s", *av, A_END);
1309 goto fail;
1310 }
1311 add->peer.name = xstrdup(*av++);
1312 ac--;
1313
1314 /* --- Crank up the resolver --- */
1315
1316 a_resolve(a, &add->r, tag, a_doadd, ac, av);
1317 return;
1318
1319 /* --- Clearing up --- */
1320
1321bad_syntax:
1322 a_fail(a, "bad-syntax", "add", "[OPTIONS] PEER ADDR ...", A_END);
1323fail:
1324 if (add->peer.name) xfree(add->peer.name);
1325 if (add->peer.tag) xfree(add->peer.tag);
1326 if (add->peer.privtag) xfree(add->peer.privtag);
1327 xfree(add);
1328 return;
1329}
1330
1331/*----- Ping --------------------------------------------------------------*/
1332
1333/* --- @a_pingcancel@ --- *
1334 *
1335 * Arguments: @admin_bgop *bg@ = background operation block
1336 *
1337 * Returns: ---
1338 *
1339 * Use: Cancels a running ping.
1340 */
1341
1342static void a_pingcancel(admin_bgop *bg)
1343{
1344 admin_pingop *pg = (admin_pingop *)bg;
1345 T( trace(T_ADMIN, "admin: cancel ping op %s", BGTAG(pg)); )
1346 p_pingdone(&pg->ping, PING_NONOTIFY);
1347}
1348
1349/* --- @a_pong@ --- *
1350 *
1351 * Arguments: @int rc@ = return code
1352 * @void *v@ = ping operation block
1353 *
1354 * Returns: ---
1355 *
1356 * Use: Collects what happened to a ping message.
1357 */
1358
1359static void a_pong(int rc, void *v)
1360{
1361 admin_pingop *pg = v;
1362 struct timeval tv;
1363 double millis;
1364
1365 switch (rc) {
1366 case PING_OK:
1367 gettimeofday(&tv, 0);
1368 tv_sub(&tv, &tv, &pg->pingtime);
1369 millis = (double)tv.tv_sec * 1000 + (double)tv.tv_usec/1000;
1370 a_bginfo(&pg->bg, "ping-ok", "%.1f", millis, A_END);
1371 a_bgok(&pg->bg);
1372 break;
1373 case PING_TIMEOUT:
1374 a_bginfo(&pg->bg, "ping-timeout", A_END);
1375 a_bgok(&pg->bg);
1376 break;
1377 case PING_PEERDIED:
1378 a_bginfo(&pg->bg, "ping-peer-died", A_END);
1379 a_bgok(&pg->bg);
1380 break;
1381 default:
1382 abort();
1383 }
1384 T( trace(T_ADMIN, "admin: ponged ping op %s", BGTAG(pg)); )
1385 a_bgrelease(&pg->bg);
1386}
1387
1388/* --- @acmd_ping@, @acmd_eping@ --- *
1389 *
1390 * Arguments: @admin *a@ = connection which requested the ping
1391 * @unsigned ac@ = argument count
1392 * @char *av[]@ = pointer to the argument list
1393 *
1394 * Returns: ---
1395 *
1396 * Use: Pings a peer.
1397 */
1398
1399static void a_ping(admin *a, unsigned ac, char *av[],
1400 const char *cmd, unsigned msg)
1401{
1402 long t = T_PING;
1403 peer *p;
1404 admin_pingop *pg = 0;
1405 const char *tag = 0;
1406
1407 OPTIONS(ac, av, {
1408 OPTARG("-background", arg, { tag = arg; })
1409 OPTTIME("-timeout", arg, { t = arg; })
1410 });
1411 if (!*av || av[1]) goto bad_syntax;
1412 if ((p = a_findpeer(a, *av)) == 0)
1413 return;
1414 pg = xmalloc(sizeof(*pg));
1415 gettimeofday(&pg->pingtime, 0);
1416 if (a_bgadd(a, &pg->bg, tag, a_pingcancel))
1417 goto fail;
1418 T( trace(T_ADMIN, "admin: ping op %s: %s to %s",
1419 BGTAG(pg), cmd, p_name(p)); )
1420 if (p_pingsend(p, &pg->ping, msg, t, a_pong, pg)) {
1421 a_bgfail(&pg->bg, "ping-send-failed", A_END);
1422 a_bgrelease(&pg->bg);
1423 }
1424 return;
1425
1426bad_syntax:
1427 a_fail(a, "bad-syntax", "%s", cmd, "[OPTIONS] PEER", cmd, A_END);
1428fail:
1429 if (pg) xfree(pg);
1430 return;
1431}
1432
1433static void acmd_ping(admin *a, unsigned ac, char *av[])
1434 { a_ping(a, ac, av, "ping", MISC_PING); }
1435static void acmd_eping(admin *a, unsigned ac, char *av[])
1436 { a_ping(a, ac, av, "eping", MISC_EPING); }
1437
1438/*----- Service commands --------------------------------------------------*/
1439
1440static void acmd_svcclaim(admin *a, unsigned ac, char *av[])
1441{
1442 admin_service *svc;
1443 unsigned f;
1444
1445 svc = sym_find(&a_svcs, av[0], -1, sizeof(*svc), &f);
1446 if (f) {
1447 if (versioncmp(av[1], svc->version) <= 0) {
1448 a_fail(a,
1449 "service-exists",
1450 "%s", SYM_NAME(svc),
1451 "%s", svc->version,
1452 A_END);
1453 return;
1454 }
1455 a_write(svc->prov, "SVCCLAIM", 0, "%s", av[0], "%s", av[1], A_END);
1456 a_svcunlink(svc);
1457 }
1458 svc->prov = a;
1459 svc->version = xstrdup(av[1]);
1460 svc->next = a->svcs;
1461 svc->prev = 0;
1462 if (a->svcs) a->svcs->prev = svc;
1463 a->svcs = svc;
1464 a_notify("SVCCLAIM", "%s", SYM_NAME(svc), "%s", svc->version, A_END);
1465 a_ok(a);
1466}
1467
1468static void acmd_svcrelease(admin *a, unsigned ac, char *av[])
1469{
1470 admin_service *svc;
1471
1472 if ((svc = a_svcfind(a, av[0])) == 0)
1473 return;
1474 if (svc->prov != a) {
1475 a_fail(a, "not-service-provider", "%s", SYM_NAME(svc), A_END);
1476 return;
1477 }
1478 a_svcrelease(svc);
1479 a_ok(a);
1480}
1481
1482static void acmd_svcensure(admin *a, unsigned ac, char *av[])
1483{
1484 admin_service *svc;
1485
1486 if ((svc = a_svcfind(a, av[0])) == 0)
1487 return;
1488 if (av[1] && versioncmp(svc->version, av[1]) < 0) {
1489 a_fail(a, "service-too-old",
1490 "%s", SYM_NAME(svc),
1491 "%s", svc->version,
1492 A_END);
1493 return;
1494 }
1495 a_ok(a);
1496}
1497
1498static void acmd_svcquery(admin *a, unsigned ac, char *av[])
1499{
1500 admin_service *svc;
1501
1502 if ((svc = a_svcfind(a, av[0])) != 0) {
1503 a_info(a, "name=%s", SYM_NAME(svc), "version=%s", svc->version, A_END);
1504 a_ok(a);
1505 }
1506}
1507
1508static void acmd_svclist(admin *a, unsigned ac, char *av[])
1509{
1510 admin_service *svc;
1511 sym_iter i;
1512
1513 for (sym_mkiter(&i, &a_svcs); (svc = sym_next(&i)) != 0; )
1514 a_info(a, "%s", SYM_NAME(svc), "%s", svc->version, A_END);
1515 a_ok(a);
1516}
1517
1518static void a_canceljob(admin_bgop *bg)
1519{
1520 admin_svcop *svc = (admin_svcop *)bg;
1521
1522 a_write(svc->prov, "SVCCANCEL", 0, "%s", a_jobidencode(svc), A_END);
1523 a_jobdestroy(svc);
1524}
1525
1526static void acmd_svcsubmit(admin *a, unsigned ac, char *av[])
1527{
1528 const char *tag = 0;
1529 admin_service *svc;
1530 admin_svcop *svcop;
1531 const char *ver = 0;
1532 dstr d = DSTR_INIT;
1533
1534 OPTIONS(ac, av, {
1535 OPTARG("-background", arg, { tag = arg; })
1536 OPTARG("-version", arg, { ver = arg; })
1537 });
1538 if (!*av) goto bad_syntax;
1539 if ((svc = a_svcfind(a, *av)) == 0) goto fail;
1540 if (ver && versioncmp(svc->version, ver) < 0) {
1541 a_fail(a, "service-too-old",
1542 "%s", SYM_NAME(svc),
1543 "%s", svc->version,
1544 A_END);
1545 goto fail;
1546 }
1547 if ((svcop = a_jobcreate(svc->prov)) == 0) {
1548 a_fail(a, "provider-overloaded", A_END);
1549 goto fail;
1550 }
1551 if (a_bgadd(a, &svcop->bg, tag, a_canceljob)) {
1552 a_jobdestroy(svcop);
1553 goto fail;
1554 }
1555 a_write(svc->prov, "SVCJOB", 0,
1556 "%s", a_jobidencode(svcop),
1557 "?TOKENS", av,
1558 A_END);
1559 goto done;
1560
1561bad_syntax:
1562 a_fail(a, "bad-syntax", "svcsubmit", "[OPTIONS] SERVICE ARGS...", A_END);
1563fail:
1564done:
1565 dstr_destroy(&d);
1566 return;
1567}
1568
1569static void a_replycmd(admin *a, const char *status, int donep, char *av[])
1570{
1571 admin_svcop *svc;
1572
1573 if ((svc = a_jobiddecode(&a->j, av[0])) == 0) {
1574 a_fail(a, "unknown-jobid", av[0], A_END);
1575 return;
1576 }
1577 a_write(svc->bg.a, status, svc->bg.tag, "?TOKENS", av + 1, A_END);
1578 if (donep) {
1579 a_jobdestroy(svc);
1580 a_bgrelease(&svc->bg);
1581 }
1582 a_ok(a);
1583}
1584
1585static void acmd_svcok(admin *a, unsigned ac, char *av[])
1586 { a_replycmd(a, "OK", 1, av); }
1587static void acmd_svcinfo(admin *a, unsigned ac, char *av[])
1588 { a_replycmd(a, "INFO", 0, av); }
1589static void acmd_svcfail(admin *a, unsigned ac, char *av[])
1590 { a_replycmd(a, "FAIL", 1, av); }
1591
1592/*----- Administration commands -------------------------------------------*/
1593
1594/* --- Miscellaneous commands --- */
1595
1596/* --- @traceish@ --- *
1597 *
1598 * Arguments: @admin *a@ = connection to complain on
1599 * @unsigned ac@ = number of arguments
1600 * @char *av[]@ = vector of arguments
1601 * @const char *what@ = what we're messing with
1602 * @const trace_opt *tt@ = options table
1603 * @unsigned *ff@ = where the flags are
1604 *
1605 * Returns: Nonzero if anything changed.
1606 *
1607 * Use: Guts of trace-ish commands like `trace' and `watch'.
1608 */
1609
1610static int traceish(admin *a, unsigned ac, char *av[],
1611 const char *what, const trace_opt *tt, unsigned *ff)
1612{
1613 int ch = 0;
1614
1615 if (!ac || strcmp(av[0], "?") == 0) {
1616 const trace_opt *t;
1617 for (t = tt; t->ch; t++) {
1618 a_info(a, "*%c%c %s",
1619 t->ch, (*ff & t->f) == t->f ? '+' : ' ', t->help, A_END);
1620 }
1621 } else {
1622 unsigned sense = 1;
1623 unsigned f = *ff;
1624 const trace_opt *t;
1625 char *p = av[0];
1626
1627 while (*p) {
1628 switch (*p) {
1629 case '+': sense = 1; break;
1630 case '-': sense = 0; break;
1631 default:
1632 for (t = tt; t->ch; t++) {
1633 if (t->ch == *p) {
1634 if (sense) f |= t->f;
1635 else f &= ~t->f;
1636 goto tropt_ok;
1637 }
1638 }
1639 a_fail(a, "bad-%s-option", what, "%c", *p, A_END);
1640 return (0);
1641 tropt_ok:;
1642 break;
1643 }
1644 p++;
1645 }
1646 *ff = f;
1647 ch = 1;
1648 }
1649 a_ok(a);
1650 return (ch);
1651}
1652
1653#ifndef NTRACE
1654
1655static void acmd_trace(admin *a, unsigned ac, char *av[])
1656{
1657 if (traceish(a, ac, av, "trace", tr_opts, &tr_flags))
1658 trace_level(tr_flags);
1659}
1660
1661#endif
1662
1663static void acmd_watch(admin *a, unsigned ac, char *av[])
1664 { traceish(a, ac, av, "watch", w_opts, &a->f); }
1665
1666static void alertcmd(admin *a, unsigned f_and, unsigned f_eq,
1667 const char *status, char *av[])
1668 { a_alert(f_and, f_eq, status, "USER", "?TOKENS", av, A_END); a_ok(a); }
1669static void acmd_notify(admin *a, unsigned ac, char *av[])
1670 { alertcmd(a, AF_NOTE, AF_NOTE, "NOTE", av); }
1671static void acmd_warn(admin *a, unsigned ac, char *av[])
1672 { alertcmd(a, AF_WARN, AF_WARN, "WARN", av); }
1673
1674static void acmd_port(admin *a, unsigned ac, char *av[])
1675 { a_info(a, "%u", p_port(), A_END); a_ok(a); }
1676
1677static void acmd_daemon(admin *a, unsigned ac, char *av[])
1678{
1679 if (flags & F_DAEMON)
1680 a_fail(a, "already-daemon", A_END);
1681 else {
1682 a_notify("DAEMON", A_END);
1683 if (a_stdin)
1684 a_destroy(a_stdin);
1685 if (daemonize())
1686 a_fail(a, "daemon-error", "?ERRNO", A_END);
1687 else {
1688 flags |= F_DAEMON;
1689 a_ok(a);
1690 }
1691 }
1692}
1693
1694static void acmd_jobs(admin *a, unsigned ac, char *av[])
1695{
1696 admin_bgop *bg;
1697
1698 for (bg = a->bg; bg; bg = bg->next) {
1699 assert(bg->tag);
1700 a_info(a, "%s", bg->tag, A_END);
1701 }
1702 a_ok(a);
1703}
1704
1705static void acmd_bgcancel(admin *a, unsigned ac, char *av[])
1706{
1707 admin_bgop *bg;
1708
1709 if ((bg = a_bgfind(a, av[0])) == 0)
1710 a_fail(a, "unknown-tag", "%s", av[0], A_END);
1711 else {
1712 bg->cancel(bg);
1713 a_bgrelease(bg);
1714 a_ok(a);
1715 }
1716}
1717
1718static void acmd_algs(admin *a, unsigned ac, char *av[])
1719{
1720 peer *p;
1721 const kdata *kd;
1722 const dhgrp *g;
1723 const algswitch *algs;
1724
1725 if (!ac)
1726 kd = master;
1727 else {
1728 if ((p = a_findpeer(a, av[0])) == 0) return;
1729 kd = p->kx.kpriv;
1730 }
1731 g = kd->grp;
1732 algs = &kd->algs;
1733
1734 g->ops->grpinfo(g, a);
1735 a_info(a,
1736 "hash=%s", algs->h->name,
1737 "mgf=%s", algs->mgf->name,
1738 "hash-sz=%lu", (unsigned long)algs->h->hashsz,
1739 A_END);
1740 a_info(a,
1741 "bulk-transform=%s", algs->bulk->ops->name,
1742 "bulk-overhead=%lu",
1743 (unsigned long)algs->bulk->ops->overhead(algs->bulk),
1744 A_END);
1745 algs->bulk->ops->alginfo(algs->bulk, a);
1746 a_info(a,
1747 "cipher-data-limit=%lu",
1748 (unsigned long)algs->bulk->ops->expsz(algs->bulk),
1749 A_END);
1750 a_ok(a);
1751}
1752
1753static void acmd_list(admin *a, unsigned ac, char *av[])
1754{
1755 FOREACH_PEER(p, { a_info(a, "%s", p_name(p), A_END); });
1756 a_ok(a);
1757}
1758
1759static void acmd_ifname(admin *a, unsigned ac, char *av[])
1760{
1761 peer *p;
1762
1763 if ((p = a_findpeer(a, av[0])) != 0) {
1764 a_info(a, "%s", p_ifname(p), A_END);
1765 a_ok(a);
1766 }
1767}
1768
1769static void acmd_setifname(admin *a, unsigned ac, char *av[])
1770{
1771 peer *p;
1772
1773 if ((p = a_findpeer(a, av[0])) != 0) {
1774 a_notify("NEWIFNAME", "?PEER", p, "%s", p_ifname(p), "%s", av[1], A_END);
1775 p_setifname(p, av[1]);
1776 a_ok(a);
1777 }
1778}
1779
1780static void acmd_getchal(admin *a, unsigned ac, char *av[])
1781{
1782 buf b;
1783
1784 buf_init(&b, buf_i, PKBUFSZ);
1785 c_new(&b);
1786 a_info(a, "?B64", BBASE(&b), (size_t)BLEN(&b), A_END);
1787 a_ok(a);
1788}
1789
1790static void acmd_checkchal(admin *a, unsigned ac, char *av[])
1791{
1792 base64_ctx b64;
1793 buf b;
1794 dstr d = DSTR_INIT;
1795
1796 base64_init(&b64);
1797 base64_decode(&b64, av[0], strlen(av[0]), &d);
1798 base64_decode(&b64, 0, 0, &d);
1799 buf_init(&b, d.buf, d.len);
1800 if (c_check(&b) || BBAD(&b) || BLEFT(&b))
1801 a_fail(a, "invalid-challenge", A_END);
1802 else
1803 a_ok(a);
1804 dstr_destroy(&d);
1805}
1806
1807static void acmd_greet(admin *a, unsigned ac, char *av[])
1808{
1809 peer *p;
1810 base64_ctx b64;
1811 dstr d = DSTR_INIT;
1812
1813 if ((p = a_findpeer(a, av[0])) != 0) {
1814 base64_init(&b64);
1815 base64_decode(&b64, av[1], strlen(av[1]), &d);
1816 base64_decode(&b64, 0, 0, &d);
1817 p_greet(p, d.buf, d.len);
1818 dstr_destroy(&d);
1819 a_ok(a);
1820 }
1821}
1822
1823static void acmd_addr(admin *a, unsigned ac, char *av[])
1824{
1825 peer *p;
1826 const addr *ad;
1827
1828 if ((p = a_findpeer(a, av[0])) != 0) {
1829 ad = p_addr(p);
1830 assert(ad->sa.sa_family == AF_INET);
1831 a_info(a, "?ADDR", ad, A_END);
1832 a_ok(a);
1833 }
1834}
1835
1836static void acmd_peerinfo(admin *a, unsigned ac, char *av[])
1837{
1838 peer *p;
1839 const peerspec *ps;
1840 const char *ptag;
1841
1842 if ((p = a_findpeer(a, av[0])) != 0) {
1843 ps = p_spec(p);
1844 a_info(a, "tunnel=%s", ps->tops->name, A_END);
1845 a_info(a, "key=%s", p_tag(p),
1846 "current-key=%s", p->kx.kpub->tag, A_END);
1847 if ((ptag = p_privtag(p)) == 0) ptag = "(default)";
1848 a_info(a, "private-key=%s", ptag,
1849 "current-private-key=%s", p->kx.kpriv->tag, A_END);
1850 a_info(a, "keepalive=%lu", ps->t_ka, A_END);
1851 a_ok(a);
1852 }
1853}
1854
1855static void acmd_servinfo(admin *a, unsigned ac, char *av[])
1856{
1857 a_info(a, "implementation=edgeware-tripe", A_END);
1858 a_info(a, "version=%s", VERSION, A_END);
1859 a_info(a, "daemon=%s", BOOL(flags & F_DAEMON), A_END);
1860 a_ok(a);
1861}
1862
1863static void acmd_stats(admin *a, unsigned ac, char *av[])
1864{
1865 peer *p;
1866 stats *st;
1867
1868 if ((p = a_findpeer(a, av[0])) == 0)
1869 return;
1870
1871 st = p_stats(p);
1872 a_info(a, "start-time=%s", timestr(st->t_start), A_END);
1873 a_info(a, "last-packet-time=%s", timestr(st->t_last), A_END);
1874 a_info(a, "last-keyexch-time=%s", timestr(st->t_kx), A_END);
1875 a_info(a, "packets-in=%lu", st->n_in, "bytes-in=%lu", st->sz_in, A_END);
1876 a_info(a,
1877 "packets-out=%lu", st->n_out,
1878 "bytes-out=%lu", st->sz_out,
1879 A_END);
1880 a_info(a,
1881 "keyexch-packets-in=%lu", st->n_kxin,
1882 "keyexch-bytes-in=%lu", st->sz_kxin,
1883 A_END);
1884 a_info(a,
1885 "keyexch-packets-out=%lu", st->n_kxout,
1886 "keyexch-bytes-out=%lu", st->sz_kxout,
1887 A_END);
1888 a_info(a,
1889 "ip-packets-in=%lu", st->n_ipin,
1890 "ip-bytes-in=%lu", st->sz_ipin,
1891 A_END);
1892 a_info(a,
1893 "ip-packets-out=%lu", st->n_ipout,
1894 "ip-bytes-out=%lu", st->sz_ipout,
1895 A_END);
1896 a_info(a, "rejected-packets=%lu", st->n_reject, A_END);
1897 a_ok(a);
1898}
1899
1900static void acmd_kill(admin *a, unsigned ac, char *av[])
1901{
1902 peer *p;
1903
1904 if ((p = a_findpeer(a, av[0])) != 0) {
1905 p_destroy(p);
1906 a_ok(a);
1907 }
1908}
1909
1910static void acmd_forcekx(admin *a, unsigned ac, char *av[])
1911{
1912 peer *p;
1913
1914 if ((p = a_findpeer(a, av[0])) != 0) {
1915 kx_start(&p->kx, 1);
1916 a_ok(a);
1917 }
1918}
1919
1920static void acmd_reload(admin *a, unsigned ac, char *av[])
1921 { p_keyreload(); a_ok(a); }
1922
1923static void acmd_quit(admin *a, unsigned ac, char *av[])
1924{
1925 a_warn("SERVER", "quit", "admin-request", A_END);
1926 a_ok(a);
1927 a_quit();
1928}
1929
1930static void acmd_version(admin *a, unsigned ac, char *av[])
1931{
1932 a_info(a, "%s", PACKAGE, "%s", VERSION, A_END);
1933 a_ok(a);
1934}
1935
1936static void acmd_tunnels(admin *a, unsigned ac, char *av[])
1937{
1938 int i;
1939
1940 for (i = 0; tunnels[i]; i++)
1941 a_info(a, "%s", tunnels[i]->name, A_END);
1942 a_ok(a);
1943}
1944
1945/* --- The command table and help --- */
1946
1947typedef struct acmd {
1948 const char *name;
1949 const char *help;
1950 unsigned argmin, argmax;
1951 void (*func)(admin */*a*/, unsigned /*ac*/, char */*av*/[]);
1952} acmd;
1953
1954static void acmd_help(admin */*a*/, unsigned /*ac*/, char */*av*/[]);
1955
1956static const acmd acmdtab[] = {
1957 { "add", "[OPTIONS] PEER ADDR ...", 2, 0xffff, acmd_add },
1958 { "addr", "PEER", 1, 1, acmd_addr },
1959 { "algs", "[PEER]", 0, 1, acmd_algs },
1960 { "bgcancel", "TAG", 1, 1, acmd_bgcancel },
1961 { "checkchal", "CHAL", 1, 1, acmd_checkchal },
1962 { "daemon", 0, 0, 0, acmd_daemon },
1963 { "eping", "[OPTIONS] PEER", 1, 0xffff, acmd_eping },
1964 { "forcekx", "PEER", 1, 1, acmd_forcekx },
1965 { "getchal", 0, 0, 0, acmd_getchal },
1966 { "greet", "PEER CHAL", 2, 2, acmd_greet },
1967 { "help", 0, 0, 0, acmd_help },
1968 { "ifname", "PEER", 1, 1, acmd_ifname },
1969 { "jobs", 0, 0, 0, acmd_jobs },
1970 { "kill", "PEER", 1, 1, acmd_kill },
1971 { "list", 0, 0, 0, acmd_list },
1972 { "notify", "MESSAGE ...", 1, 0xffff, acmd_notify },
1973 { "peerinfo", "PEER", 1, 1, acmd_peerinfo },
1974 { "ping", "[OPTIONS] PEER", 1, 0xffff, acmd_ping },
1975 { "port", 0, 0, 0, acmd_port },
1976 { "quit", 0, 0, 0, acmd_quit },
1977 { "reload", 0, 0, 0, acmd_reload },
1978 { "servinfo", 0, 0, 0, acmd_servinfo },
1979 { "setifname", "PEER NEW-NAME", 2, 2, acmd_setifname },
1980 { "svcclaim", "SERVICE VERSION", 2, 2, acmd_svcclaim },
1981 { "svcensure", "SERVICE [VERSION]", 1, 2, acmd_svcensure },
1982 { "svcfail", "JOBID TOKENS...", 1, 0xffff, acmd_svcfail },
1983 { "svcinfo", "JOBID TOKENS...", 1, 0xffff, acmd_svcinfo },
1984 { "svclist", 0, 0, 0, acmd_svclist },
1985 { "svcok", "JOBID", 1, 1, acmd_svcok },
1986 { "svcquery", "SERVICE", 1, 1, acmd_svcquery },
1987 { "svcrelease", "SERVICE", 1, 1, acmd_svcrelease },
1988 { "svcsubmit", "[OPTIONS] SERVICE TOKENS...",
1989 2, 0xffff, acmd_svcsubmit },
1990 { "stats", "PEER", 1, 1, acmd_stats },
1991#ifndef NTRACE
1992 { "trace", "[OPTIONS]", 0, 1, acmd_trace },
1993#endif
1994 { "tunnels", 0, 0, 0, acmd_tunnels },
1995 { "version", 0, 0, 0, acmd_version },
1996 { "warn", "MESSAGE ...", 1, 0xffff, acmd_warn },
1997 { "watch", "[OPTIONS]", 0, 1, acmd_watch },
1998 { 0, 0, 0, 0, 0 }
1999};
2000
2001static void acmd_help(admin *a, unsigned ac, char *av[])
2002{
2003 const acmd *c;
2004
2005 for (c = acmdtab; c->name; c++) {
2006 if (c->help)
2007 a_info(a, "%s", c->name, "*%s", c->help, A_END);
2008 else
2009 a_info(a, "%s", c->name, A_END);
2010 }
2011 a_ok(a);
2012}
2013
2014/*----- Connection handling -----------------------------------------------*/
2015
2016/* --- @a_destroypending@ --- *
2017 *
2018 * Arguments: ---
2019 *
2020 * Returns: ---
2021 *
2022 * Use: Destroys pending admin connections at a safe time.
2023 */
2024
2025static void a_destroypending(void)
2026{
2027 admin *a, *aa, *head;
2028 admin_bgop *bg, *bbg;
2029 admin_service *svc, *ssvc;
2030
2031 /* --- Destroy connections marked as pending --- *
2032 *
2033 * Slightly messy. Killing clients may cause others to finally die. Make
2034 * sure that they can be put on the list without clobbering anything or
2035 * getting lost.
2036 */
2037
2038 while (a_dead) {
2039 head = a_dead;
2040 a_dead = 0;
2041 for (a = head; a; a = aa) {
2042 aa = a->next;
2043 assert(a->f & AF_DEAD);
2044
2045 /* --- Report what we're doing --- */
2046
2047 T( trace(T_ADMIN, "admin: completing destruction of connection %u",
2048 a->seq); )
2049
2050 /* --- If this is the foreground client then shut down --- */
2051
2052 if (a->f & AF_FOREGROUND) {
2053 T( trace(T_ADMIN, "admin: foreground client quit: shutting down"); )
2054 a_warn("SERVER", "quit", "foreground-eof", A_END);
2055 a_quit();
2056 }
2057
2058 /* --- Abort any background jobs in progress --- */
2059
2060 for (bg = a->bg; bg; bg = bbg) {
2061 bbg = bg->next;
2062 bg->cancel(bg);
2063 if (bg->tag) xfree(bg->tag);
2064 xfree(bg);
2065 }
2066
2067 /* --- Release services I hold, and abort pending jobs --- */
2068
2069 for (svc = a->svcs; svc; svc = ssvc) {
2070 ssvc = svc->next;
2071 a_svcrelease(svc);
2072 }
2073 a_jobtablefinal(&a->j);
2074
2075 /* --- Close file descriptors and selectory --- */
2076
2077 selbuf_destroy(&a->b);
2078 if (a->b.reader.fd != a->w.fd) close(a->b.reader.fd);
2079 close(a->w.fd);
2080 if (a_stdin == a) a_stdin = 0;
2081
2082 /* --- Done --- */
2083
2084 DESTROY(a);
2085 }
2086 }
2087}
2088
2089/* --- @a_destroy@ --- *
2090 *
2091 * Arguments: @admin *a@ = pointer to an admin block
2092 *
2093 * Returns: ---
2094 *
2095 * Use: Destroys an admin block. This requires a certain amount of
2096 * care.
2097 */
2098
2099static void freequeue(oqueue *q)
2100{
2101 obuf *o, *oo;
2102
2103 for (o = q->hd; o; o = oo) {
2104 oo = o->next;
2105 xfree(o);
2106 }
2107 q->hd = q->tl = 0;
2108}
2109
2110static void a_destroy(admin *a)
2111{
2112 if (a->f & AF_DEAD)
2113 return;
2114
2115 if (a->next) a->next->prev = a->prev;
2116 if (a->prev) a->prev->next = a->next;
2117 else admins = a->next;
2118
2119 if (a->out.hd) sel_rmfile(&a->w);
2120 freequeue(&a->out);
2121
2122 a->f |= AF_DEAD;
2123 a->next = a_dead;
2124 a_dead = a;
2125
2126 T( trace(T_ADMIN, "admin: killing connection %u", a->seq); )
2127}
2128
2129/* --- @a_line@ --- *
2130 *
2131 * Arguments: @char *p@ = pointer to the line read
2132 * @size_t len@ = length of the line
2133 * @void *vp@ = pointer to my admin block
2134 *
2135 * Returns: ---
2136 *
2137 * Use: Handles a line of input.
2138 */
2139
2140static void a_line(char *p, size_t len, void *vp)
2141{
2142 admin *a = vp;
2143 const acmd *c;
2144 char *av[16 + 1];
2145 size_t ac;
2146
2147 QUICKRAND;
2148 if (a->f & AF_DEAD)
2149 return;
2150 if (!p) {
2151 if (!a->bg)
2152 a_destroy(a);
2153 else {
2154 a->f |= AF_CLOSE;
2155 selbuf_disable(&a->b);
2156 }
2157 return;
2158 }
2159 ac = str_qsplit(p, av, N(av) - 1, 0, STRF_QUOTE);
2160 if (!ac)
2161 return;
2162 av[ac] = 0;
2163 for (c = acmdtab; c->name; c++) {
2164 if (mystrieq(av[0], c->name)) {
2165 ac--;
2166 if (c->argmin > ac || ac > c->argmax) {
2167 if (!c->help)
2168 a_fail(a, "bad-syntax", "%s", c->name, "", A_END);
2169 else
2170 a_fail(a, "bad-syntax", "%s", c->name, "%s", c->help, A_END);
2171 } else
2172 c->func(a, ac, av + 1);
2173 return;
2174 }
2175 }
2176 a_fail(a, "unknown-command", "%s", av[0], A_END);
2177}
2178
2179/* --- @a_create@ --- *
2180 *
2181 * Arguments: @int fd_in, fd_out@ = file descriptors to use
2182 * @unsigned f@ = initial flags to set
2183 *
2184 * Returns: ---
2185 *
2186 * Use: Creates a new admin connection.
2187 */
2188
2189void a_create(int fd_in, int fd_out, unsigned f)
2190{
2191 admin *a = CREATE(admin);
2192
2193 T( static unsigned seq = 0;
2194 a->seq = seq++; )
2195 T( trace(T_ADMIN, "admin: accepted connection %u", a->seq); )
2196 a->bg = 0;
2197 a->ref = 0;
2198 a->svcs = 0;
2199 a_jobtableinit(&a->j);
2200 a->f = f;
2201 if (fd_in == STDIN_FILENO) a_stdin = a;
2202 fdflags(fd_in, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
2203 if (fd_out != fd_in)
2204 fdflags(fd_out, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
2205 selbuf_init(&a->b, &sel, fd_in, a_line, a);
2206 sel_initfile(&sel, &a->w, fd_out, SEL_WRITE, a_flush, a);
2207 a->out.hd = a->out.tl = 0;
2208 a->next = admins;
2209 a->prev = 0;
2210 if (admins) admins->prev = a;
2211 admins = a;
2212}
2213
2214/* --- @a_accept@ --- *
2215 *
2216 * Arguments: @int fd@ = file descriptor to accept
2217 * @unsigned mode@ = what to do
2218 * @void *v@ = uninteresting pointer
2219 *
2220 * Returns: ---
2221 *
2222 * Use: Accepts a new admin connection.
2223 */
2224
2225static void a_accept(int fd, unsigned mode, void *v)
2226{
2227 int nfd;
2228 struct sockaddr_un sun;
2229 socklen_t sz = sizeof(sun);
2230
2231 if ((nfd = accept(fd, (struct sockaddr *)&sun, &sz)) < 0) {
2232 if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK &&
2233 errno != ECONNABORTED && errno != EPROTO)
2234 a_warn("ADMIN", "accept-error", "?ERRNO", A_END);
2235 return;
2236 }
2237 a_create(nfd, nfd, 0);
2238}
2239
2240/* --- @a_preselect@ --- *
2241 *
2242 * Arguments: ---
2243 *
2244 * Returns: ---
2245 *
2246 * Use: Informs the admin module that we're about to select again,
2247 * and that it should do cleanup things it has delayed until a
2248 * `safe' time.
2249 */
2250
2251void a_preselect(void) { if (a_dead) a_destroypending(); }
2252
2253/* --- @a_daemon@ --- *
2254 *
2255 * Arguments: ---
2256 *
2257 * Returns: ---
2258 *
2259 * Use: Informs the admin module that it's a daemon.
2260 */
2261
2262void a_daemon(void) { flags |= F_DAEMON; }
2263
2264/* --- @a_init@ --- *
2265 *
2266 * Arguments: @const char *name@ = socket name to create
2267 * @uid_t u@ = user to own the socket
2268 * @gid_t g@ = group to own the socket
2269 * @mode_t m@ = permissions to set on the socket
2270 *
2271 * Returns: ---
2272 *
2273 * Use: Creates the admin listening socket.
2274 */
2275
2276void a_init(const char *name, uid_t u, gid_t g, mode_t m)
2277{
2278 int fd;
2279 int n = 5;
2280 struct sockaddr_un sun;
2281 struct sigaction sa;
2282 size_t sz;
2283 mode_t omask;
2284
2285 /* --- Create services table --- */
2286
2287 sym_create(&a_svcs);
2288
2289 /* --- Set up the socket address --- */
2290
2291 sz = strlen(name) + 1;
2292 if (sz > sizeof(sun.sun_path))
2293 die(EXIT_FAILURE, "socket name `%s' too long", name);
2294 BURN(sun);
2295 sun.sun_family = AF_UNIX;
2296 memcpy(sun.sun_path, name, sz);
2297 sz += offsetof(struct sockaddr_un, sun_path);
2298
2299 /* --- Attempt to bind to the socket --- */
2300
2301 omask = umask(0077);
2302again:
2303 if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
2304 die(EXIT_FAILURE, "couldn't create socket: %s", strerror(errno));
2305 if (bind(fd, (struct sockaddr *)&sun, sz) < 0) {
2306 struct stat st;
2307 int e = errno;
2308 if (errno != EADDRINUSE) {
2309 die(EXIT_FAILURE, "couldn't bind to address `%s': %s",
2310 sun.sun_path, strerror(e));
2311 }
2312 if (!n)
2313 die(EXIT_FAILURE, "too many retries; giving up");
2314 n--;
2315 if (!connect(fd, (struct sockaddr *)&sun, sz)) {
2316 die(EXIT_FAILURE, "server already listening on admin socket `%s'",
2317 sun.sun_path);
2318 }
2319 if (errno != ECONNREFUSED)
2320 die(EXIT_FAILURE, "couldn't bind to address: %s", strerror(e));
2321 if (stat(sun.sun_path, &st)) {
2322 die(EXIT_FAILURE, "couldn't stat `%s': %s",
2323 sun.sun_path, strerror(errno));
2324 }
2325 if (!S_ISSOCK(st.st_mode))
2326 die(EXIT_FAILURE, "object `%s' isn't a socket", sun.sun_path);
2327 T( trace(T_ADMIN, "admin: stale socket found; removing it"); )
2328 unlink(sun.sun_path);
2329 close(fd);
2330 goto again;
2331 }
2332 if (chown(sun.sun_path, u, g)) {
2333 die(EXIT_FAILURE, "failed to set socket owner: %s",
2334 strerror(errno));
2335 }
2336 if (chmod(sun.sun_path, m)) {
2337 die(EXIT_FAILURE, "failed to set socket permissions: %s",
2338 strerror(errno));
2339 }
2340 umask(omask);
2341 fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
2342 if (listen(fd, 5))
2343 die(EXIT_FAILURE, "couldn't listen on socket: %s", strerror(errno));
2344
2345 /* --- Listen to the socket --- */
2346
2347 sel_initfile(&sel, &sock, fd, SEL_READ, a_accept, 0);
2348 sel_addfile(&sock);
2349 sockname = name;
2350 bres_init(&sel);
2351 T( trace_custom(a_trace, 0);
2352 trace(T_ADMIN, "admin: enabled custom tracing"); )
2353 flags |= F_INIT;
2354
2355 /* --- Set up signal handlers --- */
2356
2357 sig_add(&s_term, SIGTERM, a_sigdie, 0);
2358 sig_add(&s_hup, SIGHUP, a_sighup, 0);
2359 signal(SIGPIPE, SIG_IGN);
2360 sigaction(SIGINT, 0, &sa);
2361 if (sa.sa_handler != SIG_IGN)
2362 sig_add(&s_int, SIGINT, a_sigdie, 0);
2363}
2364
2365/*----- That's all, folks -------------------------------------------------*/