chiark / gitweb /
Expunge revision histories in files.
[tripe] / client.c
1 /* -*-c-*-
2  *
3  * $Id: client.c,v 1.14 2004/04/08 01:36:17 mdw Exp $
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     write(fd, p, len + 1);
173   }
174 }
175
176 static void logfile(const char *name)
177 {
178   FILE *fp;
179
180   if ((fp = fopen(name, "a")) != 0) {
181     if (logfp)
182       fclose(logfp);
183     logfp =  fp;
184     setvbuf(logfp, 0, _IOLBF, BUFSIZ);
185   } else {
186     dstr d = DSTR_INIT;
187     dstr_putf(&d, "error opening logfile `%s': %s", name, strerror(errno));
188     if (logfp)
189       writelog("error", d.buf);
190     else if (logname)
191       die(EXIT_FAILURE, d.buf);
192     if (f & f_syslog)
193       syslog(LOG_ERR, d.buf);
194     dstr_destroy(&d);
195   }
196 }
197
198 static void sighup(int sig, void *v)
199 {
200   logfile(logname);
201 }
202
203 static void cleanup(void)
204 {
205   if (pidfile)
206     unlink(pidfile);
207 }
208
209 static void sigdie(int sig)
210 {
211   cleanup();
212   signal(sig, SIG_DFL);
213   raise(sig);
214 }
215
216 static void version(FILE *fp)
217 {
218   pquis(fp, "$, TrIPE version " VERSION "\n");
219 }
220
221 static void usage(FILE *fp)
222 {
223   pquis(fp, "\
224 Usage:\n\
225         $ [-w] [-options] [command [args]...]\n\
226         $ [-Dl] [-f file] [-options]\n\
227 Options:\n\
228         [-s] [-d directory] [-a socket] [-P pidfile]\n\
229         [-p program] [-S arg,arg,...]\n\
230 ");
231 }
232
233 static void help(FILE *fp)
234 {
235   version(fp);
236   fputc('\n', fp);
237   usage(fp);
238   fputs("\
239 \n\
240 Options in full:\n\
241 \n\
242 -h, --help              Show this help text.\n\
243 -v, --version           Show version number.\n\
244 -u, --usage             Show brief usage message.\n\
245 \n\
246 -D, --daemon            Become a background task after connecting.\n\
247 -d, --directory=DIR     Select current directory [default " CONFIGDIR "].\n\
248 -a, --admin-socket=FILE Select socket to connect to
249                           [default " SOCKETDIR "/tripesock].\n\
250 -P, --pidfile=FILE      Write process-id to FILE.\n\
251 \n\
252 -s, --spawn             Start server rather than connecting.\n\
253 -p, --spawn-path=PATH   Specify path to executable.\n\
254 -S, --spawn-args=ARGS   Specify comma-separated arguments.\n\
255 \n\
256 -l, --syslog            Log messages to system log.\n\
257 -f, --logfile=FILE      Log messages to FILE.\n\
258 -w, --warnings          Show warnings when running commands.\n\
259 ", fp);
260 }
261
262 int main(int argc, char *argv[])
263 {
264   const char *dir = CONFIGDIR;
265   const char *sock = SOCKETDIR "/tripesock";
266   const char *spawnpath = "tripe";
267   string_v spawnopts = DA_INIT;
268   char *p;
269   FILE *pidfp = 0;
270
271   ego(argv[0]);
272
273   if ((p = getenv("TRIPEDIR")) != 0)
274     dir = p;
275
276   /* --- Parse the arguments --- */
277
278   for (;;) {
279     static const struct option opts[] = {
280       { "help",         0,              0,      'h' },
281       { "version",      0,              0,      'v' },
282       { "usage",        0,              0,      'u' },
283       { "daemon",       0,              0,      'D' },
284       { "directory",    OPTF_ARGREQ,    0,      'd' },
285       { "admin-socket", OPTF_ARGREQ,    0,      'a' },
286       { "spawn",        0,              0,      's' },
287       { "spawn-path",   OPTF_ARGREQ,    0,      'p' },
288       { "spawn-args",   OPTF_ARGREQ,    0,      'S' },
289       { "syslog",       0,              0,      'l' },
290       { "logfile",      OPTF_ARGREQ,    0,      'f' },
291       { "warnings",     0,              0,      'w' },
292       { "pidfile",      OPTF_ARGREQ,    0,      'P' },
293       { 0,              0,              0,      0 }
294     };
295
296     int i = mdwopt(argc, argv, "+hvuDd:a:sp:S:lwf:nP:", opts, 0, 0, 0);
297     if (i < 0)
298       break;
299     switch (i) {
300       case 'h':
301         help(stdout);
302         exit(0);
303       case 'v':
304         version(stdout);
305         exit(0);
306       case 'u':
307         usage(stdout);
308         exit(0);
309       case 'D':
310         f |= f_daemon | f_noinput;
311         break;
312       case 'd':
313         dir = optarg;
314         break;
315       case 'a':
316         sock = optarg;
317         break;
318       case 's':
319         f |= f_spawn;
320         break;
321       case 'p':
322         f |= f_spawn;
323         spawnpath = optarg;
324         break;
325       case 'S':
326         f |= f_spawn | f_spawnopts;
327         for (p = strtok(optarg, ","); p; p = strtok(0, ","))
328           DA_PUSH(&spawnopts, p);
329         break;
330       case 'l':
331         f |= f_syslog | f_noinput;
332         break;
333       case 'w':
334         f |= f_warn;
335         break;
336       case 'f':
337         logname = optarg;
338         f |= f_noinput;
339         break;
340       case 'P':
341         pidfile = optarg;
342         break;
343       default:
344         f |= f_bogus;
345         break;
346     }
347   }
348   if ((f & f_bogus) || ((f & f_noinput) && optind < argc)) {
349     usage(stderr);
350     exit(EXIT_FAILURE);
351   }
352
353   /* --- Set various things up --- */
354
355   if (chdir(dir)) {
356     die(EXIT_FAILURE, "couldn't set `%s' as current directory: %s",
357         dir, strerror(errno));
358   }
359   if (logname)
360     logfile(logname);
361   if (!pidfile && (f & f_daemon) && ((f & f_syslog) || logname))
362     pidfile = "tripectl.pid";
363   if (pidfile && (pidfp = fopen(pidfile, "w")) == 0) {
364     die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
365         pidfile, strerror(errno));
366   }
367   signal(SIGINT, sigdie);
368   signal(SIGQUIT, sigdie);
369   signal(SIGTERM, sigdie);
370   atexit(cleanup);
371
372   /* --- Connect to the server --- */
373
374   if (f & f_spawn) {
375     int pfd[2];
376     pid_t kid;
377     struct sigaction sa;
378     sigset_t newmask, oldmask;
379
380     sa.sa_handler = reap;
381     sigemptyset(&sa.sa_mask);
382     sa.sa_flags = SA_NOCLDSTOP;
383 #ifdef SA_RESTART
384     sa.sa_flags |= SA_RESTART;
385 #endif
386     sigaction(SIGCHLD, &sa, 0);
387
388     DA_UNSHIFT(&spawnopts, (char *)sock);
389     DA_UNSHIFT(&spawnopts, "-a");
390     DA_UNSHIFT(&spawnopts, "-d.");
391     DA_UNSHIFT(&spawnopts, (char *)spawnpath);
392     DA_PUSH(&spawnopts, 0);
393     if (socketpair(PF_UNIX, SOCK_STREAM, 0, pfd))
394       die(EXIT_FAILURE, "error from socketpair: %s", strerror(errno));
395     sigemptyset(&newmask);
396     sigaddset(&newmask, SIGCHLD);
397     sigprocmask(SIG_BLOCK, &newmask, &oldmask);
398     if ((kid = fork()) < 0)
399       die(EXIT_FAILURE, "fork failed: %s", strerror(errno));
400     if (!kid) {
401       dup2(pfd[1], STDIN_FILENO);
402       dup2(pfd[1], STDOUT_FILENO);
403       close(pfd[1]);
404       close(pfd[0]);
405       if (logfp)
406         fclose(logfp);
407       if (pidfp)
408         fclose(pidfp);
409       closelog();
410       if (f & f_daemon)
411         u_detach();
412       execvp(DA(&spawnopts)[0], DA(&spawnopts));
413       die(127, "couldn't exec `%s': %s", spawnpath, strerror(errno));
414     }
415     sigprocmask(SIG_SETMASK, &oldmask, 0);
416     fd = pfd[0];
417     close(pfd[1]);
418   } else {
419     struct sockaddr_un sun;
420     size_t sz = strlen(sock) + 1;
421     if (sz > sizeof(sun.sun_path))
422       die(EXIT_FAILURE, "socket name `%s' too long", sock);
423     memset(&sun, 0, sizeof(sun));
424     sun.sun_family = AF_UNIX;
425     memcpy(sun.sun_path, sock, sz);
426     sz = sz + offsetof(struct sockaddr_un, sun_path);
427     if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
428       die(EXIT_FAILURE, "error making socket: %s", strerror(errno));
429     if (connect(fd, (struct sockaddr *)&sun, sz)) {
430       die(EXIT_FAILURE, "error connecting to `%s': %s",
431           sun.sun_path, strerror(errno));
432     }
433   }
434
435   if (f & f_daemon) {
436     if (u_daemon())
437       die(EXIT_FAILURE, "error becoming daemon: %s", strerror(errno));
438   }
439   if (pidfp) {
440     fprintf(pidfp, "%li\n", (long)getpid());
441     fclose(pidfp);
442   }
443
444   /* --- If we're meant to be interactive, do that --- */
445
446   if (!(f & f_noinput) && optind == argc) {
447     sel_state sel;
448     selbuf bu, bs;
449
450     sel_init(&sel);
451     selbuf_init(&bu, &sel, STDIN_FILENO, uline, &bu);
452     selbuf_init(&bs, &sel, fd, sline, &bs);
453     for (;;) {
454       if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
455         die(EXIT_FAILURE, "select failed: %s", strerror(errno));
456     }
457   }
458
459   /* --- If there's a command, submit it --- */
460
461   if (optind < argc) {
462     dstr d = DSTR_INIT;
463     dstr_puts(&d, argv[optind++]);
464     while (optind < argc) {
465       dstr_putc(&d, ' ');
466       dstr_puts(&d, argv[optind++]);
467     }
468     dstr_putc(&d, '\n');
469     write(fd, d.buf, d.len);
470     shutdown(fd, 1);
471     dstr_destroy(&d);
472     f |= f_command;
473   }
474
475   /* --- Pull everything else out of the box --- */
476
477   {
478     sel_state sel;
479     selbuf b;
480     sig hup;
481
482     sel_init(&sel);
483     selbuf_init(&b, &sel, fd, cline, 0);
484
485     if (f & f_syslog)
486       openlog(QUIS, 0, LOG_DAEMON);
487     if (logfp) {
488       sig_init(&sel);
489       sig_add(&hup, SIGHUP, sighup, 0);
490     }
491     for (;;) {
492       if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
493         die(EXIT_FAILURE, "select failed: %s", strerror(errno));
494     }
495   }
496
497   return (0);
498 }
499
500 /*----- That's all, folks -------------------------------------------------*/