1
2 static char rcsid[] = "@(#)$Id: fileio.c,v 1.9 1996/10/28 16:58:05 wfp5p Exp $";
3
4 /*******************************************************************************
5 * The Elm Mail System - $Revision: 1.9 $ $State: Exp $
6 *
7 * Copyright (c) 1988-1995 USENET Community Trust
8 * Copyright (c) 1986,1987 Dave Taylor
9 *******************************************************************************
10 * Bug reports, patches, comments, suggestions should be sent to:
11 *
12 * Bill Pemberton, Elm Coordinator
13 * flash@virginia.edu
14 *
15 *******************************************************************************
16 * $Log: fileio.c,v $
17 * Revision 1.9 1996/10/28 16:58:05 wfp5p
18 * Beta 1
19 *
20 * Revision 1.8 1996/08/08 19:49:25 wfp5p
21 * Alpha 11
22 *
23 * Revision 1.7 1996/03/14 17:29:34 wfp5p
24 * Alpha 9
25 *
26 * Revision 1.6 1995/09/29 17:42:10 wfp5p
27 * Alpha 8 (Chip's big changes)
28 *
29 * Revision 1.5 1995/09/11 15:19:07 wfp5p
30 * Alpha 7
31 *
32 * Revision 1.4 1995/06/22 14:48:47 wfp5p
33 * Performance enhancements from Paul Close
34 *
35 * Revision 1.3 1995/06/08 13:41:04 wfp5p
36 * A few mostly cosmetic changes
37 *
38 * Revision 1.2 1995/04/20 21:01:47 wfp5p
39 * Added the showreply feature and emacs key bindings.
40 *
41 * Revision 1.1.1.1 1995/04/19 20:38:36 wfp5p
42 * Initial import of elm 2.4 PL0 as base for elm 2.5.
43 *
44 ******************************************************************************/
45
46 /** File I/O routines, including deletion from the folder!
47
48 **/
49
50 #include "elm_defs.h"
51 #include "elm_globals.h"
52 #include "s_elm.h"
53 #include "mailfile.h"
54 #include "port_stat.h"
55
56 static char *makeAttString
57 P_((char *, int, const char *, int, const struct header_rec *));
58
copy_write_error_exit(err)59 static void copy_write_error_exit(err)
60 int err;
61 {
62 ShutdownTerm();
63 error1(catgets(elm_msg_cat, ElmSet, ElmWriteCopyMessageFailed,
64 "Write failed in copy_message()! [%s]"), strerror(err));
65 leave(LEAVE_EMERGENCY);
66 }
67
68 void
copy_message(dest_file,msgnum,cm_options)69 copy_message(dest_file, msgnum, cm_options)
70 FILE *dest_file;
71 int msgnum, cm_options;
72 {
73 /** Copy selected message to destination file, with optional 'prefix'
74 as the prefix for each line. If remove_header is true, it will
75 skip lines in the message until it finds the end of header line...
76 then it will start copying into the file... If remote is true
77 then it will append "remote from <hostname>" at the end of the
78 very first line of the file (for remailing)
79
80 If "update_status" is true then it will write a new Status:
81 line at the end of the headers. It never copies an existing one.
82
83 If "decode" is true, prompt for key if the message is encrypted,
84 else just copy it as it is.
85 **/
86
87 /*
88 * Changed buffer[SLEN] to buffer[VERY_LONG_STRING] to make it
89 * big enough to contain a full length header line. Any header
90 * is allowed to be at least 1024 bytes in length. (r.t.f. RFC)
91 * 14-Sep-1993 Jukka Ukkonen <ukkonen@csc.fi>
92 */
93
94 char *buffer;
95 char *prefix;
96 char attrbuf[SLEN];
97 register struct header_rec *msg_header;
98 register int lines, front_line, next_front,
99 in_header = 1, first_line = TRUE, ignoring = FALSE;
100 int forwarding = !!(cm_options & CM_FORWARDING);
101 int remove_header = !!(cm_options & CM_REMOVE_HEADER);
102 int remote = !!(cm_options & CM_REMOTE);
103 int update_status = !!(cm_options & CM_UPDATE_STATUS);
104 int remail = !!(cm_options & CM_REMAIL);
105 int decode = !!(cm_options & CM_DECODE);
106 #ifdef DONT_ADD_FROM
107 int strip_from = remail;
108 #else /* DONT_ADD_FROM */
109 int strip_from = FALSE;
110 #endif /* DONT_ADD_FROM */
111 int end_header = 0;
112 int sender_added = 0;
113 int crypt_line = OFF, crypted = OFF;
114 static int start_encode_len = 0, end_encode_len = 0;
115 int bytes_seen = 0;
116 int buf_len, err;
117 struct mailFile mailFile;
118 FAST_COMP_DECLARE;
119
120 /** set up lengths of encode/decode strings so we don't have to strlen()
121 ** every time */
122 if (start_encode_len == 0) {
123 start_encode_len = strlen(MSSG_START_ENCODE);
124 end_encode_len = strlen(MSSG_END_ENCODE);
125 }
126
127 msg_header = curr_folder.headers[msgnum-1];
128 prefix = ((cm_options & CM_PREFIX) ? prefixchars : "");
129
130 /** get to the first line of the message desired **/
131
132 if (fseek(curr_folder.fp, msg_header->offset, 0) == -1) {
133 dprint(1, (debugfile,
134 "ERROR: Attempt to seek %d bytes into file failed (%s)",
135 msg_header->offset, "copy_message"));
136 error1(catgets(elm_msg_cat, ElmSet, ElmSeekFailed,
137 "ELM [seek] failed trying to read %d bytes into file."),
138 msg_header->offset);
139 return;
140 }
141
142 /* how many lines in message? */
143
144 lines = msg_header->lines;
145
146 if (msg_header->encrypted && decode) {
147 get_encode_key(OFF);
148 }
149
150 /* now while not EOF & still in message... copy it! */
151
152 next_front = TRUE;
153 mailFile_attach(&mailFile, curr_folder.fp);
154
155 /* emit the opening attribution string */
156 if (cm_options & CM_ATTRIBUTION) {
157 if (forwarding) {
158 if (fwdattribution[0] != '\0') {
159 prefix = "";
160 fputs(makeAttString(attrbuf, sizeof(attrbuf),
161 fwdattribution, 0, msg_header), dest_file);
162 putc('\n', dest_file);
163 } else {
164 fputs(catgets(elm_msg_cat, ElmSet, ElmCopyMssgAttributionForwarded,
165 "Forwarded message:\n"), dest_file);
166 }
167 } else if (attribution[0]) {
168 fputs(makeAttString(attrbuf, sizeof(attrbuf),
169 attribution, 0, msg_header), dest_file);
170 putc('\n', dest_file);
171 }
172 }
173
174 while (lines) {
175 if (! (buf_len = mailFile_gets(&buffer, &mailFile)))
176 break;
177
178 bytes_seen += buf_len;
179 front_line = next_front;
180
181 if(buffer[buf_len - 1] == '\n') {
182 lines--; /* got a full line */
183 next_front = TRUE;
184 }
185 else
186 next_front = FALSE;
187
188 if (front_line && ignoring)
189 ignoring = whitespace(buffer[0]);
190
191 if (ignoring)
192 continue;
193
194 /* preload the first char of the line for fast comparisons */
195 fast_comp_load(buffer[0]);
196
197 #ifdef MMDF
198 if ((cm_options & CM_MMDF_HEAD) && strcmp(buffer, MSG_SEPARATOR) == 0)
199 continue;
200 #endif /* MMDF */
201
202 /* are we still in the header? */
203
204 if (in_header && front_line) {
205 if (buf_len < 2) {
206 in_header = 0;
207 bytes_seen = 0;
208 end_header = -1;
209 if (remail && !sender_added) {
210 if (fprintf(dest_file, "%sSender: %s\n", prefix, user_name) == EOF) {
211 copy_write_error_exit(errno);
212 }
213 }
214 }
215 else if (!isspace(*buffer)
216 && index(buffer, ':') == NULL
217 #ifdef MMDF
218 && strcmp(buffer, MSG_SEPARATOR) != 0
219 #endif /* MMDF */
220 ) {
221 in_header = 0;
222 bytes_seen = 0;
223 end_header = 1;
224 if (remail && !sender_added) {
225 if (fprintf(dest_file, "%sSender: %s\n", prefix, user_name) == EOF) {
226 copy_write_error_exit(errno);
227 }
228 }
229 } else if (in_header && remote && fast_header_cmp(buffer, "Sender", (char *)NULL)) {
230 if (remail)
231 if (fprintf(dest_file, "%sSender: %s\n", prefix, user_name) == EOF) {
232 copy_write_error_exit(errno);
233 }
234 sender_added = TRUE;
235 continue;
236 }
237 if (end_header) {
238 if (update_status) {
239 if (isoff(msg_header->status, NEW)) {
240 if (ison(msg_header->status, UNREAD)) {
241 if (fprintf(dest_file, "%sStatus: O\n", prefix) == EOF) {
242 copy_write_error_exit(errno);
243 }
244 } else { /* read */
245 int x;
246 #ifdef BSD
247 if (fprintf(dest_file, "%sStatus: OR", prefix) == EOF)
248 #else
249 if (fprintf(dest_file, "%sStatus: RO", prefix) == EOF)
250 #endif
251 {
252 copy_write_error_exit(errno);
253 }
254
255 if (ison(msg_header->status, REPLIED_TO))
256 {
257 if (putc('r', dest_file) == EOF)
258 copy_write_error_exit (errno);
259 }
260
261 for (x=0;msg_header->mailx_status[x] != '\0'; x++)
262 {
263 if ( strchr("ROr",msg_header->mailx_status[x]) == NULL)
264 {
265 if (putc(msg_header->mailx_status[x], dest_file) == EOF)
266 {
267 copy_write_error_exit(errno);
268 }
269 }
270 }
271
272 if (putc('\n', dest_file) == EOF)
273 copy_write_error_exit (errno);
274
275 }
276
277 update_status = FALSE; /* do it only once */
278 } /* else if NEW - indicate NEW with no Status: line. This is
279 * important if we resync a mailfile - we don't want
280 * NEW status lost when we copy each message out.
281 * It is the responsibility of the function that calls
282 * this function to unset NEW as appropriate to its
283 * reason for using this function to copy a message
284 */
285
286 /*
287 * add the missing newline for RFC 822
288 */
289 if (end_header > 0) {
290 /* add the missing newline for RFC 822 */
291 if (putc('\n', dest_file) == EOF) {
292 copy_write_error_exit(errno);
293 }
294 }
295 }
296 }
297 }
298
299 if (in_header) {
300 /* Process checks while in header area */
301
302 if (remove_header) {
303 ignoring = TRUE;
304 continue;
305 }
306
307 /* add remote on to front? */
308 if (first_line && remote) {
309 no_ret(buffer);
310 #ifndef MMDF
311 if (!strip_from && fprintf(dest_file, "%s%s remote from %s\n",
312 prefix, buffer, host_name) == EOF) {
313 copy_write_error_exit(errno);
314 }
315 #else
316 if (fast_strbegConst(buffer, "From ")) {
317 if (!strip_from && fprintf(dest_file, "%s%s remote from %s\n",
318 prefix, buffer, host_name) == EOF) {
319 copy_write_error_exit(errno);
320 }
321 } else {
322 if (fprintf(dest_file, "%s%s\n", prefix, buffer) == EOF) {
323 copy_write_error_exit(errno);
324 }
325 }
326 #endif /* MMDF */
327 first_line = FALSE;
328 continue;
329 }
330
331 if (!forwarding) {
332 if (fast_header_cmp(buffer, "Status", (char *)NULL) ||
333 /* we will output a new Status: line later, if desired. */
334 (strip_from && fast_stribegConst(buffer, ">From"))) {
335 ignoring = TRUE;
336 continue;
337 } else {
338 err = 0;
339 if (*prefix) err = fputs(prefix, dest_file);
340 if (err != EOF) err = fwrite(buffer, 1, buf_len, dest_file);
341 if (err != buf_len) copy_write_error_exit(errno);
342 continue;
343 }
344 }
345 else { /* forwarding */
346
347 if (fast_header_cmp(buffer, "Received", (char *)NULL)
348 || fast_stribegConst(buffer, ">From")
349 || fast_header_cmp(buffer, "Status", (char *)NULL)
350 || fast_header_cmp(buffer, "Return-Path", (char *)NULL))
351 ignoring = TRUE;
352 else
353 if (remail && fast_header_cmp(buffer, "To", (char *)NULL)) {
354 if (fprintf(dest_file, "%sOrig-%s", prefix, buffer) == EOF) {
355 copy_write_error_exit(errno);
356 }
357 } else {
358 err = 0;
359 if (*prefix) err = fputs(prefix, dest_file);
360 if (err != EOF) err = fwrite(buffer, 1, buf_len, dest_file);
361 if (err != buf_len) copy_write_error_exit(errno);
362 }
363 }
364 }
365 else { /* not in header */
366 /* Process checks that occur after the header area */
367
368 /* perform encryption checks */
369 if (buffer[0] == '[' && decode) {
370 if (!strncmp(buffer, MSSG_START_ENCODE, start_encode_len)) {
371 crypted = ON;
372 crypt_line = ON;
373 } else if (!strncmp(buffer, MSSG_END_ENCODE, end_encode_len)) {
374 crypted = OFF;
375 crypt_line = ON;
376 } else if (crypted) {
377 no_ret(buffer);
378 encode(buffer);
379 strcat(buffer, "\n");
380 }
381 } else if (crypted) {
382 no_ret(buffer);
383 encode(buffer);
384 strcat(buffer, "\n");
385 }
386
387
388 #ifndef MMDF
389 #ifndef DONT_ESCAPE_MESSAGES
390 if (msg_header->content_length <= bytes_seen &&
391 fast_strbegConst(buffer, "From ") && (real_from(buffer, NULL))) {
392 /* If we have a content-length > bytes_seen and there is lines left
393 ** this is probably a From line that is part of the body, as an
394 ** included message. A simple heuristic test.
395 */
396 dprint(1, (debugfile,
397 "\n*** Internal Problem...Tried to add the following;\n"));
398 dprint(1, (debugfile,
399 " '%s'\nto output file (copy_message) ***\n", buffer));
400 break; /* STOP NOW! */
401 }
402 #endif /* DONT_ESCAPE_MESSAGES */
403 #endif /* MMDF */
404
405 err = 0;
406 if (*prefix && !crypt_line) err = fputs(prefix, dest_file);
407 crypt_line = OFF;
408 if (err != EOF) err = fwrite(buffer, 1, buf_len, dest_file);
409 if (err != buf_len) copy_write_error_exit(errno);
410 }
411 }
412 #ifndef MMDF
413 if (buf_len + strlen(prefix) > 1)
414 if (putc('\n', dest_file) == EOF) { /* blank line to keep mailx happy *sigh* */
415 copy_write_error_exit(errno);
416 }
417 #endif /* MMDF */
418
419 /* emit the closing attribution */
420 if ((cm_options & CM_ATTRIBUTION) && forwarding && fwdattribution[0]) {
421 fputs(makeAttString(attrbuf, sizeof(attrbuf),
422 fwdattribution, 1, msg_header), dest_file);
423 putc('\n', dest_file);
424 }
425
426 /* Since fprintf is buffered, its return value is only useful for
427 * writes which exceed the blocksize. Do a fflush to ensure that
428 * the message has, in fact, been written.
429 */
430 if (fflush(dest_file) == EOF) {
431 copy_write_error_exit(errno);
432 }
433 mailFile_detach(&mailFile);
434 }
435
436
437 static char *
makeAttString(retbuf,retbuflen,attribution,sel_field,messageHeader)438 makeAttString(retbuf, retbuflen, attribution, sel_field, messageHeader)
439 char *retbuf; /* storage space for result */
440 int retbuflen; /* size of result buffer */
441 const char *attribution; /* attribution string to expand */
442 int sel_field; /* field to select in "%[...]" list */
443 const struct header_rec *messageHeader; /* current message header info */
444 {
445 const char *aptr = attribution; /* cursor into the attribution spec */
446 char *rptr = retbuf; /* cursor into the result buffer */
447 const char *expval; /* next item to add to result buffer */
448 int explen; /* length of expansion value */
449 int in_selection; /* currently doing %[...] list? */
450 int curr_field; /* field number of current %[...] list */
451 char fromfield[STRING]; /* room for parsed from address */
452
453 --retbuflen; /* reserve space for '\0' */
454 in_selection = FALSE;
455
456 /*
457 * Process the attribution string.
458 */
459 for ( ; retbuflen > 1 && *aptr != '\0' ; ++aptr) {
460
461 /*
462 * Handle the character if it is not a %-expansion.
463 */
464 switch (*aptr) {
465
466 case '|': /* next choice of "%[sel0|sel1|...]" list */
467 if (in_selection) {
468 ++curr_field;
469 expval = NULL;
470 } else {
471 expval = aptr;
472 explen = 1;
473 }
474 break;
475
476 case ']': /* end of "%[sel0|sel1|...]" list */
477 if (in_selection) {
478 in_selection = FALSE;
479 expval = NULL;
480 } else {
481 expval = aptr;
482 explen = 1;
483 }
484 break;
485
486 case '\\': /* backslash-quoting */
487 switch (*++aptr) {
488 case 't':
489 expval = "\t";
490 explen = 1;
491 break;
492 case 'n':
493 expval = "\n";
494 explen = 1;
495 break;
496 case '\0':
497 --aptr;
498 /*FALLTHRU*/
499 default:
500 expval = aptr;
501 explen = 1;
502 break;
503 }
504 break;
505
506 case '%': /* special %-expansion */
507
508 switch (*++aptr) {
509
510 case 's': /* backward compatibility with 2.4 */
511 case 'F': /* expand from */
512 expval = messageHeader->from;
513 explen = strlen(expval);
514 break;
515
516 case 'D': /* expand date */
517 expval = ctime(&messageHeader->time_sent);
518 explen = strlen(expval)-1; /* exclude newline */
519 break;
520
521 case 'I': /* expand message ID */
522 expval = messageHeader->messageid;
523 explen = strlen(expval);
524 break;
525
526 case 'S': /* expand subject */
527 expval = messageHeader->subject;
528 explen = strlen(expval);
529 break;
530
531 case '[': /* %[sel0|sel1|...] */
532 in_selection = TRUE;
533 curr_field = 0;
534 expval = NULL;
535 break;
536
537 case ')': /* special case for %)F - from name */
538 /*FALLTHROUGH*/
539 case '>': /* special case for %>F - from address */
540 if (*++aptr != 'F') {
541 expval = aptr-2;
542 explen = 3;
543 }
544 else {
545 int ret;
546 if (*(aptr-1) == ')') /* from name */
547 ret = parse_arpa_mailbox(messageHeader->allfrom, NULL, 0,
548 fromfield, sizeof(fromfield), NULL);
549 else
550 if (*(aptr-1) == '>') /* from addr */
551 ret = parse_arpa_mailbox(messageHeader->allfrom,
552 fromfield, sizeof(fromfield), NULL, 0, NULL);
553 else {
554 sprintf(fromfield, "error: %c", *(aptr-1));
555 }
556 if (ret < 0 || *fromfield == '\0') {
557 explen = 0;
558 while (*(aptr+1) && whitespace(*(aptr+1)))
559 aptr++;
560 }
561 else {
562 expval = fromfield;
563 explen = strlen(expval);
564 }
565 }
566 break;
567
568 case '%': /* add a % and skip on past... */
569 expval = aptr;
570 explen = 1;
571 break;
572
573 case '\0':
574 expval = --aptr;
575 explen = 1;
576 break;
577
578 default:
579 expval = aptr-1;
580 explen = 2;
581 break;
582
583 }
584
585 break;
586
587 default: /* just a regular char */
588 expval = aptr;
589 explen = 1;
590
591 }
592
593 /*
594 * Add the expansion value to the result.
595 */
596 if (expval != NULL && (!in_selection || curr_field == sel_field)) {
597 if (explen < retbuflen) {
598 (void) strncpy(rptr, expval, explen);
599 rptr += explen;
600 }
601 retbuflen -= explen;
602 }
603
604 }
605
606 *rptr = '\0';
607 return(retbuf);
608 }
609
610
611 static struct stat saved_buf;
612 static char saved_fname[SLEN];
613
614 int
save_file_stats(fname)615 save_file_stats(fname)
616 char *fname;
617 {
618 /* if fname exists, save the owner, group, mode and filename.
619 * otherwise flag nothing saved. Return 0 if saved, else -1.
620 */
621
622 if (stat(fname, &saved_buf) == 0) {
623 (void)strfcpy(saved_fname, fname, sizeof(saved_fname));
624 dprint(2, (debugfile,
625 "save_file_stats(%s) successful - owner=%d group=%d mode=%o\n",
626 fname, saved_buf.st_uid, saved_buf.st_gid, saved_buf.st_mode));
627 return(0);
628 }
629 saved_fname[0] = '\0';
630 dprint(2, (debugfile, "save_file_stats(%s) FAILED [%s]\n",
631 fname, strerror(errno)));
632 return(-1);
633
634 }
635
restore_file_stats(fname)636 restore_file_stats(fname)
637 char *fname;
638 {
639 /* if fname matches the saved file name, set the owner and group
640 * of fname to the saved owner, group and mode,
641 * else to the userid and groupid of the user and to 700.
642 * Return -1 if the either mode or owner/group not set
643 * 0 if the default values were used
644 * 1 if the saved values were used
645 */
646
647 int old_umask, i, new_mode, new_owner, new_group, ret_code;
648 int chown_restricted;
649
650
651 new_mode = 0600;
652 new_owner = userid;
653 new_group = groupid;
654 ret_code = 0;
655
656 if(strcmp(fname, saved_fname) == 0) {
657 new_mode = saved_buf.st_mode;
658 new_owner = saved_buf.st_uid;
659 new_group = saved_buf.st_gid;
660 ret_code = 1;
661 }
662 dprint(2, (debugfile, "restore_file_stats(%s) - %s file stats\n",
663 fname, (ret_code ? "restoring previous" : "setting new")));
664
665 old_umask = umask(0);
666 if (chmod(fname, new_mode & 0777) < 0) {
667 ret_code = -1;
668 dprint(4, (debugfile, " chmod(%s, %.3o) FAILED [%s]\n",
669 fname, (new_mode & 0777), strerror(errno)));
670 } else {
671 dprint(4, (debugfile, " chmod(%s, %.3o) succeeded\n",
672 fname, (new_mode & 0777)));
673 }
674 (void) umask(old_umask);
675
676 #ifdef BSD
677 # define CHOWN_IS_RESTRICTED TRUE /* BSD restricts chown() to root */
678 #else
679 # ifdef _PC_CHOWN_RESTRICTED
680 # define CHOWN_IS_RESTRICTED pathconf(fname, _PC_CHOWN_RESTRICTED)
681 # else
682 # define CHOWN_IS_RESTRICTED FALSE /* or so we would like to think... */
683 # endif
684 #endif
685
686 if (elm_chown(fname, new_owner, new_group) < 0) {
687 if (!CHOWN_IS_RESTRICTED)
688 ret_code = -1;
689 dprint(4, (debugfile, " elm_chown(%s, %d, %d) FAILED [%s]\n",
690 fname, new_owner, new_group, strerror(errno)));
691 } else {
692 dprint(4, (debugfile, " elm_chown(%s, %d, %d) succeeded\n",
693 fname, new_owner, new_group));
694 }
695
696 return(ret_code);
697 }
698
699