Commit | Line | Data |
---|---|---|
99248ed2 MW |
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 -------------------------------------------------*/ |