chiark / gitweb /
Add sequence number protection.
[tripe] / client.c
CommitLineData
410c8acf 1/* -*-c-*-
2 *
9dd01bc6 3 * $Id: client.c,v 1.3 2001/02/04 17:10:15 mdw Exp $
410c8acf 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 $
9dd01bc6 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 *
73189848 36 * Revision 1.2 2001/02/04 01:17:54 mdw
37 * Create a configuration header file to tidy up command lines.
38 *
410c8acf 39 * Revision 1.1 2001/02/03 20:26:37 mdw
40 * Initial checkin.
41 *
42 */
43
44/*----- Header files ------------------------------------------------------*/
45
73189848 46#include "config.h"
47
410c8acf 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
92static FILE *logfp = 0;
93static unsigned f = 0;
94static int fd;
9dd01bc6 95static volatile sig_atomic_t reopen = 0;
410c8acf 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
109static 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
9dd01bc6 118static void sighup(int sig)
119{
120 reopen = 1;
121}
122
410c8acf 123static 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
132static 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
174static 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
184static 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
9dd01bc6 198static 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
410c8acf 209static void version(FILE *fp)
210{
211 pquis(fp, "$, TrIPE version " VERSION "\n");
212}
213
214static void usage(FILE *fp)
215{
216 pquis(fp, "\
217Usage:\n\
218 $ [-w] [-options] [command [args]...]\n\
219 $ [-Dl] [-f file] [-options]\n\
220Options:\n\
221 [-s] [-d directory] [-a socket] [-p program] [-S arg,arg,...]\n\
222");
223}
224
225static void help(FILE *fp)
226{
227 version(fp);
228 fputc('\n', fp);
229 usage(fp);
230 fputs("\
231\n\
232Options 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
252int 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;
9dd01bc6 258 const char *logname = 0;
410c8acf 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':
9dd01bc6 326 logname = optarg;
327 logfile(logname);
410c8acf 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
410c8acf 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)) {
9dd01bc6 358 DA_PUSH(&spawnopts, "-d");
359 DA_PUSH(&spawnopts, (char *)dir);
410c8acf 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]);
9dd01bc6 376 if (logfp)
377 fclose(logfp);
378 closelog();
410c8acf 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;
9dd01bc6 388 dstr d = DSTR_INIT;
389 dstr_putf(&d, "%s/%s", dir, sock);
390 if (d.sz + 1 > sizeof(sun.sun_path))
410c8acf 391 die(EXIT_FAILURE, "socket name `%s' too long", sock);
392 memset(&sun, 0, sizeof(sun));
393 sun.sun_family = AF_UNIX;
9dd01bc6 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);
410c8acf 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",
9dd01bc6 401 sun.sun_path, strerror(errno));
410c8acf 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);
9dd01bc6 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 }
410c8acf 455 for (;;) {
9dd01bc6 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;
410c8acf 467 die(EXIT_FAILURE, "read failed: %s", strerror(errno));
9dd01bc6 468 }
410c8acf 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 -------------------------------------------------*/