1 /*-
2 * Copyright (c) 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
6 *
7 * See the LICENSE file for redistribution information.
8 */
9
10 #include "config.h"
11
12 #include <sys/types.h>
13 #include <sys/queue.h>
14 #include <sys/stat.h>
15
16 /*
17 * We include <sys/file.h>, because the open #defines were found there
18 * on historical systems. We also include <fcntl.h> because the open(2)
19 * #defines are found there on newer systems.
20 */
21 #include <sys/file.h>
22
23 #include <bitstring.h>
24 #include <dirent.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <pwd.h>
29 #include <netinet/in.h> /* Required by resolv.h. */
30 #include <resolv.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 #include <unistd.h>
36
37 #include "version.h"
38 #include "common.h"
39 #include "pathnames.h"
40
41 /*
42 * Recovery code.
43 *
44 * The basic scheme is as follows. In the EXF structure, we maintain full
45 * paths of a b+tree file and a mail recovery file. The former is the file
46 * used as backing store by the DB package. The latter is the file that
47 * contains an email message to be sent to the user if we crash. The two
48 * simple states of recovery are:
49 *
50 * + first starting the edit session:
51 * the b+tree file exists and is mode 700, the mail recovery
52 * file doesn't exist.
53 * + after the file has been modified:
54 * the b+tree file exists and is mode 600, the mail recovery
55 * file exists, and is exclusively locked.
56 *
57 * In the EXF structure we maintain a file descriptor that is the locked
58 * file descriptor for the mail recovery file.
59 *
60 * To find out if a recovery file/backing file pair are in use, try to get
61 * a lock on the recovery file.
62 *
63 * To find out if a backing file can be deleted at boot time, check for an
64 * owner execute bit. (Yes, I know it's ugly, but it's either that or put
65 * special stuff into the backing file itself, or correlate the files at
66 * boot time, neither of which looks like fun.) Note also that there's a
67 * window between when the file is created and the X bit is set. It's small,
68 * but it's there. To fix the window, check for 0 length files as well.
69 *
70 * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
71 * this DOES NOT mean that any initialization has been done, only that we
72 * haven't yet failed at setting up or doing recovery.
73 *
74 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
75 * If that bit is not set when ending a file session:
76 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
77 * they are unlink(2)'d, and free(3)'d.
78 * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
79 *
80 * The backing b+tree file is set up when a file is first edited, so that
81 * the DB package can use it for on-disk caching and/or to snapshot the
82 * file. When the file is first modified, the mail recovery file is created,
83 * the backing file permissions are updated, the file is sync(2)'d to disk,
84 * and the timer is started. Then, at RCV_PERIOD second intervals, the
85 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
86 * means that the data structures (SCR, EXF, the underlying tree structures)
87 * must be consistent when the signal arrives.
88 *
89 * The recovery mail file contains normal mail headers, with two additional
90 *
91 * X-vi-data: <file|path>;<base64 encoded path>
92 *
93 * MIME headers; the folding character is limited to ' '.
94 *
95 * Btree files are named "vi.XXXXXX" and recovery files are named
96 * "recover.XXXXXX".
97 */
98
99 #define VI_DHEADER "X-vi-data:"
100
101 static int rcv_copy(SCR *, int, char *);
102 static void rcv_email(SCR *, char *);
103 static int rcv_mailfile(SCR *, int, char *);
104 static int rcv_mktemp(SCR *, char *, char *);
105 static int rcv_dlnwrite(SCR *, const char *, const char *, FILE *);
106 static int rcv_dlnread(SCR *, char **, char **, FILE *);
107
108 /*
109 * rcv_tmp --
110 * Build a file name that will be used as the recovery file.
111 *
112 * PUBLIC: int rcv_tmp(SCR *, EXF *, char *);
113 */
114 int
rcv_tmp(SCR * sp,EXF * ep,char * name)115 rcv_tmp(SCR *sp, EXF *ep, char *name)
116 {
117 struct stat sb;
118 int fd;
119 char *dp, *path;
120
121 /*
122 * !!!
123 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
124 *
125 *
126 * If the recovery directory doesn't exist, try and create it. As
127 * the recovery files are themselves protected from reading/writing
128 * by other than the owner, the worst that can happen is that a user
129 * would have permission to remove other user's recovery files. If
130 * the sticky bit has the BSD semantics, that too will be impossible.
131 */
132 if (opts_empty(sp, O_RECDIR, 0))
133 goto err;
134 dp = O_STR(sp, O_RECDIR);
135 if (stat(dp, &sb)) {
136 if (errno != ENOENT || mkdir(dp, 0)) {
137 msgq(sp, M_SYSERR, "%s", dp);
138 goto err;
139 }
140 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
141 }
142
143 if ((path = join(dp, "vi.XXXXXX")) == NULL)
144 goto err;
145 if ((fd = rcv_mktemp(sp, path, dp)) == -1) {
146 free(path);
147 goto err;
148 }
149 (void)fchmod(fd, S_IRWXU);
150 (void)close(fd);
151
152 ep->rcv_path = path;
153 if (0) {
154 err: msgq(sp, M_ERR,
155 "056|Modifications not recoverable if the session fails");
156 return (1);
157 }
158
159 /* We believe the file is recoverable. */
160 F_SET(ep, F_RCV_ON);
161 return (0);
162 }
163
164 /*
165 * rcv_init --
166 * Force the file to be snapshotted for recovery.
167 *
168 * PUBLIC: int rcv_init(SCR *);
169 */
170 int
rcv_init(SCR * sp)171 rcv_init(SCR *sp)
172 {
173 EXF *ep;
174 recno_t lno;
175
176 ep = sp->ep;
177
178 /* Only do this once. */
179 F_CLR(ep, F_FIRSTMODIFY);
180
181 /* If we already know the file isn't recoverable, we're done. */
182 if (!F_ISSET(ep, F_RCV_ON))
183 return (0);
184
185 /* Turn off recoverability until we figure out if this will work. */
186 F_CLR(ep, F_RCV_ON);
187
188 /* Test if we're recovering a file, not editing one. */
189 if (ep->rcv_mpath == NULL) {
190 /* Build a file to mail to the user. */
191 if (rcv_mailfile(sp, 0, NULL))
192 goto err;
193
194 /* Force a read of the entire file. */
195 if (db_last(sp, &lno))
196 goto err;
197
198 /* Turn on a busy message, and sync it to backing store. */
199 sp->gp->scr_busy(sp,
200 "057|Copying file for recovery...", BUSY_ON);
201 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
202 msgq_str(sp, M_SYSERR, ep->rcv_path,
203 "058|Preservation failed: %s");
204 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
205 goto err;
206 }
207 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
208 }
209
210 /* Turn off the owner execute bit. */
211 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
212
213 /* We believe the file is recoverable. */
214 F_SET(ep, F_RCV_ON);
215 return (0);
216
217 err: msgq(sp, M_ERR,
218 "059|Modifications not recoverable if the session fails");
219 return (1);
220 }
221
222 /*
223 * rcv_sync --
224 * Sync the file, optionally:
225 * flagging the backup file to be preserved
226 * snapshotting the backup file and send email to the user
227 * sending email to the user if the file was modified
228 * ending the file session
229 *
230 * PUBLIC: int rcv_sync(SCR *, u_int);
231 */
232 int
rcv_sync(SCR * sp,u_int flags)233 rcv_sync(SCR *sp, u_int flags)
234 {
235 EXF *ep;
236 int fd, rval;
237 char *dp, *buf;
238
239 /* Make sure that there's something to recover/sync. */
240 ep = sp->ep;
241 if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
242 return (0);
243
244 /* Sync the file if it's been modified. */
245 if (F_ISSET(ep, F_MODIFIED)) {
246 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
247 F_CLR(ep, F_RCV_ON | F_RCV_NORM);
248 msgq_str(sp, M_SYSERR,
249 ep->rcv_path, "060|File backup failed: %s");
250 return (1);
251 }
252
253 /* REQUEST: don't remove backing file on exit. */
254 if (LF_ISSET(RCV_PRESERVE))
255 F_SET(ep, F_RCV_NORM);
256
257 /* REQUEST: send email. */
258 if (LF_ISSET(RCV_EMAIL))
259 rcv_email(sp, ep->rcv_mpath);
260 }
261
262 /*
263 * !!!
264 * Each time the user exec's :preserve, we have to snapshot all of
265 * the recovery information, i.e. it's like the user re-edited the
266 * file. We copy the DB(3) backing file, and then create a new mail
267 * recovery file, it's simpler than exiting and reopening all of the
268 * underlying files.
269 *
270 * REQUEST: snapshot the file.
271 */
272 rval = 0;
273 if (LF_ISSET(RCV_SNAPSHOT)) {
274 if (opts_empty(sp, O_RECDIR, 0))
275 goto err;
276 dp = O_STR(sp, O_RECDIR);
277 if ((buf = join(dp, "vi.XXXXXX")) == NULL) {
278 msgq(sp, M_SYSERR, NULL);
279 goto err;
280 }
281 if ((fd = rcv_mktemp(sp, buf, dp)) == -1) {
282 free(buf);
283 goto err;
284 }
285 sp->gp->scr_busy(sp,
286 "061|Copying file for recovery...", BUSY_ON);
287 if (rcv_copy(sp, fd, ep->rcv_path) ||
288 close(fd) || rcv_mailfile(sp, 1, buf)) {
289 (void)unlink(buf);
290 (void)close(fd);
291 rval = 1;
292 }
293 free(buf);
294 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
295 }
296 if (0) {
297 err: rval = 1;
298 }
299
300 /* REQUEST: end the file session. */
301 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
302 rval = 1;
303
304 return (rval);
305 }
306
307 /*
308 * rcv_mailfile --
309 * Build the file to mail to the user.
310 */
311 static int
rcv_mailfile(SCR * sp,int issync,char * cp_path)312 rcv_mailfile(SCR *sp, int issync, char *cp_path)
313 {
314 EXF *ep;
315 GS *gp;
316 struct passwd *pw;
317 int len;
318 time_t now;
319 uid_t uid;
320 int fd;
321 FILE *fp;
322 char *dp, *p, *t, *qt, *buf, *mpath;
323 char *t1, *t2, *t3;
324 int st;
325
326 /*
327 * XXX
328 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3)
329 * first, then fallback to _POSIX_HOST_NAME_MAX.
330 */
331 char *host;
332 long hostmax = sysconf(_SC_HOST_NAME_MAX);
333 if (hostmax < 0)
334 hostmax = _POSIX_HOST_NAME_MAX;
335
336 gp = sp->gp;
337 if ((pw = getpwuid(uid = getuid())) == NULL) {
338 msgq(sp, M_ERR,
339 "062|Information on user id %u not found", uid);
340 return (1);
341 }
342
343 if (opts_empty(sp, O_RECDIR, 0))
344 return (1);
345 dp = O_STR(sp, O_RECDIR);
346 if ((mpath = join(dp, "recover.XXXXXX")) == NULL) {
347 msgq(sp, M_SYSERR, NULL);
348 return (1);
349 }
350 if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) {
351 free(mpath);
352 return (1);
353 }
354 if ((fp = fdopen(fd, "w")) == NULL) {
355 free(mpath);
356 close(fd);
357 return (1);
358 }
359
360 /*
361 * XXX
362 * We keep an open lock on the file so that the recover option can
363 * distinguish between files that are live and those that need to
364 * be recovered. There's an obvious window between the mkstemp call
365 * and the lock, but it's pretty small.
366 */
367 ep = sp->ep;
368 if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS)
369 msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
370 if (!issync) {
371 /* Save the recover file descriptor, and mail path. */
372 ep->rcv_fd = dup(fd);
373 ep->rcv_mpath = mpath;
374 cp_path = ep->rcv_path;
375 }
376
377 t = sp->frp->name;
378 if ((p = strrchr(t, '/')) == NULL)
379 p = t;
380 else
381 ++p;
382 (void)time(&now);
383
384 if ((st = rcv_dlnwrite(sp, "file", t, fp))) {
385 if (st == 1)
386 goto werr;
387 goto err;
388 }
389 if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) {
390 if (st == 1)
391 goto werr;
392 goto err;
393 }
394
395 MALLOC(sp, host, hostmax + 1);
396 if (host == NULL)
397 goto err;
398 (void)gethostname(host, hostmax + 1);
399
400 len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n",
401 "From: root@", host, " (Nvi recovery program)",
402 "To: ", pw->pw_name, "@", host,
403 "Subject: Nvi saved the file ", p,
404 "Precedence: bulk"); /* For vacation(1). */
405 if (len < 0) {
406 free(host);
407 goto werr;
408 }
409
410 if ((qt = quote(t)) == NULL) {
411 free(host);
412 msgq(sp, M_SYSERR, NULL);
413 goto err;
414 }
415 len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
416 "On ", ctime(&now), ", the user ", pw->pw_name,
417 " was editing a file named ", t, " on the machine ",
418 host, ", when it was saved for recovery. ",
419 "You can recover most, if not all, of the changes ",
420 "to this file using the -r option to ", getprogname(), ":\n\n\t",
421 getprogname(), " -r ", qt);
422 free(qt);
423 free(host);
424 if (len == -1) {
425 msgq(sp, M_SYSERR, NULL);
426 goto err;
427 }
428
429 /*
430 * Format the message. (Yes, I know it's silly.)
431 * Requires that the message end in a <newline>.
432 */
433 #define FMTCOLS 60
434 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
435 /* Check for a short length. */
436 if (len <= FMTCOLS) {
437 t2 = t1 + (len - 1);
438 goto wout;
439 }
440
441 /* Check for a required <newline>. */
442 t2 = strchr(t1, '\n');
443 if (t2 - t1 <= FMTCOLS)
444 goto wout;
445
446 /* Find the closest space, if any. */
447 for (t3 = t2; t2 > t1; --t2)
448 if (*t2 == ' ') {
449 if (t2 - t1 <= FMTCOLS)
450 goto wout;
451 t3 = t2;
452 }
453 t2 = t3;
454
455 /* t2 points to the last character to display. */
456 wout: *t2++ = '\n';
457
458 /* t2 points one after the last character to display. */
459 if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) {
460 free(buf);
461 goto werr;
462 }
463 }
464
465 if (issync) {
466 fflush(fp);
467 rcv_email(sp, mpath);
468 free(mpath);
469 }
470 if (fclose(fp)) {
471 free(buf);
472 werr: msgq(sp, M_SYSERR, "065|Recovery file");
473 goto err;
474 }
475 free(buf);
476 return (0);
477
478 err: if (!issync)
479 ep->rcv_fd = -1;
480 if (fp != NULL)
481 (void)fclose(fp);
482 return (1);
483 }
484
485 /*
486 * people making love
487 * never exactly the same
488 * just like a snowflake
489 *
490 * rcv_list --
491 * List the files that can be recovered by this user.
492 *
493 * PUBLIC: int rcv_list(SCR *);
494 */
495 int
rcv_list(SCR * sp)496 rcv_list(SCR *sp)
497 {
498 struct dirent *dp;
499 struct stat sb;
500 DIR *dirp;
501 FILE *fp;
502 int found;
503 char *p, *file, *path;
504 char *dtype, *data;
505 int st;
506
507 /* Open the recovery directory for reading. */
508 if (opts_empty(sp, O_RECDIR, 0))
509 return (1);
510 p = O_STR(sp, O_RECDIR);
511 if (chdir(p) || (dirp = opendir(".")) == NULL) {
512 msgq_str(sp, M_SYSERR, p, "recdir: %s");
513 return (1);
514 }
515
516 /* Read the directory. */
517 for (found = 0; (dp = readdir(dirp)) != NULL;) {
518 if (strncmp(dp->d_name, "recover.", 8))
519 continue;
520
521 /* If it's readable, it's recoverable. */
522 if ((fp = fopen(dp->d_name, "r")) == NULL)
523 continue;
524
525 switch (file_lock(sp, NULL, fileno(fp), 1)) {
526 case LOCK_FAILED:
527 /*
528 * XXX
529 * Assume that a lock can't be acquired, but that we
530 * should permit recovery anyway. If this is wrong,
531 * and someone else is using the file, we're going to
532 * die horribly.
533 */
534 break;
535 case LOCK_SUCCESS:
536 break;
537 case LOCK_UNAVAIL:
538 /* If it's locked, it's live. */
539 (void)fclose(fp);
540 continue;
541 }
542
543 /* Check the headers. */
544 for (file = NULL, path = NULL;
545 file == NULL || path == NULL;) {
546 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
547 if (st == 1)
548 msgq_str(sp, M_ERR, dp->d_name,
549 "066|%s: malformed recovery file");
550 goto next;
551 }
552 if (dtype == NULL)
553 continue;
554 if (!strcmp(dtype, "file"))
555 file = data;
556 else if (!strcmp(dtype, "path"))
557 path = data;
558 else
559 free(data);
560 }
561
562 /*
563 * If the file doesn't exist, it's an orphaned recovery file,
564 * toss it.
565 *
566 * XXX
567 * This can occur if the backup file was deleted and we crashed
568 * before deleting the email file.
569 */
570 errno = 0;
571 if (stat(path, &sb) &&
572 errno == ENOENT) {
573 (void)unlink(dp->d_name);
574 goto next;
575 }
576
577 /* Get the last modification time and display. */
578 (void)fstat(fileno(fp), &sb);
579 (void)printf("%.24s: %s\n",
580 ctime(&sb.st_mtime), file);
581 found = 1;
582
583 /* Close, discarding lock. */
584 next: (void)fclose(fp);
585 free(file);
586 free(path);
587 }
588 if (found == 0)
589 (void)printf("%s: No files to recover\n", getprogname());
590 (void)closedir(dirp);
591 return (0);
592 }
593
594 /*
595 * rcv_read --
596 * Start a recovered file as the file to edit.
597 *
598 * PUBLIC: int rcv_read(SCR *, FREF *);
599 */
600 int
rcv_read(SCR * sp,FREF * frp)601 rcv_read(SCR *sp, FREF *frp)
602 {
603 struct dirent *dp;
604 struct stat sb;
605 DIR *dirp;
606 FILE *fp;
607 EXF *ep;
608 struct timespec rec_mtim = { 0, 0 };
609 int found, locked = 0, requested, sv_fd;
610 char *name, *p, *t, *rp, *recp, *pathp;
611 char *file, *path, *recpath;
612 char *dtype, *data;
613 int st;
614
615 if (opts_empty(sp, O_RECDIR, 0))
616 return (1);
617 rp = O_STR(sp, O_RECDIR);
618 if ((dirp = opendir(rp)) == NULL) {
619 msgq_str(sp, M_SYSERR, rp, "%s");
620 return (1);
621 }
622
623 name = frp->name;
624 sv_fd = -1;
625 recp = pathp = NULL;
626 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
627 if (strncmp(dp->d_name, "recover.", 8))
628 continue;
629 if ((recpath = join(rp, dp->d_name)) == NULL) {
630 msgq(sp, M_SYSERR, NULL);
631 continue;
632 }
633
634 /* If it's readable, it's recoverable. */
635 if ((fp = fopen(recpath, "r")) == NULL) {
636 free(recpath);
637 continue;
638 }
639
640 switch (file_lock(sp, NULL, fileno(fp), 1)) {
641 case LOCK_FAILED:
642 /*
643 * XXX
644 * Assume that a lock can't be acquired, but that we
645 * should permit recovery anyway. If this is wrong,
646 * and someone else is using the file, we're going to
647 * die horribly.
648 */
649 locked = 0;
650 break;
651 case LOCK_SUCCESS:
652 locked = 1;
653 break;
654 case LOCK_UNAVAIL:
655 /* If it's locked, it's live. */
656 (void)fclose(fp);
657 continue;
658 }
659
660 /* Check the headers. */
661 for (file = NULL, path = NULL;
662 file == NULL || path == NULL;) {
663 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
664 if (st == 1)
665 msgq_str(sp, M_ERR, dp->d_name,
666 "067|%s: malformed recovery file");
667 goto next;
668 }
669 if (dtype == NULL)
670 continue;
671 if (!strcmp(dtype, "file"))
672 file = data;
673 else if (!strcmp(dtype, "path"))
674 path = data;
675 else
676 free(data);
677 }
678 ++found;
679
680 /*
681 * If the file doesn't exist, it's an orphaned recovery file,
682 * toss it.
683 *
684 * XXX
685 * This can occur if the backup file was deleted and we crashed
686 * before deleting the email file.
687 */
688 errno = 0;
689 if (stat(path, &sb) &&
690 errno == ENOENT) {
691 (void)unlink(dp->d_name);
692 goto next;
693 }
694
695 /* Check the file name. */
696 if (strcmp(file, name))
697 goto next;
698
699 ++requested;
700
701 /* If we've found more than one, take the most recent. */
702 (void)fstat(fileno(fp), &sb);
703 if (recp == NULL ||
704 timespeccmp(&rec_mtim, &sb.st_mtim, <)) {
705 p = recp;
706 t = pathp;
707 recp = recpath;
708 pathp = path;
709 if (p != NULL) {
710 free(p);
711 free(t);
712 }
713 rec_mtim = sb.st_mtim;
714 if (sv_fd != -1)
715 (void)close(sv_fd);
716 sv_fd = dup(fileno(fp));
717 } else {
718 next: free(recpath);
719 free(path);
720 }
721 (void)fclose(fp);
722 free(file);
723 }
724 (void)closedir(dirp);
725
726 if (recp == NULL) {
727 msgq_str(sp, M_INFO, name,
728 "068|No files named %s, readable by you, to recover");
729 return (1);
730 }
731 if (found) {
732 if (requested > 1)
733 msgq(sp, M_INFO,
734 "069|There are older versions of this file for you to recover");
735 if (found > requested)
736 msgq(sp, M_INFO,
737 "070|There are other files for you to recover");
738 }
739
740 /*
741 * Create the FREF structure, start the btree file.
742 *
743 * XXX
744 * file_init() is going to set ep->rcv_path.
745 */
746 if (file_init(sp, frp, pathp, 0)) {
747 free(recp);
748 free(pathp);
749 (void)close(sv_fd);
750 return (1);
751 }
752 free(pathp);
753
754 /*
755 * We keep an open lock on the file so that the recover option can
756 * distinguish between files that are live and those that need to
757 * be recovered. The lock is already acquired, just copy it.
758 */
759 ep = sp->ep;
760 ep->rcv_mpath = recp;
761 ep->rcv_fd = sv_fd;
762 if (!locked)
763 F_SET(frp, FR_UNLOCKED);
764
765 /* We believe the file is recoverable. */
766 F_SET(ep, F_RCV_ON);
767 return (0);
768 }
769
770 /*
771 * rcv_copy --
772 * Copy a recovery file.
773 */
774 static int
rcv_copy(SCR * sp,int wfd,char * fname)775 rcv_copy(SCR *sp, int wfd, char *fname)
776 {
777 int nr, nw, off, rfd;
778 char buf[8 * 1024];
779
780 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
781 goto err;
782 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
783 for (off = 0; nr; nr -= nw, off += nw)
784 if ((nw = write(wfd, buf + off, nr)) < 0)
785 goto err;
786 if (nr == 0)
787 return (0);
788
789 err: msgq_str(sp, M_SYSERR, fname, "%s");
790 return (1);
791 }
792
793 /*
794 * rcv_mktemp --
795 * Paranoid make temporary file routine.
796 */
797 static int
rcv_mktemp(SCR * sp,char * path,char * dname)798 rcv_mktemp(SCR *sp, char *path, char *dname)
799 {
800 int fd;
801
802 if ((fd = mkstemp(path)) == -1)
803 msgq_str(sp, M_SYSERR, dname, "%s");
804 return (fd);
805 }
806
807 /*
808 * rcv_email --
809 * Send email.
810 */
811 static void
rcv_email(SCR * sp,char * fname)812 rcv_email(SCR *sp, char *fname)
813 {
814 char *buf;
815
816 if (asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname) == -1) {
817 msgq_str(sp, M_ERR, strerror(errno),
818 "071|not sending email: %s");
819 return;
820 }
821 (void)system(buf);
822 free(buf);
823 }
824
825 /*
826 * rcv_dlnwrite --
827 * Encode a string into an X-vi-data line and write it.
828 */
829 static int
rcv_dlnwrite(SCR * sp,const char * dtype,const char * src,FILE * fp)830 rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp)
831 {
832 char *bp = NULL, *p;
833 size_t blen = 0;
834 size_t dlen, len;
835 int plen, xlen;
836
837 len = strlen(src);
838 dlen = strlen(dtype);
839 GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2);
840 (void)memcpy(bp, dtype, dlen);
841 bp[dlen] = ';';
842 if ((xlen = b64_ntop((u_char *)src,
843 len, bp + dlen + 1, blen)) == -1)
844 goto err;
845 xlen += dlen + 1;
846
847 /* Output as an MIME folding header. */
848 if ((plen = fprintf(fp, VI_DHEADER " %.*s\n",
849 FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0)
850 goto err;
851 plen -= (int)sizeof(VI_DHEADER) + 1;
852 for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) {
853 p += plen;
854 if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0)
855 goto err;
856 plen -= 2;
857 }
858 FREE_SPACE(sp, bp, blen);
859 return (0);
860
861 err: FREE_SPACE(sp, bp, blen);
862 return (1);
863 alloc_err:
864 msgq(sp, M_SYSERR, NULL);
865 return (-1);
866 }
867
868 /*
869 * rcv_dlnread --
870 * Read an X-vi-data line and decode it.
871 */
872 static int
rcv_dlnread(SCR * sp,char ** dtypep,char ** datap,FILE * fp)873 rcv_dlnread(SCR *sp, char **dtypep,
874 char **datap, /* free *datap if != NULL after use. */
875 FILE *fp)
876 {
877 int ch;
878 char buf[1024];
879 char *bp = NULL, *p, *src;
880 size_t blen = 0;
881 size_t len, off, dlen;
882 char *dtype, *data;
883 int xlen;
884
885 if (fgets(buf, sizeof(buf), fp) == NULL)
886 return (1);
887 if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) {
888 *dtypep = NULL;
889 *datap = NULL;
890 return (0);
891 }
892
893 /* Fetch an MIME folding header. */
894 len = strlen(buf) - sizeof(VI_DHEADER) + 1;
895 GET_SPACE_GOTOC(sp, bp, blen, len);
896 (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len);
897 p = bp + len;
898 while ((ch = fgetc(fp)) == ' ') {
899 if (fgets(buf, sizeof(buf), fp) == NULL)
900 goto err;
901 off = strlen(buf);
902 len += off;
903 ADD_SPACE_GOTOC(sp, bp, blen, len);
904 p = bp + len - off;
905 (void)memcpy(p, buf, off);
906 }
907 bp[len] = '\0';
908 (void)ungetc(ch, fp);
909
910 for (p = bp; *p == ' ' || *p == '\n'; p++);
911 if ((src = strchr(p, ';')) == NULL)
912 goto err;
913 dlen = src - p;
914 src += 1;
915 len -= src - bp;
916
917 /* Memory looks like: "<data>\0<dtype>\0". */
918 MALLOC(sp, data, dlen + len / 4 * 3 + 2);
919 if (data == NULL)
920 goto err;
921 if ((xlen = (b64_pton(p + dlen + 1,
922 (u_char *)data, len / 4 * 3 + 1))) == -1) {
923 free(data);
924 goto err;
925 }
926 data[xlen] = '\0';
927 dtype = data + xlen + 1;
928 (void)memcpy(dtype, p, dlen);
929 dtype[dlen] = '\0';
930 FREE_SPACE(sp, bp, blen);
931 *dtypep = dtype;
932 *datap = data;
933 return (0);
934
935 err: FREE_SPACE(sp, bp, blen);
936 return (1);
937 alloc_err:
938 msgq(sp, M_SYSERR, NULL);
939 return (-1);
940 }
941