chiark / gitweb /
And a typo fix.
[fwd] / file.c
1 /* -*-c-*-
2  *
3  * $Id: file.c,v 1.6 2003/11/29 20:36:07 mdw Exp $
4  *
5  * File source and target
6  *
7  * (c) 1999 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the `fw' port forwarder.
13  *
14  * `fw' 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  * `fw' 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 `fw'; 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: file.c,v $
32  * Revision 1.6  2003/11/29 20:36:07  mdw
33  * Privileged outgoing connections.
34  *
35  * Revision 1.5  2002/02/22 23:43:32  mdw
36  * Call @xfree@ rather than @free@.
37  *
38  * Revision 1.4  2001/02/03 20:30:03  mdw
39  * Support re-reading config files on SIGHUP.
40  *
41  * Revision 1.3  1999/12/22 15:43:47  mdw
42  * Fix log messages.
43  *
44  * Revision 1.2  1999/10/22 22:47:13  mdw
45  * New (empty) endpoint method required.
46  *
47  * Revision 1.1  1999/07/26 23:33:32  mdw
48  * New sources and targets.
49  *
50  */
51
52 /*----- Header files ------------------------------------------------------*/
53
54 #include "config.h"
55
56 #include <ctype.h>
57 #include <errno.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61
62 #include <sys/types.h>
63 #include <sys/stat.h>
64 #include <unistd.h>
65 #include <fcntl.h>
66
67 #include <mLib/alloc.h>
68 #include <mLib/dstr.h>
69 #include <mLib/fdflags.h>
70 #include <mLib/sub.h>
71
72 #include "conf.h"
73 #include "endpt.h"
74 #include "fattr.h"
75 #include "file.h"
76 #include "fw.h"
77 #include "reffd.h"
78 #include "scan.h"
79 #include "source.h"
80 #include "target.h"
81
82 /*----- Data structures ---------------------------------------------------*/
83
84 /* --- File specification --- */
85
86 typedef struct fspec {
87   unsigned type;
88   union {
89     reffd *fd;
90     char *name;
91   } u;
92 } fspec;
93
94 #define FTYPE_GUESS 0u
95 #define FTYPE_FD 1u
96 #define FTYPE_NAME 2u
97 #define FTYPE_NULL 3u
98
99 /* --- File options --- */
100
101 typedef struct fopts {
102   unsigned of;
103 } fopts;
104
105 /* --- File data block --- */
106
107 typedef struct fdata {
108   fspec in, out;
109   fattr fa;
110   fopts fo;
111 } fdata;
112
113 /* --- File source block --- */
114
115 typedef struct fsource {
116   source s;
117   fdata f;
118 } fsource;  
119
120 /* --- File target block --- */
121
122 typedef struct ftarget {
123   target t;
124   fdata f;
125 } ftarget;
126
127 /*----- Static variables --------------------------------------------------*/
128
129 static fopts file_opts = { O_TRUNC };
130
131 static reffd *rstdin, *rstdout;
132 static reffd *rnull;
133
134 /*----- File endpoint operations ------------------------------------------*/
135
136 /* --- @wclose@ --- */
137
138 static void fept_wclose(endpt *e)
139 {
140   if (e->out) {
141     REFFD_DEC(e->out);
142     e->out = 0;
143   }
144 }
145
146 /* --- @close@ --- */
147
148 static void fept_close(endpt *e)
149 {
150   if (e->in)
151     REFFD_DEC(e->in);
152   if (e->out)
153     REFFD_DEC(e->out);
154   fw_dec();
155   DESTROY(e);
156 }
157
158 /* --- Endpoint operations table --- */
159
160 static endpt_ops fept_ops = { 0, 0, fept_wclose, fept_close };
161
162 /*----- General operations on sources and targets -------------------------*/
163
164 /* --- @file_null@ --- *
165  *
166  * Arguments:   @void *p@ = an uninteresting argument
167  *
168  * Returns:     ---
169  *
170  * Use:         Removes the reference to @rnull@ when the file closes.
171  */
172
173 static void file_null(void *p)
174 {
175   rnull = 0;
176 }
177
178 /* --- @file_nullref@ --- *
179  *
180  * Arguments:   ---
181  *
182  * Returns:     A reference to a file descriptor open on /dev/null.
183  *
184  * Use:         Obtains a file descriptor for /dev/null.  The reference
185  *              @rnull@ is `weak', so that I only have a descriptor open when
186  *              I actually need one.
187  */
188
189 static reffd *file_nullref(void)
190 {
191   if (rnull)
192     REFFD_INC(rnull);
193   else {
194     int n;
195     if ((n = open("/dev/null", O_RDWR)) < 0) {
196       fw_log(-1, "couldn't open `/dev/null': %s", strerror(errno));
197       return (0);
198     }
199     fdflags(n, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
200     rnull = reffd_init(n);
201     reffd_handler(rnull, file_null, 0);
202   }
203   return (rnull);
204 }  
205
206 /* --- @file_fspec@ --- *
207  *
208  * Arguments:   @fspec *f@ = pointer to filespec to fill in
209  *              @scanner *sc@ = pointer to scanner to read from
210  *
211  * Returns:     ---
212  *
213  * Use:         Reads in a file spec.
214  */
215
216 static void file_fspec(fspec *f, scanner *sc)
217 {
218   int c = 0;
219   unsigned type = FTYPE_GUESS;
220   reffd *fd = 0;
221
222   /* --- Set up references to @stdin@ and @stdout@ --- */
223
224   if (!rstdin) {
225     fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
226     fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
227     rstdin = reffd_init(STDIN_FILENO);
228     rstdout = reffd_init(STDOUT_FILENO);
229   }
230
231   /* --- Try to get an access type --- */
232
233   if (sc->t == ':') {
234     c = 1;
235     token(sc);
236   }
237
238   if (sc->t == CTOK_WORD) {
239     int i = conf_enum(sc, "fd,name,null", c ? ENUM_ABBREV : ENUM_NONE,
240                       "file access type");
241     type = i + 1;
242     if (type && sc->t == ':')
243       token(sc);
244   }
245
246   /* --- Check for a valid file descriptor name --- */
247
248   if (sc->t == CTOK_WORD && type != FTYPE_NAME && type != FTYPE_NULL) {
249     if (strcmp(sc->d.buf, "stdin") == 0) {
250       fd = rstdin;
251       REFFD_INC(rstdin);
252     } else if (strcmp(sc->d.buf, "stdout") == 0) {
253       fd = rstdout;
254       REFFD_INC(rstdout);
255     }
256     if (fd)
257       REFFD_INC(fd);
258     else if (isdigit((unsigned char)*sc->d.buf)) {
259       int nfd = atoi(sc->d.buf);
260       switch (nfd) {
261         case STDIN_FILENO: fd = rstdin; REFFD_INC(rstdin); break;
262         case STDOUT_FILENO: fd = rstdout; REFFD_INC(rstdout); break;
263         default:
264           fd = reffd_init(nfd);
265           fdflags(nfd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
266           break;
267       }
268     }
269   }
270
271   /* --- Sort out what to do as a result --- */
272
273   if (type == FTYPE_NULL)
274     f->type = FTYPE_NULL;
275   else if (fd) {
276     f->type = FTYPE_FD;
277     f->u.fd = fd;
278     token(sc);  
279   } else if (type == FTYPE_FD) {
280     if (sc->t == CTOK_WORD)
281       error(sc, "bad file descriptor `%s'", sc->d.buf);
282     else
283       error(sc, "parse error, expected file descriptor");
284   } else {
285     dstr d = DSTR_INIT;
286     conf_name(sc, '/', &d);
287     f->type = FTYPE_NAME;
288     f->u.name = xstrdup(d.buf);
289     dstr_destroy(&d);
290   }
291 }
292
293 /* --- @read@ --- */
294
295 static void file_read(fdata *f, scanner *sc)
296 {
297   file_fspec(&f->in, sc);
298   if (sc->t != ',') {
299     f->out = f->in;
300     if (f->out.type == FTYPE_FD && f->out.u.fd == rstdin)
301       f->out.u.fd = rstdout;
302   } else {
303     token(sc);
304     file_fspec(&f->out, sc);
305   }
306   f->fa = fattr_global;
307   f->fo = file_opts;
308 }
309
310 /* --- @print@ --- */
311
312 static void file_pfspec(fspec *f, dstr *d)
313 {
314   switch (f->type) {
315     case FTYPE_FD:
316       switch (f->u.fd->fd) {
317         case STDIN_FILENO:
318           dstr_puts(d, "stdin");
319           break;
320         case STDOUT_FILENO:
321           dstr_puts(d, "stdout");
322           break;
323         default:
324           dstr_putf(d, "fd:%i", f->u.fd->fd);
325           break;
326       }
327       break;
328     case FTYPE_NAME:
329       dstr_putf(d, "name:%s", f->u.name);
330       break;
331     case FTYPE_NULL:
332       dstr_puts(d, "null");
333       break;
334   }       
335 }
336
337 static void file_desc(fdata *f, dstr *d)
338 {
339   dstr_puts(d, "file ");
340   file_pfspec(&f->in, d);
341   dstr_puts(d, ", ");
342   file_pfspec(&f->out, d);
343 }
344
345 /* --- @option@ --- */
346
347 static int file_option(fdata *f, scanner *sc)
348 {
349   fopts *fo = f ? &f->fo : &file_opts;
350
351   CONF_BEGIN(sc, "file", "file")
352
353   /* --- Handle file-specific options --- */
354
355   if (strcmp(sc->d.buf, "create") == 0) {
356     token(sc);
357     if (sc->t == '=')
358       token(sc);
359     switch (conf_enum(sc, "no,yes", ENUM_ABBREV, "create status")) {
360       case 0: fo->of &= ~O_CREAT; break;
361       case 1: fo->of |= O_CREAT; break;
362     }
363     CONF_ACCEPT;
364   }
365
366   else if (strcmp(sc->d.buf, "open") == 0) {
367     token(sc);
368     if (sc->t == '=')
369       token(sc);
370     fo->of &= ~(O_TRUNC | O_APPEND | O_EXCL);
371     switch (conf_enum(sc, "no,truncate,append",
372                       ENUM_ABBREV, "open status")) {
373       case 0: fo->of |= O_EXCL; break;
374       case 1: fo->of |= O_TRUNC; break;
375       case 2: fo->of |= O_APPEND; break;
376     }
377     CONF_ACCEPT;
378   }
379
380   /* --- See if it's a file attribute --- */
381
382   {
383     fattr *fa = f ? &f->fa : &fattr_global;
384     if (fattr_option(sc, fa))
385       CONF_ACCEPT;
386   }
387
388   /* --- Nobody understood it --- */
389
390   CONF_END;
391 }
392
393 /* --- @endpt@ --- */
394
395 static endpt *file_endpt(fdata *f, const char *desc)
396 {
397   reffd *in = 0, *out = 0;
398   endpt *e;
399
400   /* --- Make the input file first --- */
401
402   switch (f->in.type) {
403     case FTYPE_NULL:
404       in = file_nullref();
405       break;
406     case FTYPE_FD:
407       in = f->in.u.fd;
408       REFFD_INC(in);
409       break;
410     case FTYPE_NAME: {
411       int fd;
412       if ((fd = open(f->in.u.name, O_RDONLY | O_NONBLOCK)) < 0) {
413         fw_log(-1, "[%s] couldn't open `%s' for reading: %s",
414                desc, f->in.u.name, strerror(errno));
415         return (0);
416       }
417       in = reffd_init(fd);
418     } break;
419   }
420
421   /* --- Make the output file second --- */
422
423   switch (f->out.type) {
424     case FTYPE_NULL:
425       out = file_nullref();
426       break;
427     case FTYPE_FD:
428       out = f->out.u.fd;
429       REFFD_INC(out);
430       break;
431     case FTYPE_NAME: {
432       int m = f->fo.of | O_WRONLY | O_NONBLOCK;
433       int fd = -1;
434
435       /* --- First try creating the file --- *
436        *
437        * This lets me know whether to set the file attributes or not.  It
438        * also stands a chance of noticing people playing silly beggars with
439        * dangling symlinks.
440        */
441
442       if ((m & O_CREAT) &&
443           (fd = open(f->out.u.name, m | O_EXCL, f->fa.mode)) < 0 &&
444           (errno != EEXIST || (m & O_EXCL))) {
445         fw_log(-1, "[%s] couldn't create `%s': %s",
446                desc, f->out.u.name, strerror(errno));
447         REFFD_DEC(in);
448         return (0);
449       }
450
451       if (fd != -1) {
452         if (fattr_apply(f->out.u.name, &f->fa)) {
453           fw_log(-1, "[%s] couldn't apply file attributes to `%s': %s",
454                  desc, f->out.u.name, strerror(errno));
455         }
456       } else if ((fd = open(f->out.u.name, m & ~O_CREAT)) < 0) {
457         fw_log(-1, "[%s] couldn't open `%s': %s",
458                desc, f->out.u.name, strerror(errno));
459         REFFD_DEC(in);
460         return (0);
461       }
462
463       fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
464       out = reffd_init(fd);
465     } break;
466   }
467
468   /* --- Set up the endpoint --- */
469
470   e = CREATE(endpt);
471   e->ops = &fept_ops;
472   e->other = 0;
473   e->f = EPF_FILE;
474   e->t = 0;
475   e->in = in;
476   e->out = out;
477   fw_inc();
478   return (e);
479 }
480
481 /* --- @destroy@ --- */
482
483 static void file_destroy(fdata *f)
484 {
485   if (f->in.type == FTYPE_NAME)
486     xfree(f->in.u.name);
487   if (f->out.type == FTYPE_NAME)
488     xfree(f->out.u.name);
489 }
490
491 /*----- File source description -------------------------------------------*/
492
493 /* --- @option@ --- */
494
495 static int fsource_option(source *s, scanner *sc)
496 {
497   fsource *fs = (fsource *)s;
498   return (file_option(fs ? &fs->f : 0, sc));
499 }
500
501 /* --- @read@ --- */
502
503 static source *fsource_read(scanner *sc)
504 {
505   fsource *fs;
506
507   if (!conf_prefix(sc, "file"))
508     return (0);
509   fs = CREATE(fsource);
510   fs->s.ops = &fsource_ops;
511   fs->s.desc = 0;
512   file_read(&fs->f, sc);
513   return (&fs->s);
514 }
515
516 /* --- @attach@ --- */
517
518 static void fsource_destroy(source */*s*/);
519
520 static void fsource_attach(source *s, scanner *sc, target *t)
521 {
522   fsource *fs = (fsource *)s;
523   endpt *e, *ee;
524
525   /* --- Set up the source description string --- */
526
527   {
528     dstr d = DSTR_INIT;
529     file_desc(&fs->f, &d);
530     dstr_puts(&d, " -> ");
531     dstr_puts(&d, t->desc);
532     fs->s.desc = xstrdup(d.buf);
533     dstr_destroy(&d);
534   }
535
536   /* --- And then create the endpoints --- */
537
538   if ((ee = t->ops->create(t, fs->s.desc)) == 0)
539     goto tidy;
540   if ((e = file_endpt(&fs->f, fs->s.desc)) == 0) {
541     ee->ops->close(ee);
542     goto tidy;
543   }
544   endpt_join(e, ee);
545
546   /* --- Dispose of the source and target now --- */
547
548 tidy:
549   t->ops->destroy(t);
550   fsource_destroy(&fs->s);
551 }
552
553 /* --- @destroy@ --- */
554
555 static void fsource_destroy(source *s)
556 {
557   fsource *fs = (fsource *)s;
558   xfree(fs->s.desc);
559   file_destroy(&fs->f);
560   DESTROY(fs);
561 }
562
563 /* --- File source operations --- */
564
565 source_ops fsource_ops = {
566   "file",
567   fsource_option, fsource_read, fsource_attach, fsource_destroy
568 };
569
570 /*----- File target description -------------------------------------------*/
571
572 /* --- @option@ --- */
573
574 static int ftarget_option(target *t, scanner *sc)
575 {
576   ftarget *ft = (ftarget *)t;
577   return (file_option(ft ? &ft->f : 0, sc));
578 }
579
580 /* --- @read@ --- */
581
582 static target *ftarget_read(scanner *sc)
583 {
584   ftarget *ft;
585   dstr d = DSTR_INIT;
586
587   if (!conf_prefix(sc, "file"))
588     return (0);
589   ft = CREATE(ftarget);
590   ft->t.ops = &ftarget_ops;
591   file_read(&ft->f, sc);
592   file_desc(&ft->f, &d);
593   ft->t.desc = xstrdup(d.buf);
594   dstr_destroy(&d);
595   return (&ft->t);
596 }
597
598 /* --- @create@ --- */
599
600 static endpt *ftarget_create(target *t, const char *desc)
601 {
602   ftarget *ft = (ftarget *)t;
603   endpt *e = file_endpt(&ft->f, desc);
604   return (e);
605 }
606
607 /* --- @destroy@ --- */
608
609 static void ftarget_destroy(target *t)
610 {
611   ftarget *ft = (ftarget *)t;
612   file_destroy(&ft->f);
613   xfree(ft->t.desc);
614   DESTROY(ft);
615 }
616
617 /* --- File target operations --- */
618
619 target_ops ftarget_ops = {
620   "file",
621   ftarget_option, ftarget_read, 0, ftarget_create, ftarget_destroy
622 };
623
624 /*----- That's all, folks -------------------------------------------------*/