chiark / gitweb /
Make file descriptors be nonblocking and close-on-exec.
[tripe] / client.c
1 /* -*-c-*-
2  *
3  * $Id: client.c,v 1.3 2001/02/04 17:10:15 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 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: client.c,v $
32  * Revision 1.3  2001/02/04 17:10:15  mdw
33  * Reopen logfiles on receipt of @SIGHUP@ (not done very well).  Don't
34  * change directory -- just mangle pathnames instead.
35  *
36  * Revision 1.2  2001/02/04 01:17:54  mdw
37  * Create a configuration header file to tidy up command lines.
38  *
39  * Revision 1.1  2001/02/03 20:26:37  mdw
40  * Initial checkin.
41  *
42  */
43
44 /*----- Header files ------------------------------------------------------*/
45
46 #include "config.h"
47
48 #include <ctype.h>
49 #include <errno.h>
50 #include <signal.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <time.h>
55
56 #include <sys/types.h>
57 #include <sys/time.h>
58 #include <unistd.h>
59 #include <fcntl.h>
60 #include <sys/wait.h>
61 #include <syslog.h>
62
63 #include <sys/socket.h>
64 #include <sys/un.h>
65 #include <arpa/inet.h>
66 #include <netdb.h>
67
68 #include <mLib/alloc.h>
69 #include <mLib/darray.h>
70 #include <mLib/dstr.h>
71 #include <mLib/lbuf.h>
72 #include <mLib/mdwopt.h>
73 #include <mLib/quis.h>
74 #include <mLib/report.h>
75 #include <mLib/sel.h>
76 #include <mLib/selbuf.h>
77 #include <mLib/str.h>
78
79 #include "util.h"
80
81 #undef sun
82
83 /*----- Data structures ---------------------------------------------------*/
84
85 #ifndef STRING_V
86 #  define STRING_V
87    DA_DECL(string_v, char *);
88 #endif
89
90 /*----- Static variables --------------------------------------------------*/
91
92 static FILE *logfp = 0;
93 static unsigned f = 0;
94 static int fd;
95 static volatile sig_atomic_t reopen = 0;
96
97 #define f_bogus 1u
98 #define f_spawn 2u
99 #define f_daemon 4u
100 #define f_spawnopts 8u
101 #define f_syslog 16u
102 #define f_command 32u
103 #define f_noinput 64u
104 #define f_warn 128u
105 #define f_uclose 256u
106
107 /*----- Main code ---------------------------------------------------------*/
108
109 static void reap(int sig)
110 {
111   int s;
112   int e = errno;
113   while (waitpid(-1, &s, WNOHANG) > 0)
114     ;
115   errno = e;
116 }
117
118 static void sighup(int sig)
119 {
120   reopen = 1;
121 }
122
123 static void writelog(const char *cat, const char *msg)
124 {
125   char buf[256];
126   time_t t = time(0);
127   struct tm *tm = localtime(&t);
128   strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
129   fprintf(logfp, "%s %s: %s\n", buf, cat, msg);
130 }
131
132 static void cline(char *p, void *b)
133 {
134   char *q;
135   if (!p) {
136     if (f & f_command)
137       die(EXIT_FAILURE, "server dropped the connection");
138     exit(0);
139   }
140   q = str_getword(&p);
141   if (!q)
142     return;
143   if (strcmp(q, "WARN") == 0) {
144     if (f & f_syslog)
145       syslog(LOG_WARNING, "%s", p);
146     if (logfp)
147       writelog("warning", p);
148     if (f & f_warn)
149       fprintf(stderr, "Warning: %s\n", p);
150   } else if (strcmp(q, "TRACE") == 0) {
151     if (f & f_syslog)
152       syslog(LOG_DEBUG, "%s", p);
153     if (logfp)
154       writelog("debug", p);
155   } else if (!(f & f_command)) {
156     if (f & f_syslog)
157       syslog(LOG_ERR, "unexpected output `%s %s'", q, p);
158     if (logfp) {
159       dstr d = DSTR_INIT;
160       dstr_putf(&d, "unexpected output `%s %s'", q, p);
161       writelog("error", d.buf);
162       dstr_destroy(&d);
163     }
164   } else if (strcmp(q, "ERR") == 0)
165     die(EXIT_FAILURE, "%s", p);
166   else if (strcmp(q, "INFO") == 0)
167     puts(p);
168   else if (strcmp(q, "OK") == 0)
169     exit(0);
170   else
171     die(EXIT_FAILURE, "unexpected output `%s %s'", q, p); 
172 }
173
174 static void sline(char *p, void *b)
175 {
176   if (!p) {
177     if (!(f & f_uclose))
178       moan("server closed the connection");
179     exit(0);
180   }
181   puts(p);
182 }
183
184 static void uline(char *p, void *b)
185 {
186   size_t sz;
187   if (!p) {
188     selbuf_destroy(b);
189     shutdown(fd, 1);
190     f |= f_uclose;
191   } else {
192     sz = strlen(p);
193     p[sz] = '\n';
194     write(fd, p, sz + 1);
195   }
196 }
197
198 static void logfile(const char *name)
199 {
200   if (logfp)
201     fclose(logfp);
202   if ((logfp = fopen(name, "a")) == 0) {
203     die(EXIT_FAILURE, "error opening logfile `%s': %s",
204         name, strerror(errno));
205   }
206   setvbuf(logfp, 0, _IOLBF, BUFSIZ);
207 }
208
209 static void version(FILE *fp)
210 {
211   pquis(fp, "$, TrIPE version " VERSION "\n");
212 }
213
214 static void usage(FILE *fp)
215 {
216   pquis(fp, "\
217 Usage:\n\
218         $ [-w] [-options] [command [args]...]\n\
219         $ [-Dl] [-f file] [-options]\n\
220 Options:\n\
221         [-s] [-d directory] [-a socket] [-p program] [-S arg,arg,...]\n\
222 ");
223 }
224
225 static void help(FILE *fp)
226 {
227   version(fp);
228   fputc('\n', fp);
229   usage(fp);
230   fputs("\
231 \n\
232 Options in full:\n\
233 \n\
234 -h, --help              Show this help text.\n\
235 -v, --version           Show version number.\n\
236 -u, --usage             Show brief usage message.\n\
237 \n\
238 -D, --daemon            Become a background task after connecting.\n\
239 -d, --directory=DIR     Select current directory [default /var/lib/tripe]\n\
240 -a, --admin-socket=FILE Select socket to connect to.\n\
241 \n\
242 -s, --spawn             Start server rather than connecting.\n\
243 -p, --spawn-path=PATH   Specify path to executable.\n\
244 -S, --spawn-args=ARGS   Specify comma-separated arguments.\n\
245 \n\
246 -l, --syslog            Log messages to system log.\n\
247 -f, --logfile=FILE      Log messages to FILE.\n\
248 -w, --warnings          Show warnings when running commands.\n\
249 ", fp);
250 }
251
252 int main(int argc, char *argv[])
253 {
254   const char *dir = "/var/lib/tripe";
255   const char *sock = "tripesock";
256   const char *spawnpath = "tripe";
257   string_v spawnopts = DA_INIT;
258   const char *logname = 0;
259   char *p;
260
261   ego(argv[0]);
262
263   if ((p = getenv("TRIPEDIR")) != 0)
264     dir = p;
265
266   /* --- Parse the arguments --- */
267
268   for (;;) {
269     static const struct option opts[] = {
270       { "help",         0,              0,      'h' },
271       { "version",      0,              0,      'v' },
272       { "usage",        0,              0,      'u' },
273       { "daemon",       0,              0,      'D' },
274       { "directory",    OPTF_ARGREQ,    0,      'd' },
275       { "admin-socket", OPTF_ARGREQ,    0,      'a' },
276       { "spawn",        0,              0,      's' },
277       { "spawn-path",   OPTF_ARGREQ,    0,      'p' },
278       { "spawn-args",   OPTF_ARGREQ,    0,      'S' },
279       { "syslog",       0,              0,      'l' },
280       { "logfile",      OPTF_ARGREQ,    0,      'f' },
281       { "warnings",     0,              0,      'w' },
282       { 0,              0,              0,      0 }
283     };
284
285     int i = mdwopt(argc, argv, "hvuDd:a:sp:S:lwf:n", opts, 0, 0, 0);
286     if (i < 0)
287       break;
288     switch (i) {
289       case 'h':
290         help(stdout);
291         exit(0);
292       case 'v':
293         version(stdout);
294         exit(0);
295       case 'u':
296         usage(stdout);
297         exit(0);
298       case 'D':
299         f |= f_daemon | f_noinput;
300         break;
301       case 'd':
302         dir = optarg;
303         break;
304       case 'a':
305         sock = optarg;
306         break;
307       case 's':
308         f |= f_spawn;
309         break;
310       case 'p':
311         f |= f_spawn;
312         spawnpath = optarg;
313         break;
314       case 'S':
315         f |= f_spawn | f_spawnopts;
316         for (p = strtok(optarg, ","); p; p = strtok(0, ","))
317           DA_PUSH(&spawnopts, p);
318         break;
319       case 'l':
320         f |= f_syslog | f_noinput;
321         break;
322       case 'w':
323         f |= f_warn;
324         break;
325       case 'f':
326         logname = optarg;
327         logfile(logname);
328         f |= f_noinput;
329         break;
330       default:
331         f |= f_bogus;
332         break;
333     }
334   }
335   if ((f & f_bogus) || ((f & f_noinput) && optind < argc)) {
336     usage(stderr);
337     exit(EXIT_FAILURE);
338   }
339
340   /* --- Connect to the server --- */
341
342   if (f & f_spawn) {
343     int pfd[2];
344     pid_t kid;
345     struct sigaction sa;
346     sigset_t newmask, oldmask;
347
348     sa.sa_handler = reap;
349     sigemptyset(&sa.sa_mask);
350     sa.sa_flags = SA_NOCLDSTOP;
351 #ifdef SA_RESTART
352     sa.sa_flags |= SA_RESTART;
353 #endif
354     sigaction(SIGCHLD, &sa, 0);
355
356     DA_UNSHIFT(&spawnopts, (char *)spawnpath);
357     if (!(f & f_spawnopts)) {
358       DA_PUSH(&spawnopts, "-d");
359       DA_PUSH(&spawnopts, (char *)dir);
360       DA_PUSH(&spawnopts, "-a");
361       DA_PUSH(&spawnopts, (char *)sock);
362     }
363     DA_PUSH(&spawnopts, 0);
364     if (socketpair(PF_UNIX, SOCK_STREAM, 0, pfd))
365       die(EXIT_FAILURE, "error from socketpair: %s", strerror(errno));
366     sigemptyset(&newmask);
367     sigaddset(&newmask, SIGCHLD);
368     sigprocmask(SIG_BLOCK, &newmask, &oldmask);
369     if ((kid = fork()) < 0)
370       die(EXIT_FAILURE, "fork failed: %s", strerror(errno));
371     if (!kid) {
372       dup2(pfd[1], STDIN_FILENO);
373       dup2(pfd[1], STDOUT_FILENO);
374       close(pfd[1]);
375       close(pfd[0]);
376       if (logfp)
377         fclose(logfp);
378       closelog();
379       execvp(DA(&spawnopts)[0], DA(&spawnopts));
380       die(127, "couldn't exec `%s': %s", spawnpath, strerror(errno));
381     }
382     sigprocmask(SIG_SETMASK, &oldmask, 0);
383     fd = pfd[0];
384     close(pfd[1]);
385   } else {
386     struct sockaddr_un sun;
387     size_t sz = strlen(sock) + 1;
388     dstr d = DSTR_INIT;
389     dstr_putf(&d, "%s/%s", dir, sock);
390     if (d.sz + 1 > sizeof(sun.sun_path))
391       die(EXIT_FAILURE, "socket name `%s' too long", sock);
392     memset(&sun, 0, sizeof(sun));
393     sun.sun_family = AF_UNIX;
394     memcpy(sun.sun_path, d.buf, d.sz + 1);
395     sz = d.sz + offsetof(struct sockaddr_un, sun_path) + 1;
396     dstr_destroy(&d);
397     if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
398       die(EXIT_FAILURE, "error making socket: %s", strerror(errno));
399     if (connect(fd, (struct sockaddr *)&sun, sz)) {
400       die(EXIT_FAILURE, "error connecting to `%s': %s",
401           sun.sun_path, strerror(errno));
402     }
403   }
404
405   if (f & f_daemon) {
406     if (u_daemon())
407       die(EXIT_FAILURE, "error becoming daemon: %s", strerror(errno));
408   }
409
410   /* --- If we're meant to be interactive, do that --- */
411
412   if (!(f & f_noinput) && optind == argc) {
413     sel_state sel;
414     selbuf bu, bs;
415
416     sel_init(&sel);
417     selbuf_init(&bu, &sel, STDIN_FILENO, uline, &bu);
418     selbuf_init(&bs, &sel, fd, sline, &bs);
419     for (;;) {
420       if (sel_select(&sel))
421         die(EXIT_FAILURE, "select failed: %s", strerror(errno));
422     }
423   }
424
425   /* --- If there's a command, submit it --- */
426
427   if (optind < argc) {
428     dstr d = DSTR_INIT;
429     dstr_puts(&d, argv[optind++]);
430     while (optind < argc) {
431       dstr_putc(&d, ' ');
432       dstr_puts(&d, argv[optind++]);
433     }
434     dstr_putc(&d, '\n');
435     write(fd, d.buf, d.len);
436     shutdown(fd, 1);
437     dstr_destroy(&d);
438     f |= f_command;
439   }
440
441   /* --- Pull everything else out of the box --- */
442
443   {
444     lbuf b;
445     lbuf_init(&b, cline, 0);
446     if (f & f_syslog)
447       openlog(QUIS, 0, LOG_DAEMON);
448     if (logfp) {
449       struct sigaction sa;
450       sa.sa_handler = sighup;
451       sa.sa_flags = 0;
452       sigemptyset(&sa.sa_mask);
453       sigaction(SIGHUP, &sa, 0);
454     }
455     for (;;) {
456       size_t sz;
457       ssize_t n;
458       if (reopen) {
459         logfile(logname);
460         reopen = 0;
461       }
462       sz = lbuf_free(&b, &p);
463       n = read(fd, p, sz);
464       if (n < 0) {
465         if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
466           continue;
467         die(EXIT_FAILURE, "read failed: %s", strerror(errno));
468       }
469       if (n == 0)
470         break;
471       lbuf_flush(&b, p, n);
472     }
473     lbuf_close(&b);
474   }
475
476   return (0);
477 }
478
479 /*----- That's all, folks -------------------------------------------------*/