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