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