chiark / gitweb /
configure.ac: Escape plan for old Nettle without pkg-config dropping.
[distorted-backup] / rmt.c
1 /* -*-c-*-
2  *
3  * Fake rmt(8) server for hashing and storing files
4  *
5  * (c) 2010 Mark Wooding
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23  */
24
25 /*----- Header files ------------------------------------------------------*/
26
27 #define _FILE_OFFSET_BITS 64
28
29 #include <ctype.h>
30 #include <errno.h>
31 #include <limits.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <sys/types.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40
41 #include <getopt.h>
42
43 #include <pwd.h>
44
45 #include <mLib/dstr.h>
46 #include <mLib/quis.h>
47 #include <mLib/report.h>
48
49 #include <nettle/sha.h>
50
51 /*----- Configuration -----------------------------------------------------*/
52
53 #ifndef BKP
54 #  define BKP "/mnt/bkp"
55 #endif
56
57 /*----- Main code ---------------------------------------------------------*/
58
59 #define BUFSZ 10240
60 #define LSEEK_GET_TAPEPOS 10
61 #define LSEEK_GO2_TAPEPOS 11
62
63 struct flag {
64   const char *name;
65   int f;
66 };
67
68 static const struct flag openflag[] = {
69   /* ;;; Emacs Lisp to generate the table below.  Place your cursor just
70      ;;; after the closing `)' and press C-x C-e.
71
72      (let ((flags '(rdonly wronly rdwr creat excl trunc nonblock ndelay
73                     noctty append dsync rsync sync cloexec async
74                     direct noatime nofollow shlock exlock defer)))
75        (save-excursion
76          (goto-char (point-min))
77          (search-forward (concat "***" "BEGIN openflag" "***"))
78          (beginning-of-line 2)
79          (delete-region (point)
80                         (progn
81                           (search-forward "***END***")
82                           (beginning-of-line)
83                           (point)))
84        (dolist (f (sort (copy-list flags) #'string<))
85          (let ((up (upcase (symbol-name f))))
86            (insert (format "#ifdef O_%s\n" up))
87            (insert (format "  { \"%s\", O_%s },\n" up up))
88            (insert "#endif\n")))))
89   */
90   /***BEGIN openflag***/
91 #ifdef O_APPEND
92   { "APPEND", O_APPEND },
93 #endif
94 #ifdef O_ASYNC
95   { "ASYNC", O_ASYNC },
96 #endif
97 #ifdef O_CLOEXEC
98   { "CLOEXEC", O_CLOEXEC },
99 #endif
100 #ifdef O_CREAT
101   { "CREAT", O_CREAT },
102 #endif
103 #ifdef O_DEFER
104   { "DEFER", O_DEFER },
105 #endif
106 #ifdef O_DIRECT
107   { "DIRECT", O_DIRECT },
108 #endif
109 #ifdef O_DSYNC
110   { "DSYNC", O_DSYNC },
111 #endif
112 #ifdef O_EXCL
113   { "EXCL", O_EXCL },
114 #endif
115 #ifdef O_EXLOCK
116   { "EXLOCK", O_EXLOCK },
117 #endif
118 #ifdef O_NDELAY
119   { "NDELAY", O_NDELAY },
120 #endif
121 #ifdef O_NOATIME
122   { "NOATIME", O_NOATIME },
123 #endif
124 #ifdef O_NOCTTY
125   { "NOCTTY", O_NOCTTY },
126 #endif
127 #ifdef O_NOFOLLOW
128   { "NOFOLLOW", O_NOFOLLOW },
129 #endif
130 #ifdef O_NONBLOCK
131   { "NONBLOCK", O_NONBLOCK },
132 #endif
133 #ifdef O_RDONLY
134   { "RDONLY", O_RDONLY },
135 #endif
136 #ifdef O_RDWR
137   { "RDWR", O_RDWR },
138 #endif
139 #ifdef O_RSYNC
140   { "RSYNC", O_RSYNC },
141 #endif
142 #ifdef O_SHLOCK
143   { "SHLOCK", O_SHLOCK },
144 #endif
145 #ifdef O_SYNC
146   { "SYNC", O_SYNC },
147 #endif
148 #ifdef O_TRUNC
149   { "TRUNC", O_TRUNC },
150 #endif
151 #ifdef O_WRONLY
152   { "WRONLY", O_WRONLY },
153 #endif
154   /***END***/
155   { 0, 0 }
156 };
157
158 int main(int argc, char *argv[])
159 {
160   int ch;
161   dstr d = DSTR_INIT, dd = DSTR_INIT;
162   unsigned char buf[BUFSZ];
163   int fd = -1, hfd = -1;
164   off_t rc;
165   off_t off = 0;
166   struct passwd *pw;
167   uid_t u;
168   unsigned f = 0;
169 #define f_bogus 1u
170   const char *p = 0;
171   const char *bkp = 0, *host = 0;
172   struct sha256_ctx hc;
173
174   ego(argv[0]);
175   setvbuf(stdin, 0, _IONBF, 0);
176   signal(SIGPIPE, SIG_IGN);
177
178   for (;;) {
179     int o = getopt(argc, argv, "H:r:");
180     if (o < 0) break;
181     switch (o) {
182       case 'H': host = optarg; break;
183       case 'r': bkp = optarg; break;
184       default: f |= f_bogus; break;
185     }
186   }
187   argc -= optind; argv += optind;
188   if ((f & f_bogus) || argc) {
189     pquis(stderr, "usage: $ [-r ROOT] [-H HOST]\n");
190     exit(1);
191   }
192
193   if (!bkp) bkp = getenv("BKP");
194   if (!bkp) bkp = BKP;
195
196   if (!host) host = getenv("BKP_HOST");
197
198   if (!host) {
199     p = getenv("USER");
200     if (!p) p = getenv("LOGNAME");
201     if (!p) {
202       u = getuid();
203       if ((pw = getpwuid(u)) == 0)
204         die(1, "no passwd entry (you don't exist?)");
205       p = pw->pw_name;
206     }
207     if (strncmp(p, "bkp-", 4) != 0) {
208       die(1, "can't deduce host name: "
209           "login name `%s' doesn't begin with `bkp-'",
210           p);
211     }
212     host = p + 4;
213   }
214
215   for (;;) {
216     if (fflush(stdout)) goto fail;
217     ch = getchar();
218     if (ch == EOF) break;
219     DRESET(&d); DRESET(&dd); rc = 0;
220
221 #define CHECKFD do { if (fd < 0) goto badf; } while (0)
222 #define ERROR(what) do moan(what ": %s", strerror(errno)); while (0)
223 #define ERROR1(what, arg) \
224   do moan(what ": %s", arg, strerror(errno)); while (0)
225 #define ARG(d) do {                                                     \
226   if (dstr_putline(&d, stdin) == EOF || ferror(stdin) || feof(stdin))   \
227     { moan("read (stdin)", strerror(errno)); goto fail; }               \
228 } while (0)
229
230 #define SKIPWS do { while (isspace((unsigned char)*p)) p++; } while (0)
231
232     switch (ch) {
233
234       case 'O': {
235         /* Ofile\nmode\n -- open file */
236
237         const struct flag *ff;
238         char *p, *q;
239         size_t n;
240         long mode, f;
241
242         ARG(d); ARG(dd);
243         if (fd >= 0 && close(fd)) ERROR("close (fd)");
244         if (hfd >= 0 && close(hfd)) ERROR("close (hash)");
245         fd = hfd = -1;
246
247         if (chdir(bkp) || chdir(host)) ERROR1("chdir (%s)", host);
248         p = d.buf;
249         if ((q = strchr(p, '/')) == 0)
250           { moan("bad path: missing `/')"); goto inval; }
251         *q++ = 0;
252         if (chdir(p) || chdir("prepare/incoming")) ERROR1("chdir (%s)", p);
253
254         memmove(d.buf, q, d.len - (q - d.buf) + 1);
255         d.len -= q - d.buf;
256
257         errno = 0;
258         mode = strtol(dd.buf, &p, 0);
259         if (errno) ERROR("bad mode");
260         else if (mode < 0 || mode > INT_MAX)
261           { moan("bad mode: range"); goto range; }
262         SKIPWS;
263         if (!*p) {
264           switch (mode & O_ACCMODE) {
265             case O_RDONLY: mode = O_RDONLY; break;
266             case O_WRONLY: mode = O_WRONLY | O_TRUNC | O_CREAT; break;
267             case O_RDWR: mode = O_RDWR;
268             default: moan("bad mode: unknown access type"); goto inval;
269           }
270         } else {
271           mode = 0;
272           for (;;) {
273             if (p[0] == 'O' && p[1] == '_') {
274               p += 2;
275               n = strcspn(p, " \t|");
276               for (ff = openflag; ff->name; ff++) {
277                 if (strncmp(p, ff->name, n) == 0 && !ff->name[n])
278                   goto ofmatch;
279               }
280               moan("bad mode: unknown flag O_%.*s", (int)n, p);
281               goto inval;
282             ofmatch:
283               mode |= ff->f;
284               p += n;
285             } else if (isdigit((unsigned long)*p)) {
286               errno = 0;
287               f = strtol(p, &p, 0);
288               if (errno) ERROR("bad mode");
289               else if (f < 0 || f > INT_MAX)
290                 { moan("bad mode: range"); goto range; }
291               mode |= f;
292             } else {
293               moan("bad mode: unexpected token");
294               goto inval;
295             }
296             SKIPWS;
297             if (!*p)
298               break;
299             else if (*p != '|') {
300               moan("bad mode: expected `|'");
301               goto inval;
302             }
303             p++;
304             SKIPWS;
305           }
306         }
307
308         if ((fd = open(d.buf, mode, 0666)) < 0) ERROR1("open (%s)", d.buf);
309         if ((mode & O_ACCMODE) == O_WRONLY) {
310           DPUTS(&d, ".hash");
311           if ((hfd = open(d.buf,
312                          mode & (O_ACCMODE | O_CREAT | O_EXCL | O_TRUNC),
313                          0666)) < 0) {
314             close(fd); fd = -1;
315             ERROR1("open (%s)", d.buf);
316           }
317           sha256_init(&hc);
318         }
319         off = 0;
320       } break;
321
322       case 'C': {
323         /* Chunoz\n -- close file */
324
325         uint8_t h[SHA256_DIGEST_SIZE], *p;
326         char hex[SHA256_DIGEST_SIZE * 2 + 1], *q;
327         unsigned i;
328
329         ARG(d); CHECKFD;
330         if (close(fd)) ERROR("close (fd)");
331         fd = -1;
332         if (hfd >= 0) {
333           sha256_digest(&hc, sizeof(h), h);
334           for (p = h, q = hex; p < h + sizeof(h); p++, q += 2)
335             sprintf(q, "%02x", *p);
336           *q++ = '\n';
337           errno = EIO;
338           if (write(hfd, hex, sizeof(hex)) < sizeof(hex) || close(hfd))
339             ERROR("close (hash)");
340           hfd = -1;
341         }
342       } break;
343
344       case 'L': {
345         /* Loffset\nwhence\n -- seek
346          *  (warning: the manual page gets these the wrong way round)
347          */
348
349         int whence;
350         off_t offset;
351
352         ARG(d); ARG(dd); CHECKFD;
353         offset = atoi(d.buf); whence = strtoull(dd.buf, 0, 0);
354         switch (whence) {
355           case LSEEK_GET_TAPEPOS: whence = SEEK_CUR; offset = 0; break;
356           case LSEEK_GO2_TAPEPOS: whence = SEEK_SET; break;
357         }
358         switch (whence) {
359           case SEEK_CUR:
360             if (!offset) { rc = off; break; }
361           default:
362             rc = lseek(fd, offset, whence);
363             if (rc == (off_t)-1) ERROR("seek");
364             off = rc;
365             break;
366         }
367       } break;
368
369       case 'W': {
370         /* Wlen\ndata... -- write */
371
372         size_t n, nn, sz;
373         ssize_t ssz;
374         unsigned char *p;
375         int botch = 0;
376
377         ARG(d); CHECKFD;
378         rc = sz = strtoul(d.buf, 0, 0);
379         while (sz) {
380           nn = sz > BUFSZ ? BUFSZ : sz;
381           n = fread(buf, 1, nn, stdin);
382           if (n < nn) {
383             if (feof(stdin)) { moan("eof on stdin"); goto fail;}
384             else ERROR("read (stdin)");
385           }
386           if (hfd >= 0) sha256_update(&hc, n, buf);
387           p = buf;
388           while (!botch && n) {
389             if ((ssz = write(fd, p, n)) > 0) {
390               p += ssz; off += ssz; n -= ssz;
391             } else if (!ssz) { moan("zero-length write"); goto fail; }
392             else if (errno != EINTR) { botch = errno; }
393           }
394           sz -= nn;
395         }
396         if (botch) { errno = botch; ERROR("write"); }
397       } break;
398
399       case 'R': {
400         /* Rlen\n -- read */
401
402         size_t nn;
403         ssize_t ssz;
404
405         ARG(d); CHECKFD;
406         nn = strtoul(d.buf, 0, 0); if (nn > BUFSZ) nn = BUFSZ;
407         if ((ssz = read(fd, buf, nn)) < 0) ERROR("read");
408         off += ssz;
409         printf("A%ld\n", (long)ssz);
410         if (fwrite(buf, 1, ssz, stdout) < ssz)
411           { moan("write (stdout): %s", strerror(errno)); goto fail; }
412         continue;
413       } break;
414
415       case 'i': case 'I':
416         /* Iop\ncount\n -- ioctl */
417         ARG(d); ARG(dd); CHECKFD; goto notty;
418
419       case 'S':
420         /* S -- ioctl */
421         CHECKFD; goto notty;
422       case 's':
423         /* sop -- ioctl */
424
425         if ((ch = getchar()) == EOF) goto fail;
426         CHECKFD; goto notty;
427
428       default:
429         goto fail;
430     }
431
432     printf("A%llu\n", (unsigned long long)rc);
433     continue;
434
435   badf: errno = EBADF; goto error;
436   range: errno = ERANGE; goto error;
437   inval: errno = EINVAL; goto error;
438   notty: errno = ENOTTY; goto error;
439   error:
440     printf("E%d\n%s\n", errno, strerror(errno));
441     continue;
442   }
443   if (fflush(stdout) || ferror(stdout) || ferror(stdin)) goto fail;
444   return (0);
445 fail:
446   return (1);
447 }
448
449 /*----- That's all, folks -------------------------------------------------*/