1
2 static char rcsid[] = "@(#)$Id: showmsg.c,v 1.8 1999/03/24 14:04:05 wfp5p Exp $";
3
4 /*******************************************************************************
5 * The Elm Mail System - $Revision: 1.8 $ $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: showmsg.c,v $
17 * Revision 1.8 1999/03/24 14:04:05 wfp5p
18 * elm 2.5PL0
19 *
20 * Revision 1.7 1996/10/28 16:58:10 wfp5p
21 * Beta 1
22 *
23 * Revision 1.6 1996/08/08 19:49:30 wfp5p
24 * Alpha 11
25 *
26 * Revision 1.5 1996/05/09 15:51:27 wfp5p
27 * Alpha 10
28 *
29 * Revision 1.4 1996/03/14 17:29:51 wfp5p
30 * Alpha 9
31 *
32 * Revision 1.3 1995/09/29 17:42:27 wfp5p
33 * Alpha 8 (Chip's big changes)
34 *
35 * Revision 1.2 1995/06/12 20:33:37 wfp5p
36 * Alpha 2 clean up
37 *
38 * Revision 1.1.1.1 1995/04/19 20:38:38 wfp5p
39 * Initial import of elm 2.4 PL0 as base for elm 2.5.
40 *
41 ******************************************************************************/
42
43 /** This file contains all the routines needed to display the specified
44 message.
45 **/
46
47 #include "elm_defs.h"
48 #include "elm_globals.h"
49 #include "s_elm.h"
50 #include "port_wait.h"
51
52 #ifndef I_UNISTD
53 void _exit();
54 #endif
55
56 extern char *elm_date_str();
57
58 int pipe_abort = FALSE; /* did we receive a SIGNAL(SIGPIPE)? */
59
60 FILE *pipe_wr_fp; /* file pointer to write to external pager */
61 extern int lines_displayed, /* defined in "builtin" */
62 lines_put_on_screen; /* ditto too! */
63
64 /*
65 * FOO - I believe the SIGWINCH handling is botched. By ignoring it,
66 * it is causing Elm to lose track of screen changes. I don't understand
67 * why this needs to be done, the SIGWINCH handler should be harmless.
68 * For the time being, I'm removing SIGWINCH, which will prevent the
69 * routine from dinking around with it.
70 */
71 #ifdef SIGWINCH
72 # undef SIGWINCH
73 #endif
74
75 int
show_msg(number)76 show_msg(number)
77 int number;
78 {
79 /*** Display number'th message. Get starting and ending lines
80 of message from headers data structure, then fly through
81 the file, displaying only those lines that are between the
82 two!
83
84 Return 0 to return to the index screen or a character entered
85 by the user to initiate a command without returning to
86 the index screen (to be processed via process_showmsg_cmd()).
87 ***/
88
89 char title1[SLEN], title2[SLEN], title3[SLEN], titlebuf[SLEN];
90 char who[LONG_STRING], buffer[VERY_LONG_STRING];
91 waitstatus_t wait_stat;
92
93 int crypted = 0; /* encryption */
94 static int start_encode_len = 0, end_encode_len = 0;
95 int weed_header, weeding_out = 0; /* weeding */
96 int using_to, /* misc use */
97 pipe_fd[2], /* pipe file descriptors */
98 new_pipe_fd, /* dup'ed pipe fil des */
99 lines, /* num lines in msg */
100 fork_ret, /* fork return value */
101 wait_ret, /* wait return value */
102 form_letter = FALSE, /* Form ltr? */
103 form_letter_section = 0, /* section */
104 padding = 0, /* counter */
105 builtin = FALSE, /* our pager? */
106 val = 0, /* return val */
107 buf_len, /* line length */
108 err; /* place holder for errno */
109 struct header_rec *current_header = curr_folder.headers[number-1];
110 #ifdef SIGTSTP
111 SIGHAND_TYPE (*oldstop)(), (*oldcont)();
112 #endif
113 #ifdef SIGWINCH
114 SIGHAND_TYPE (*oldwinch)();
115 #endif
116
117 /** set up lengths of encode/decode strings so we don't have to
118 ** strlen() every time */
119 if (start_encode_len == 0) {
120 start_encode_len = strlen(MSSG_START_ENCODE);
121 end_encode_len = strlen(MSSG_END_ENCODE);
122 }
123
124 lines = current_header->lines;
125
126 dprint(4, (debugfile,"displaying %d lines from message %d using %s\n",
127 lines, number, pager));
128
129 if (number > curr_folder.num_mssgs || number < 1)
130 return(val);
131
132 if(ison(current_header->status, NEW)) {
133 clearit(current_header->status, NEW); /* it's been read now! */
134 current_header->status_chgd = TRUE;
135 }
136 if(ison(current_header->status, UNREAD)) {
137 clearit(current_header->status, UNREAD); /* it's been read now! */
138 current_header->status_chgd = TRUE;
139 }
140
141 #ifdef MIME_RECV
142 if ((current_header->status & MIME_MESSAGE) &&
143 ((current_header->status & MIME_NEEDDECOD) ||
144 (current_header->status & MIME_NOTPLAIN)) &&
145 !getenv("NOMETAMAIL") ) {
146 char fname[STRING], Cmd[SLEN], line[VERY_LONG_STRING];
147 int code, err;
148 long lines = current_header->lines;
149 FILE *fpout;
150
151 if (fseek(curr_folder.fp, current_header->offset, 0) != -1) {
152 sprintf(fname, "%semm.%d.%d", temp_dir, getpid(), getuid());
153 if ((fpout = fopen(fname, "w")) != NULL) {
154 copy_message(fpout, curr_folder.curr_mssg, CM_DECODE);
155 (void) fclose (fpout);
156 sprintf(Cmd, "metamail -p -z -m Elm %s", fname);
157 Raw(OFF);
158 softkeys_off();
159 EnableFkeys(OFF);
160 ClearScreen();
161 code = system_call(Cmd, SY_ENAB_SIGINT);
162 Raw(ON | NO_TITE); /* Raw on but don't switch screen */
163 (void) unlink (fname);
164 if (code == 0) {
165 PutLine0(LINES,0, catgets(elm_msg_cat, ElmSet, ElmPressAnyKeyIndex,
166 "Press any key to return to index."));
167 (void) GetKey(0);
168 NewLine();
169 Raw(OFF | NO_TITE); /* Raw off so raw on takes effect */
170 Raw(ON); /* Finally raw on and switch screen */
171 softkeys_on();
172 EnableFkeys(ON);
173
174 return(0);
175 }
176 }
177 }
178 }
179 #endif
180
181 if (fseek(curr_folder.fp, current_header->offset, 0) == -1) {
182 err = errno;
183 dprint(1, (debugfile,
184 "Error: seek %d bytes into file, errno %s (show_message)\n",
185 current_header->offset, strerror(err)));
186 error2(catgets(elm_msg_cat, ElmSet, ElmSeekFailedFile,
187 "ELM [seek] couldn't read %d bytes into file (%s)."),
188 current_header->offset, strerror(err));
189 return(val);
190 }
191 if(current_header->encrypted)
192 get_encode_key(OFF);
193
194 builtin = (
195 (strbegConst(pager,"builtin")|| strbegConst(pager,"internal"))
196 || ( builtin_lines < 0
197 ? lines < LINES + builtin_lines : lines < builtin_lines)
198 );
199
200 if (builtin) {
201
202 start_builtin(lines);
203
204 } else {
205
206 /* put terminal out of raw mode so external pager has normal env */
207 Raw(OFF);
208
209 /* create pipe for external pager and fork */
210
211 if(pipe(pipe_fd) == -1) {
212 err = errno;
213 dprint(1, (debugfile, "Error: pipe failed, errno %s (show_msg)\n",
214 strerror(err)));
215 error1(catgets(elm_msg_cat, ElmSet, ElmPreparePagerPipe,
216 "Could not prepare for external pager(pipe()-%s)."),
217 strerror(err));
218 Raw(ON);
219 return(val);
220 }
221
222 if((fork_ret = fork()) == -1) {
223
224 err = errno;
225 dprint(1, (debugfile, "Error: fork failed, errno %s (show_msg)\n",
226 strerror(err)));
227 error1(catgets(elm_msg_cat, ElmSet, ElmPreparePagerFork,
228 "Could not prepare for external pager(fork()-%s)."),
229 strerror(err));
230 Raw(ON);
231 return(val);
232
233 } else if (fork_ret == 0) {
234
235 /* child fork */
236
237 /* close write-only pipe fd and fit read-only pipe fd to stdin */
238
239 close(pipe_fd[1]);
240 close(fileno(stdin));
241 if((new_pipe_fd = dup(pipe_fd[0])) == -1) {
242 err = errno;
243 dprint(1, (debugfile, "Error: dup failed, errno %s (show_msg)\n",
244 strerror(err)));
245 error1(catgets(elm_msg_cat, ElmSet, ElmPreparePagerDup,
246 "Could not prepare for external pager(dup()-%s)."),
247 strerror(err));
248 _exit(err);
249 }
250 close(pipe_fd[0]); /* original pipe fd no longer needed */
251
252 /* use stdio on new pipe fd */
253 if(fdopen(new_pipe_fd, "r") == NULL) {
254 err = errno;
255 dprint(1,
256 (debugfile, "Error: child fdopen failed, errno %s (show_msg)\n",
257 strerror(err)));
258 error1(catgets(elm_msg_cat, ElmSet, ElmPreparePagerChildFdopen,
259 "Could not prepare for external pager(child fdopen()-%s)."),
260 strerror(err));
261 _exit(err);
262 }
263
264 /* now execute pager and exit */
265
266 /* system_call() will return user to user's normal permissions.
267 * This is what makes this pipe secure - user won't have elm's
268 * special SETGID permissions (if so configured) and will only
269 * be able to execute a pager that user normally has permission
270 * to execute */
271
272 _exit(system_call(pager, SY_ENAB_SIGINT));
273
274 } /* else this is the parent fork */
275
276 /* close read-only pipe fd and do write-only with stdio */
277 close(pipe_fd[0]);
278
279 if((pipe_wr_fp = fdopen(pipe_fd[1], "w")) == NULL) {
280 err = errno;
281 dprint(1,
282 (debugfile, "Error: parent fdopen failed, errno %s (show_msg)\n",
283 strerror(err)));
284 error1(catgets(elm_msg_cat, ElmSet, ElmPreparePagerParentFdopen,
285 "Could not prepare for external pager(parent fdopen()-%s)."),
286 strerror(err));
287
288 /* Failure - must close pipe and wait for child */
289 close(pipe_fd[1]);
290 while ((wait_ret = wait(&wait_stat)) != fork_ret && wait_ret!= -1)
291 ;
292
293 Raw(OFF);
294 return(val); /* pager may have already touched the screen */
295 }
296
297 /* and that's it! */
298 lines_displayed = 0;
299 #ifdef SIGTSTP
300 oldstop = signal(SIGTSTP,SIG_DFL);
301 oldcont = signal(SIGCONT,SIG_DFL);
302 #endif
303 #ifdef SIGWINCH
304 oldwinch = signal(SIGWINCH,SIG_DFL);
305 #endif
306 EnableFkeys(OFF);
307 }
308
309 ClearScreen();
310
311 pipe_abort = FALSE;
312
313 if (form_letter = (current_header->status&FORM_LETTER)) {
314 if (filter)
315 form_letter_section = 1; /* initialize to section 1 */
316 }
317
318 if (title_messages && filter) {
319
320 using_to =
321 tail_of(current_header->from, who, current_header->to);
322
323 MCsprintf(title1, "%s %d/%d ",
324 curr_folder.headers[curr_folder.curr_mssg-1]->status & DELETED
325 ? nls_deleted : form_letter ? nls_form : nls_message,
326 number, curr_folder.num_mssgs);
327 MCsprintf(title2, "%s %s", using_to? nls_to : nls_from, who);
328 elm_date_str(title3, current_header, FALSE);
329 strcat(title3, " ");
330 strcat(title3, current_header->time_zone);
331
332 /* truncate or pad title2 portion on the right
333 * so that line fits exactly */
334 padding =
335 COLS - (strlen(title1) + (buf_len=strlen(title2)) + strlen(title3));
336
337 sprintf(titlebuf, "%s%-*.*s%s\n", title1, buf_len+padding,
338 buf_len+padding, title2, title3);
339
340 if (builtin)
341 display_line(titlebuf, strlen(titlebuf));
342 else
343 fprintf(pipe_wr_fp, "%s", titlebuf);
344
345 /** if there's a subject, let's output it next,
346 centered if it fits on a single line. **/
347
348 if ((buf_len = strlen(current_header->subject)) > 0 &&
349 matchInList(weedlist,weedcount,"Subject:",TRUE)) {
350 padding = (buf_len < COLS ? COLS - buf_len : 0);
351 sprintf(buffer, "%*s%s\n", padding/2, "", current_header->subject);
352 } else
353 strcpy(buffer, "\n");
354
355 if (builtin)
356 display_line(buffer, strlen(buffer));
357 else
358 fprintf(pipe_wr_fp, "%s", buffer);
359
360 /** was this message address to us? if not, then to whom? **/
361
362 if (! using_to && matchInList(weedlist,weedcount,"To:",TRUE) && filter &&
363 strcmp(current_header->to, user_name) != 0 &&
364 strlen(current_header->to) > 0) {
365 sprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmMessageAddressedTo,
366 "%s(message addressed to %.60s)\n"),
367 strlen(current_header->subject) > 0 ? "\n" : "",
368 current_header->to);
369 if (builtin)
370 display_line(buffer, strlen(buffer));
371 else
372 fprintf(pipe_wr_fp, "%s", buffer);
373 }
374
375 /** The test above is: if we didn't originally send the mail
376 (e.g. we're not reading "mail.sent") AND the user is currently
377 weeding out the "To:" line (otherwise they'll get it twice!)
378 AND the user is actually weeding out headers AND the message
379 wasn't addressed to the user AND the 'to' address is non-zero
380 (consider what happens when the message doesn't HAVE a "To:"
381 line...the value is NULL but it doesn't match the username
382 either. We don't want to display something ugly like
383 "(message addressed to )" which will just clutter up the
384 screen!).
385
386 And you thought programming was EASY!!!!
387 **/
388
389 /** one more friendly thing - output a line indicating what sort
390 of status the message has (e.g. Urgent etc). Mostly added
391 for X.400 support, this is nonetheless generally useful to
392 include...
393 **/
394
395 buffer[0] = '\0';
396
397 /* we want to flag Urgent, Confidential, Private and Expired tags */
398
399 if (current_header->status & PRIVATE)
400 strcpy(buffer, catgets(elm_msg_cat, ElmSet, ElmTaggedPrivate,
401 "\n(** This message is tagged Private"));
402 else if (current_header->status & CONFIDENTIAL)
403 strcpy(buffer, catgets(elm_msg_cat, ElmSet, ElmTaggedCompanyConfidential,
404 "\n(** This message is tagged Company Confidential"));
405
406 if (current_header->status & URGENT) {
407 if (buffer[0] == '\0')
408 strcpy(buffer, catgets(elm_msg_cat, ElmSet, ElmTaggedUrgent,
409 "\n(** This message is tagged Urgent"));
410 else if (current_header->status & EXPIRED)
411 strcat(buffer, catgets(elm_msg_cat, ElmSet, ElmCommaUrgent, ", Urgent"));
412 else
413 strcat(buffer, catgets(elm_msg_cat, ElmSet, ElmAndUrgent, " and Urgent"));
414 }
415
416 if (current_header->status & EXPIRED) {
417 if (buffer[0] == '\0')
418 strcpy(buffer, catgets(elm_msg_cat, ElmSet, ElmMessageHasExpired,
419 "\n(** This message has Expired"));
420 else
421 strcat(buffer, catgets(elm_msg_cat, ElmSet, ElmAndHasExpired,
422 ", and has Expired"));
423 }
424
425 if (buffer[0] != '\0') {
426 strcat(buffer, " **)\n");
427 if (builtin)
428 display_line(buffer, strlen(buffer));
429 else
430 fprintf(pipe_wr_fp, buffer);
431 }
432
433 if (builtin) /* this is for a one-line blank */
434 display_line("\n",1); /* separator between the title */
435 else /* stuff and the actual message */
436 fprintf(pipe_wr_fp, "\n"); /* we're trying to display */
437
438 }
439
440 weed_header = filter; /* allow us to change it after header */
441
442 while (lines > 0 && pipe_abort == FALSE) {
443
444 if ((buf_len = mail_gets(buffer, VERY_LONG_STRING, curr_folder.fp)) == 0) {
445
446 dprint(1, (debugfile,
447 "Premature end of file! Lines left = %d msg = %s (show_msg)\n",
448 lines, number));
449
450 error(catgets(elm_msg_cat, ElmSet, ElmPrematureEndOfFile,
451 "Premature end of file!"));
452 if (sleepmsg > 0)
453 sleep(sleepmsg);
454 break;
455 }
456 if (buf_len > 0) {
457 if(buffer[buf_len - 1] == '\n') {
458 lines--;
459 lines_displayed++;
460 }
461 while(buf_len > 0 && (buffer[buf_len - 1] == '\n'
462 ||buffer[buf_len - 1] == '\r'))
463 --buf_len;
464 }
465
466 if (buf_len == 0) {
467 weed_header = 0; /* past header! */
468 weeding_out = 0;
469 }
470
471 if (form_letter && weed_header)
472 /* skip it. NEVER display random headers in forms! */;
473 else if (weed_header && matchInList(weedlist,weedcount,buffer,TRUE))
474 weeding_out = 1; /* aha! We don't want to see this! */
475 else if (buffer[0] == '[') {
476 if (strncmp(buffer, MSSG_START_ENCODE, start_encode_len)==0)
477 crypted = ON;
478 else if (strncmp(buffer, MSSG_END_ENCODE, end_encode_len)==0)
479 crypted = OFF;
480 else if (crypted)
481 encode(buffer);
482 val = show_line(buffer, buf_len, builtin);
483 }
484 else if (crypted) {
485 encode(buffer);
486 val = show_line(buffer, buf_len, builtin);
487 }
488 else if (weeding_out) {
489 weeding_out = (whitespace(buffer[0])); /* 'n' line weed */
490 if (! weeding_out) /* just turned on! */
491 val = show_line(buffer, buf_len, builtin);
492 }
493 else if (form_letter && strbegConst(buffer,"***") && filter) {
494 strcpy(buffer,
495 "\n------------------------------------------------------------------------------\n");
496 val = show_line(buffer, buf_len, builtin); /* hide '***' */
497 form_letter_section++;
498 }
499 else if (form_letter_section == 1 || form_letter_section == 3)
500 /** skip this stuff - we can't deal with it... **/;
501 else
502 val = show_line(buffer, buf_len, builtin);
503
504 if (val != 0) /* discontinue the display */
505 break;
506 }
507
508 if (!builtin) {
509 /* EnableFkeys(ON);*/
510 fclose(pipe_wr_fp);
511 while ((wait_ret = wait(&wait_stat)) != fork_ret
512 && wait_ret!= -1)
513 ;
514 /* turn raw on **after** child terminates in case child
515 * doesn't put us back to cooked mode after we return ourselves
516 * to raw.
517 */
518 Raw(ON);
519 EnableFkeys(ON);
520 #ifdef SIGTSTP
521 (void)signal(SIGTSTP,oldstop);
522 (void)signal(SIGCONT,oldcont);
523 #endif
524 #ifdef SIGWINCH
525 (void)signal(SIGWINCH,oldwinch);
526 #endif
527 }
528
529 /* If we are to prompt for a user input command and we don't
530 * already have one */
531 if ((prompt_after_pager || builtin) && val == 0) {
532 MoveCursor(LINES,0);
533 if (Term.status & TERM_CAN_SO)
534 StartStandout();
535 PutLine0(-1, -1, catgets(elm_msg_cat, ElmSet, ElmCommandIToReturn,
536 " Command ('i' to return to index): "));
537 if (Term.status & TERM_CAN_SO)
538 EndStandout();
539 val = GetKey(0);
540 }
541
542 /* 'q' means quit current operation and pop back up to previous level -
543 * in this case it therefore means return to index screen.
544 */
545 return(val == 'i' || val == 'q' ? 0 : val);
546 }
547
548 int
show_line(buffer,buf_len,builtin)549 show_line(buffer, buf_len, builtin)
550 char *buffer;
551 int buf_len;
552 int builtin;
553 {
554 /** Hands the given line to the output pipe. 'builtin' is true if
555 we're using the builtin pager.
556 Return the character entered by the user to indicate
557 a command other than continuing with the display (only possible
558 with the builtin pager), otherwise 0. **/
559
560 strcpy(buffer + buf_len, "\n");
561 ++buf_len;
562 #ifdef MMDF
563 if (strncmp(buffer, MSG_SEPARATOR, buf_len) == 0)
564 return(0); /* no reason to show the ending terminator */
565 #endif /* MMDF */
566 if (builtin) {
567 return(display_line(buffer, buf_len));
568 }
569 errno = 0;
570 fprintf(pipe_wr_fp, "%s", buffer);
571 if (errno != 0)
572 dprint(1, (debugfile, "\terror %s hit!\n", strerror(errno)));
573 return(0);
574 }
575
576