chiark / gitweb /
client: Clean up variable declarations.
[tripe] / client / tripectl.c
CommitLineData
410c8acf 1/* -*-c-*-
410c8acf 2 *
3 * Client for TrIPE
4 *
5 * (c) 2001 Straylight/Edgeware
6 */
7
e04c2d50 8/*----- Licensing notice --------------------------------------------------*
410c8acf 9 *
10 * This file is part of Trivial IP Encryption (TrIPE).
11 *
12 * TrIPE is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
e04c2d50 16 *
410c8acf 17 * TrIPE is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
e04c2d50 21 *
410c8acf 22 * You should have received a copy of the GNU General Public License
23 * along with TrIPE; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
410c8acf 27/*----- Header files ------------------------------------------------------*/
28
73189848 29#include "config.h"
30
410c8acf 31#include <ctype.h>
32#include <errno.h>
33#include <signal.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <time.h>
38
39#include <sys/types.h>
40#include <sys/time.h>
41#include <unistd.h>
42#include <fcntl.h>
43#include <sys/wait.h>
44#include <syslog.h>
45
46#include <sys/socket.h>
47#include <sys/un.h>
48#include <arpa/inet.h>
49#include <netdb.h>
50
51#include <mLib/alloc.h>
19dd2531 52#include <mLib/daemonize.h>
410c8acf 53#include <mLib/darray.h>
54#include <mLib/dstr.h>
410c8acf 55#include <mLib/mdwopt.h>
56#include <mLib/quis.h>
57#include <mLib/report.h>
58#include <mLib/sel.h>
59#include <mLib/selbuf.h>
5f5150b1 60#include <mLib/sig.h>
410c8acf 61#include <mLib/str.h>
19dd2531 62#include <mLib/versioncmp.h>
410c8acf 63
64#include "util.h"
65
66#undef sun
67
68/*----- Data structures ---------------------------------------------------*/
69
70#ifndef STRING_V
71# define STRING_V
72 DA_DECL(string_v, char *);
73#endif
74
75/*----- Static variables --------------------------------------------------*/
76
8efdf64b 77static sel_state sel;
06b2a088 78static const char *pidfile = 0;
79static const char *logname = 0;
410c8acf 80static FILE *logfp = 0;
81static unsigned f = 0;
82static int fd;
de014da6 83static const char *bgtag = 0;
410c8acf 84
85#define f_bogus 1u
86#define f_spawn 2u
87#define f_daemon 4u
88#define f_spawnopts 8u
89#define f_syslog 16u
90#define f_command 32u
91#define f_noinput 64u
92#define f_warn 128u
93#define f_uclose 256u
94
95/*----- Main code ---------------------------------------------------------*/
96
97static void reap(int sig)
98{
410c8acf 99 int e = errno;
06b2a088 100 while (waitpid(-1, 0, WNOHANG) > 0)
410c8acf 101 ;
102 errno = e;
103}
104
105static void writelog(const char *cat, const char *msg)
106{
107 char buf[256];
108 time_t t = time(0);
109 struct tm *tm = localtime(&t);
110 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
111 fprintf(logfp, "%s %s: %s\n", buf, cat, msg);
112}
113
de014da6 114static void checkbg(char **p)
115{
116 char *q = str_getword(p);
117 if (!q)
118 die(EXIT_FAILURE, "missing background tag");
119 if (!bgtag || strcmp(bgtag, q) != 0)
120 die(EXIT_FAILURE, "unexpected background tag `%s'", q);
121}
122
123static void checkfg(void)
6047fbac 124 { if (bgtag) die(EXIT_FAILURE, "unexpected foreground response"); }
de014da6 125
6be88c14 126static void cline(char *p, size_t len, void *b)
410c8acf 127{
128 char *q;
129 if (!p) {
130 if (f & f_command)
131 die(EXIT_FAILURE, "server dropped the connection");
132 exit(0);
133 }
134 q = str_getword(&p);
135 if (!q)
136 return;
137 if (strcmp(q, "WARN") == 0) {
138 if (f & f_syslog)
139 syslog(LOG_WARNING, "%s", p);
140 if (logfp)
141 writelog("warning", p);
142 if (f & f_warn)
143 fprintf(stderr, "Warning: %s\n", p);
144 } else if (strcmp(q, "TRACE") == 0) {
145 if (f & f_syslog)
146 syslog(LOG_DEBUG, "%s", p);
147 if (logfp)
148 writelog("debug", p);
149 } else if (!(f & f_command)) {
150 if (f & f_syslog)
151 syslog(LOG_ERR, "unexpected output `%s %s'", q, p);
152 if (logfp) {
153 dstr d = DSTR_INIT;
154 dstr_putf(&d, "unexpected output `%s %s'", q, p);
155 writelog("error", d.buf);
156 dstr_destroy(&d);
157 }
de014da6 158 } else if (strcmp(q, "FAIL") == 0) {
159 checkfg();
410c8acf 160 die(EXIT_FAILURE, "%s", p);
de014da6 161 } else if (strcmp(q, "INFO") == 0) {
162 checkfg();
410c8acf 163 puts(p);
5136c31e 164 fflush(stdout);
de014da6 165 } else if (strcmp(q, "OK") == 0) {
166 checkfg();
167 exit(0);
168 } else if (strcmp(q, "BGDETACH") == 0) {
169 if (bgtag)
170 die(EXIT_FAILURE, "repeat detach");
171 bgtag = xstrdup(p);
172 } else if (strcmp(q, "BGOK") == 0) {
173 checkbg(&p);
410c8acf 174 exit(0);
de014da6 175 } else if (strcmp(q, "BGINFO") == 0) {
176 checkbg(&p);
177 puts(p);
5136c31e 178 fflush(stdout);
de014da6 179 } else if (strcmp(q, "BGFAIL") == 0) {
180 checkbg(&p);
181 die(EXIT_FAILURE, "%s", p);
182 } else
e04c2d50 183 die(EXIT_FAILURE, "unexpected output `%s %s'", q, p);
410c8acf 184}
185
6be88c14 186static void sline(char *p, size_t len, void *b)
410c8acf 187{
188 if (!p) {
189 if (!(f & f_uclose))
190 moan("server closed the connection");
191 exit(0);
192 }
193 puts(p);
5136c31e 194 fflush(stdout);
410c8acf 195}
196
6be88c14 197static void uline(char *p, size_t len, void *b)
410c8acf 198{
410c8acf 199 if (!p) {
200 selbuf_destroy(b);
201 shutdown(fd, 1);
202 f |= f_uclose;
203 } else {
6be88c14 204 p[len] = '\n';
3cdc3f3a 205 errno = EIO;
206 if (write(fd, p, len + 1) != len + 1)
207 moan("write failed: %s", strerror(errno));
208 }
209}
210
211static void setup(const char *cmd)
212{
213 dstr d = DSTR_INIT;
214 char ch;
215 char *p, *q;
216 int n;
217
218 dstr_puts(&d, cmd);
219 dstr_putc(&d, '\n');
220 errno = EIO; /* Relax: be vague */
221 if (write(fd, d.buf, d.len) != d.len) {
222 die(EXIT_FAILURE, "error sending setup command `%s': %s",
223 cmd, strerror(errno));
224 }
225 dstr_reset(&d);
226 for (;;) {
227 n = read(fd, &ch, 1);
228 if (!n)
229 die(EXIT_FAILURE, "unexpected EOF during setup");
230 if (n < 0) {
231 die(EXIT_FAILURE, "error receiving reply to `%s': %s",
232 cmd, strerror(errno));
233 }
234 if (d.len < 256)
235 dstr_putc(&d, ch);
236 if (ch == '\n') {
237 p = d.buf;
238 q = str_getword(&p);
239 if (!q)
240 ;
241 else if (strcmp(q, "OK") == 0)
242 return;
243 else if (strcmp(q, "FAIL") == 0)
244 die(EXIT_FAILURE, "setup command `%s' failed: %s", cmd, p);
245 dstr_reset(&d);
246 }
410c8acf 247 }
248}
249
9dd01bc6 250static void logfile(const char *name)
251{
fa4bef80 252 FILE *fp;
253
254 if ((fp = fopen(name, "a")) != 0) {
255 if (logfp)
256 fclose(logfp);
257 logfp = fp;
258 setvbuf(logfp, 0, _IOLBF, BUFSIZ);
259 } else {
260 dstr d = DSTR_INIT;
261 dstr_putf(&d, "error opening logfile `%s': %s", name, strerror(errno));
262 if (logfp)
263 writelog("error", d.buf);
264 else if (logname)
265 die(EXIT_FAILURE, d.buf);
266 if (f & f_syslog)
267 syslog(LOG_ERR, d.buf);
268 dstr_destroy(&d);
9dd01bc6 269 }
9dd01bc6 270}
271
f98df549 272static void sighup(int sig, void *v) { logfile(logname); }
5f5150b1 273
6047fbac 274static void cleanup(void) { if (pidfile) unlink(pidfile); }
06b2a088 275
276static void sigdie(int sig)
6047fbac 277 { cleanup(); signal(sig, SIG_DFL); raise(sig); }
06b2a088 278
f685e692
MW
279static void putarg(string_v *av, const char *fmt, ...)
280{
281 va_list ap;
282 dstr d = DSTR_INIT;
283
284 va_start(ap, fmt);
285 dstr_vputf(&d, fmt, &ap);
286 dstr_putz(&d);
287 va_end(ap);
288 DA_UNSHIFT(av, xstrdup(d.buf));
289 dstr_destroy(&d);
290}
291
410c8acf 292static void version(FILE *fp)
f98df549 293 { pquis(fp, "$, TrIPE version " VERSION "\n"); }
410c8acf 294
295static void usage(FILE *fp)
296{
297 pquis(fp, "\
298Usage:\n\
2d752320 299 $ [-w] [-OPTIONS] [COMMAND [ARGS]...]\n\
300 $ [-Dl] [-f FILE] [-OPTIONS]\n\
410c8acf 301Options:\n\
2d752320 302 [-s] [-d DIRECTORY] [-a SOCKET] [-P PIDFILE]\n\
303 [-p PROGRAM] [-S ARG,ARG,...]\n\
410c8acf 304");
305}
306
307static void help(FILE *fp)
308{
309 version(fp);
310 fputc('\n', fp);
311 usage(fp);
312 fputs("\
313\n\
314Options in full:\n\
315\n\
316-h, --help Show this help text.\n\
317-v, --version Show version number.\n\
318-u, --usage Show brief usage message.\n\
319\n\
320-D, --daemon Become a background task after connecting.\n\
ef4a1ab7 321-d, --directory=DIR Select current directory [default " CONFIGDIR "].\n\
ab46a787
MW
322-U, --setuid=USER Set uid to USER after initialization.\n\
323-G, --setgid=GROUP Set gid to GROUP after initialization.\n\
3cdc3f3a 324-a, --admin-socket=FILE Select socket to connect to\n\
e04c2d50 325 [default " SOCKETDIR "/tripesock].\n\
06b2a088 326-P, --pidfile=FILE Write process-id to FILE.\n\
410c8acf 327\n\
328-s, --spawn Start server rather than connecting.\n\
329-p, --spawn-path=PATH Specify path to executable.\n\
330-S, --spawn-args=ARGS Specify comma-separated arguments.\n\
331\n\
332-l, --syslog Log messages to system log.\n\
333-f, --logfile=FILE Log messages to FILE.\n\
334-w, --warnings Show warnings when running commands.\n\
335", fp);
336}
337
338int main(int argc, char *argv[])
339{
ef4a1ab7 340 const char *dir = CONFIGDIR;
341 const char *sock = SOCKETDIR "/tripesock";
410c8acf 342 const char *spawnpath = "tripe";
343 string_v spawnopts = DA_INIT;
344 char *p;
06b2a088 345 FILE *pidfp = 0;
8efdf64b
MW
346 int i;
347 size_t sz;
ab46a787
MW
348 uid_t u = -1;
349 gid_t g = -1;
8efdf64b
MW
350 int pfd[2];
351 pid_t kid;
352 struct sigaction sa;
353 sigset_t newmask, oldmask;
354 struct sockaddr_un sun;
355 selbuf bu, bs;
356 dstr d = DSTR_INIT;
357 sig hup;
410c8acf 358
359 ego(argv[0]);
360
361 if ((p = getenv("TRIPEDIR")) != 0)
362 dir = p;
797cf76b
MW
363 if ((p = getenv("TRIPESOCK")) != 0)
364 sock = p;
410c8acf 365
366 /* --- Parse the arguments --- */
367
368 for (;;) {
369 static const struct option opts[] = {
370 { "help", 0, 0, 'h' },
371 { "version", 0, 0, 'v' },
372 { "usage", 0, 0, 'u' },
373 { "daemon", 0, 0, 'D' },
ab46a787
MW
374 { "uid", OPTF_ARGREQ, 0, 'U' },
375 { "setuid", OPTF_ARGREQ, 0, 'U' },
376 { "gid", OPTF_ARGREQ, 0, 'G' },
377 { "setgid", OPTF_ARGREQ, 0, 'G' },
410c8acf 378 { "directory", OPTF_ARGREQ, 0, 'd' },
379 { "admin-socket", OPTF_ARGREQ, 0, 'a' },
380 { "spawn", 0, 0, 's' },
381 { "spawn-path", OPTF_ARGREQ, 0, 'p' },
382 { "spawn-args", OPTF_ARGREQ, 0, 'S' },
383 { "syslog", 0, 0, 'l' },
384 { "logfile", OPTF_ARGREQ, 0, 'f' },
385 { "warnings", 0, 0, 'w' },
06b2a088 386 { "pidfile", OPTF_ARGREQ, 0, 'P' },
410c8acf 387 { 0, 0, 0, 0 }
388 };
389
8efdf64b 390 i = mdwopt(argc, argv, "+hvuDU:G:d:a:sp:S:lwf:nP:", opts, 0, 0, 0);
410c8acf 391 if (i < 0)
392 break;
393 switch (i) {
394 case 'h':
395 help(stdout);
396 exit(0);
397 case 'v':
398 version(stdout);
399 exit(0);
400 case 'u':
401 usage(stdout);
402 exit(0);
403 case 'D':
404 f |= f_daemon | f_noinput;
405 break;
ab46a787
MW
406 case 'U':
407 u = u_getuser(optarg, &g);
408 break;
409 case 'G':
410 g = u_getgroup(optarg);
411 break;
410c8acf 412 case 'd':
413 dir = optarg;
414 break;
415 case 'a':
416 sock = optarg;
417 break;
418 case 's':
419 f |= f_spawn;
420 break;
421 case 'p':
422 f |= f_spawn;
423 spawnpath = optarg;
424 break;
425 case 'S':
426 f |= f_spawn | f_spawnopts;
427 for (p = strtok(optarg, ","); p; p = strtok(0, ","))
428 DA_PUSH(&spawnopts, p);
429 break;
430 case 'l':
431 f |= f_syslog | f_noinput;
432 break;
433 case 'w':
434 f |= f_warn;
435 break;
436 case 'f':
9dd01bc6 437 logname = optarg;
410c8acf 438 f |= f_noinput;
439 break;
06b2a088 440 case 'P':
441 pidfile = optarg;
442 break;
410c8acf 443 default:
444 f |= f_bogus;
445 break;
446 }
447 }
448 if ((f & f_bogus) || ((f & f_noinput) && optind < argc)) {
449 usage(stderr);
450 exit(EXIT_FAILURE);
451 }
452
06b2a088 453 /* --- Set various things up --- */
454
455 if (chdir(dir)) {
456 die(EXIT_FAILURE, "couldn't set `%s' as current directory: %s",
457 dir, strerror(errno));
458 }
459 if (logname)
460 logfile(logname);
461 if (!pidfile && (f & f_daemon) && ((f & f_syslog) || logname))
462 pidfile = "tripectl.pid";
463 if (pidfile && (pidfp = fopen(pidfile, "w")) == 0) {
464 die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
465 pidfile, strerror(errno));
466 }
8efdf64b
MW
467 sel_init(&sel);
468 sig_init(&sel);
06b2a088 469 signal(SIGINT, sigdie);
470 signal(SIGQUIT, sigdie);
471 signal(SIGTERM, sigdie);
472 atexit(cleanup);
473
410c8acf 474 /* --- Connect to the server --- */
475
476 if (f & f_spawn) {
410c8acf 477 sa.sa_handler = reap;
478 sigemptyset(&sa.sa_mask);
479 sa.sa_flags = SA_NOCLDSTOP;
480#ifdef SA_RESTART
481 sa.sa_flags |= SA_RESTART;
482#endif
483 sigaction(SIGCHLD, &sa, 0);
484
410c8acf 485 DA_PUSH(&spawnopts, 0);
ab46a787
MW
486 if (g != (gid_t)-1) putarg(&spawnopts, "-G%lu", (unsigned long)g);
487 if (u != (uid_t)-1) putarg(&spawnopts, "-U%lu", (unsigned long)u);
f685e692
MW
488 putarg(&spawnopts, "-a%s", sock);
489 putarg(&spawnopts, "-d.");
490 putarg(&spawnopts, "-F");
491 putarg(&spawnopts, "%s", spawnpath);
410c8acf 492 if (socketpair(PF_UNIX, SOCK_STREAM, 0, pfd))
493 die(EXIT_FAILURE, "error from socketpair: %s", strerror(errno));
494 sigemptyset(&newmask);
495 sigaddset(&newmask, SIGCHLD);
496 sigprocmask(SIG_BLOCK, &newmask, &oldmask);
497 if ((kid = fork()) < 0)
498 die(EXIT_FAILURE, "fork failed: %s", strerror(errno));
499 if (!kid) {
500 dup2(pfd[1], STDIN_FILENO);
501 dup2(pfd[1], STDOUT_FILENO);
410c8acf 502 close(pfd[0]);
6047fbac
MW
503 close(pfd[1]);
504 if (logfp) fclose(logfp);
505 if (pidfp) fclose(pidfp);
9dd01bc6 506 closelog();
6047fbac 507 if (f & f_daemon) detachtty();
410c8acf 508 execvp(DA(&spawnopts)[0], DA(&spawnopts));
509 die(127, "couldn't exec `%s': %s", spawnpath, strerror(errno));
510 }
511 sigprocmask(SIG_SETMASK, &oldmask, 0);
512 fd = pfd[0];
513 close(pfd[1]);
514 } else {
8efdf64b 515 sz = strlen(sock) + 1;
06b2a088 516 if (sz > sizeof(sun.sun_path))
410c8acf 517 die(EXIT_FAILURE, "socket name `%s' too long", sock);
518 memset(&sun, 0, sizeof(sun));
519 sun.sun_family = AF_UNIX;
06b2a088 520 memcpy(sun.sun_path, sock, sz);
521 sz = sz + offsetof(struct sockaddr_un, sun_path);
410c8acf 522 if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
523 die(EXIT_FAILURE, "error making socket: %s", strerror(errno));
524 if (connect(fd, (struct sockaddr *)&sun, sz)) {
525 die(EXIT_FAILURE, "error connecting to `%s': %s",
9dd01bc6 526 sun.sun_path, strerror(errno));
410c8acf 527 }
528 }
529
ab46a787 530 u_setugid(u, g);
410c8acf 531 if (f & f_daemon) {
19dd2531 532 if (daemonize())
410c8acf 533 die(EXIT_FAILURE, "error becoming daemon: %s", strerror(errno));
534 }
06b2a088 535 if (pidfp) {
a803add7 536 fprintf(pidfp, "%li\n", (long)getpid());
06b2a088 537 fclose(pidfp);
538 }
3cdc3f3a 539 signal(SIGPIPE, SIG_IGN);
410c8acf 540
541 /* --- If we're meant to be interactive, do that --- */
542
3cdc3f3a 543 if (optind == argc)
544 setup("WATCH -A+tw");
410c8acf 545 if (!(f & f_noinput) && optind == argc) {
410c8acf 546 selbuf_init(&bu, &sel, STDIN_FILENO, uline, &bu);
547 selbuf_init(&bs, &sel, fd, sline, &bs);
548 for (;;) {
5f5150b1 549 if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
410c8acf 550 die(EXIT_FAILURE, "select failed: %s", strerror(errno));
551 }
552 }
553
554 /* --- If there's a command, submit it --- */
555
556 if (optind < argc) {
3cdc3f3a 557 setup((f & f_warn) ? "WATCH -A+w" : "WATCH -A");
0ed0735f
MW
558 while (optind < argc)
559 u_quotify(&d, argv[optind++]);
410c8acf 560 dstr_putc(&d, '\n');
3cdc3f3a 561 errno = EIO;
562 if (write(fd, d.buf, d.len) != d.len || shutdown(fd, 1))
563 die(EXIT_FAILURE, "write failed: %s", strerror(errno));
410c8acf 564 dstr_destroy(&d);
565 f |= f_command;
566 }
567
568 /* --- Pull everything else out of the box --- */
569
8efdf64b 570 selbuf_init(&bs, &sel, fd, cline, 0);
5f5150b1 571
8efdf64b
MW
572 if (f & f_syslog)
573 openlog(QUIS, 0, LOG_DAEMON);
574 if (logfp)
575 sig_add(&hup, SIGHUP, sighup, 0);
576 for (;;) {
577 if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
578 die(EXIT_FAILURE, "select failed: %s", strerror(errno));
410c8acf 579 }
580
581 return (0);
582}
583
584/*----- That's all, folks -------------------------------------------------*/