37a2769a392da68e06e07a24fcc599ea451e22b3
[citadel.git] / citadel / messages.c
1 /*
2  * $Id$
3  *
4  * Citadel message support routines
5  * see copyright.txt for copyright information
6  */
7
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <sys/types.h>
12 #include <fcntl.h>
13 #include <stdio.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <signal.h>
17 #include <errno.h>
18 #include <limits.h>
19 #include <sys/wait.h>
20 #include <sys/stat.h>
21
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #ifdef HAVE_PTHREAD_H
34 #include <pthread.h>
35 #endif
36
37 #include <stdarg.h>
38 #include <libcitadel.h>
39 #include "citadel.h"
40 #include "citadel_ipc.h"
41 #include "citadel_decls.h"
42 #include "messages.h"
43 #include "commands.h"
44 #include "rooms.h"
45 #include "html.h"
46 #ifndef HAVE_SNPRINTF
47 #include "snprintf.h"
48 #endif
49 #include "screen.h"
50
51 #define MAXWORDBUF SIZ
52 #define NO_REPLY_TO     "nobody ... xxxxxx"
53
54 char reply_to[SIZ];
55 char reply_subject[SIZ];
56
57 struct cittext {
58         struct cittext *next;
59         char text[MAXWORDBUF];
60 };
61
62 void stty_ctdl(int cmd);
63 int haschar(const char *st, int ch);
64 void ctdl_getline(char *string, int lim);
65 int file_checksum(char *filename);
66 void progress(CtdlIPC* ipc, unsigned long curr, unsigned long cmax);
67
68 unsigned long *msg_arr = NULL;
69 int msg_arr_size = 0;
70 int num_msgs;
71 char rc_alt_semantics;
72 extern char room_name[];
73 extern char tempdir[];
74 extern unsigned room_flags;
75 extern unsigned room_flags2;
76 extern long highest_msg_read;
77 extern char temp[];
78 extern char temp2[];
79 extern int screenwidth;
80 extern int screenheight;
81 extern long maxmsgnum;
82 extern char is_mail;
83 extern char is_aide;
84 extern char is_room_aide;
85 extern char fullname[];
86 extern char axlevel;
87 extern unsigned userflags;
88 extern char sigcaught;
89 extern char printcmd[];
90 extern int rc_allow_attachments;
91 extern int rc_display_message_numbers;
92 extern int rc_force_mail_prompts;
93 extern int editor_pid;
94 extern CtdlIPC *ipc_for_signal_handlers;        /* KLUDGE cover your eyes */
95 int num_urls = 0;
96 char urls[MAXURLS][SIZ];
97 char imagecmd[SIZ];
98 int has_images = 0;                             /* Current msg has images */
99 struct parts *last_message_parts = NULL;        /* Parts from last msg */
100
101
102
103 void ka_sigcatch(int signum)
104 {
105         alarm(S_KEEPALIVE);
106         signal(SIGALRM, ka_sigcatch);
107         CtdlIPCNoop(ipc_for_signal_handlers);
108 }
109
110
111 /*
112  * server keep-alive version of wait() (needed for external editor)
113  */
114 pid_t ka_wait(int *kstatus)
115 {
116         pid_t p;
117
118         alarm(S_KEEPALIVE);
119         signal(SIGALRM, ka_sigcatch);
120         do {
121                 errno = 0;
122                 p = wait(kstatus);
123         } while (errno == EINTR);
124         signal(SIGALRM, SIG_IGN);
125         alarm(0);
126         return (p);
127 }
128
129
130 /*
131  * version of system() that uses ka_wait()
132  */
133 int ka_system(char *shc)
134 {
135         pid_t childpid;
136         pid_t waitpid;
137         int retcode;
138
139         childpid = fork();
140         if (childpid < 0) {
141                 color(BRIGHT_RED);
142                 perror("Cannot fork");
143                 color(DIM_WHITE);
144                 return ((pid_t) childpid);
145         }
146
147         if (childpid == 0) {
148                 execlp("/bin/sh", "sh", "-c", shc, NULL);
149                 exit(127);
150         }
151
152         if (childpid > 0) {
153                 do {
154                         waitpid = ka_wait(&retcode);
155                 } while (waitpid != childpid);
156                 return (retcode);
157         }
158
159         return (-1);
160 }
161
162
163
164 /*
165  * add a newline to the buffer...
166  */
167 void add_newline(struct cittext *textlist)
168 {
169         struct cittext *ptr;
170
171         ptr = textlist;
172         while (ptr->next != NULL)
173                 ptr = ptr->next;
174
175         while (ptr->text[strlen(ptr->text) - 1] == 32)
176                 ptr->text[strlen(ptr->text) - 1] = 0;
177         /* strcat(ptr->text,"\n"); */
178
179         ptr->next = (struct cittext *)
180             malloc(sizeof(struct cittext));
181         ptr = ptr->next;
182         ptr->next = NULL;
183         strcpy(ptr->text, "");
184 }
185
186
187 /*
188  * add a word to the buffer...
189  */
190 void add_word(struct cittext *textlist, char *wordbuf)
191 {
192         struct cittext *ptr;
193
194         ptr = textlist;
195         while (ptr->next != NULL)
196                 ptr = ptr->next;
197
198         if (3 + strlen(ptr->text) + strlen(wordbuf) > screenwidth) {
199                 ptr->next = (struct cittext *)
200                     malloc(sizeof(struct cittext));
201                 ptr = ptr->next;
202                 ptr->next = NULL;
203                 strcpy(ptr->text, "");
204         }
205
206         strcat(ptr->text, wordbuf);
207         strcat(ptr->text, " ");
208 }
209
210
211 /*
212  * begin editing of an opened file pointed to by fp
213  */
214 void citedit(CtdlIPC *ipc, FILE * fp)
215 {
216         int a, prev, finished, b, last_space;
217         int appending = 0;
218         struct cittext *textlist = NULL;
219         struct cittext *ptr;
220         char wordbuf[MAXWORDBUF];
221
222         /* first, load the text into the buffer */
223         fseek(fp, 0L, 0);
224         textlist = (struct cittext *) malloc(sizeof(struct cittext));
225         textlist->next = NULL;
226         strcpy(textlist->text, "");
227
228         strcpy(wordbuf, "");
229         prev = (-1);
230         while (a = getc(fp), a >= 0) {
231                 appending = 1;
232                 if ((a == 32) || (a == 9) || (a == 13) || (a == 10)) {
233                         add_word(textlist, wordbuf);
234                         strcpy(wordbuf, "");
235                         if ((prev == 13) || (prev == 10)) {
236                                 add_word(textlist, "\n");
237                                 add_newline(textlist);
238                                 add_word(textlist, "");
239                         }
240                 } else {
241                         wordbuf[strlen(wordbuf) + 1] = 0;
242                         wordbuf[strlen(wordbuf)] = a;
243                 }
244                 if (strlen(wordbuf) + 3 > screenwidth) {
245                         add_word(textlist, wordbuf);
246                         strcpy(wordbuf, "");
247                 }
248                 prev = a;
249         }
250
251         /* get text */
252         finished = 0;
253         prev = (appending ? 13 : (-1));
254         strcpy(wordbuf, "");
255         async_ka_start();
256         do {
257                 a = inkey();
258                 if (a == 10)
259                         a = 13;
260                 if (a == 9)
261                         a = 32;
262                 if (a == 127)
263                         a = 8;
264
265                 if ((a != 32) && (prev == 13)) {
266                         add_word(textlist, "\n");
267                         scr_printf(" ");
268                 }
269
270                 if ((a == 32) && (prev == 13)) {
271                         add_word(textlist, "\n");
272                         add_newline(textlist);
273                 }
274
275                 if (a == 8) {
276                         if (!IsEmptyStr(wordbuf)) {
277                                 wordbuf[strlen(wordbuf) - 1] = 0;
278                                 scr_putc(8);
279                                 scr_putc(32);
280                                 scr_putc(8);
281                         }
282                 } else if (a == 23) {
283                         do {
284                                 wordbuf[strlen(wordbuf) - 1] = 0;
285                                 scr_putc(8);
286                                 scr_putc(32);
287                                 scr_putc(8);
288                         } while (!IsEmptyStr(wordbuf) && wordbuf[strlen(wordbuf) - 1] != ' ');
289                 } else if (a == 13) {
290                         scr_printf("\n");
291                         if (IsEmptyStr(wordbuf))
292                                 finished = 1;
293                         else {
294                                 for (b = 0; b < strlen(wordbuf); ++b)
295                                         if (wordbuf[b] == 32) {
296                                                 wordbuf[b] = 0;
297                                                 add_word(textlist,
298                                                          wordbuf);
299                                                 strcpy(wordbuf,
300                                                        &wordbuf[b + 1]);
301                                                 b = 0;
302                                         }
303                                 add_word(textlist, wordbuf);
304                                 strcpy(wordbuf, "");
305                         }
306                 } else {
307                         scr_putc(a);
308                         wordbuf[strlen(wordbuf) + 1] = 0;
309                         wordbuf[strlen(wordbuf)] = a;
310                 }
311                 if ((strlen(wordbuf) + 3) > screenwidth) {
312                         last_space = (-1);
313                         for (b = 0; b < strlen(wordbuf); ++b)
314                                 if (wordbuf[b] == 32)
315                                         last_space = b;
316                         if (last_space >= 0) {
317                                 for (b = 0; b < strlen(wordbuf); ++b)
318                                         if (wordbuf[b] == 32) {
319                                                 wordbuf[b] = 0;
320                                                 add_word(textlist,
321                                                          wordbuf);
322                                                 strcpy(wordbuf,
323                                                        &wordbuf[b + 1]);
324                                                 b = 0;
325                                         }
326                                 for (b = 0; b < strlen(wordbuf); ++b) {
327                                         scr_putc(8);
328                                         scr_putc(32);
329                                         scr_putc(8);
330                                 }
331                                 scr_printf("\n%s", wordbuf);
332                         } else {
333                                 add_word(textlist, wordbuf);
334                                 strcpy(wordbuf, "");
335                                 scr_printf("\n");
336                         }
337                 }
338                 prev = a;
339         } while (finished == 0);
340         async_ka_end();
341
342         /* write the buffer back to disk */
343         fseek(fp, 0L, 0);
344         for (ptr = textlist; ptr != NULL; ptr = ptr->next) {
345                 fprintf(fp, "%s", ptr->text);
346         }
347         putc(10, fp);
348         fflush(fp);
349         ftruncate(fileno(fp), ftell(fp));
350
351         /* and deallocate the memory we used */
352         while (textlist != NULL) {
353                 ptr = textlist->next;
354                 free(textlist);
355                 textlist = ptr;
356         }
357 }
358
359
360 /*
361  * Free the struct parts
362  */
363 void free_parts(struct parts *p)
364 {
365         struct parts *a_part = p;
366
367         while (a_part) {
368                 struct parts *q;
369
370                 q = a_part;
371                 a_part = a_part->next;
372                 free(q);
373         }
374 }
375
376
377 /*
378  * Read a message from the server
379  */
380 int read_message(CtdlIPC *ipc,
381         long num,   /* message number */
382         int pagin, /* 0 = normal read, 1 = read with pagination, 2 = header */
383         FILE *dest) /* Destination file, NULL for screen */
384 {
385         char buf[SIZ];
386         char now[SIZ];
387         int format_type = 0;
388         int fr = 0;
389         int nhdr = 0;
390         struct ctdlipcmessage *message = NULL;
391         int r;                          /* IPC response code */
392         char *converted_text = NULL;
393         char *lineptr;
394         char *nextline;
395         char *searchptr;
396         int i;
397         char ch;
398         int linelen;
399         int final_line_is_blank = 0;
400
401         has_images = 0;
402
403         sigcaught = 0;
404         stty_ctdl(1);
405
406         strcpy(reply_to, NO_REPLY_TO);
407         strcpy(reply_subject, "");
408
409         r = CtdlIPCGetSingleMessage(ipc, num, (pagin == READ_HEADER ? 1 : 0), 4, &message, buf);
410         if (r / 100 != 1) {
411                 err_printf("*** msg #%ld: %d %s\n", num, r, buf);
412                 ++lines_printed;
413                 lines_printed = checkpagin(lines_printed, pagin, screenheight);
414                 stty_ctdl(0);
415                 free(message->text);
416                 free_parts(message->attachments);
417                 free(message);
418                 return (0);
419         }
420
421         if (dest) {
422                 fprintf(dest, "\n ");
423         } else {
424                 scr_printf("\n");
425                 ++lines_printed;
426                 lines_printed = checkpagin(lines_printed, pagin, screenheight);
427                 if (pagin != 2)
428                         scr_printf(" ");
429         }
430         if (pagin == 1 && !dest) {
431                 color(BRIGHT_CYAN);
432         }
433
434         /* View headers only */
435         if (pagin == 2) {
436                 pprintf("nhdr=%s\nfrom=%s\ntype=%d\nmsgn=%s\n",
437                                 message->nhdr ? "yes" : "no",
438                                 message->author, message->type,
439                                 message->msgid);
440                 if (!IsEmptyStr(message->subject)) {
441                         pprintf("subj=%s\n", message->subject);
442                 }
443                 if (!IsEmptyStr(message->email)) {
444                         pprintf("rfca=%s\n", message->email);
445                 }
446                 pprintf("hnod=%s\nroom=%s\nnode=%s\ntime=%s",
447                                 message->hnod, message->room,
448                                 message->node, 
449                                 asctime(localtime(&message->time)));
450                 if (!IsEmptyStr(message->recipient)) {
451                         pprintf("rcpt=%s\n", message->recipient);
452                 }
453                 if (message->attachments) {
454                         struct parts *ptr;
455
456                         for (ptr = message->attachments; ptr; ptr = ptr->next) {
457                                 pprintf("part=%s|%s|%s|%s|%s|%ld\n",
458                                         ptr->name, ptr->filename, ptr->number,
459                                         ptr->disposition, ptr->mimetype,
460                                         ptr->length);
461                         }
462                 }
463                 pprintf("\n");
464                 stty_ctdl(0);
465                 free(message->text);
466                 free_parts(message->attachments);
467                 free(message);
468                 return (0);
469         }
470
471         if (rc_display_message_numbers) {
472                 if (dest) {
473                         fprintf(dest, "[#%s] ", message->msgid);
474                 } else {
475                         color(DIM_WHITE);
476                         scr_printf("[");
477                         color(BRIGHT_WHITE);
478                         scr_printf("#%s", message->msgid);
479                         color(DIM_WHITE);
480                         scr_printf("] ");
481                 }
482         }
483         if (nhdr == 1 && !is_room_aide) {
484                 if (dest) {
485                         fprintf(dest, " ****");
486                 } else {
487                         scr_printf(" ****");
488                 }
489         } else {
490                 fmt_date(now, sizeof now, message->time, 0);
491                 if (dest) {
492                         fprintf(dest, "%s from %s ", now, message->author);
493                         if (!IsEmptyStr(message->email)) {
494                                 fprintf(dest, "<%s> ", message->email);
495                         }
496                 } else {
497                         color(BRIGHT_CYAN);
498                         scr_printf("%s ", now);
499                         color(DIM_WHITE);
500                         scr_printf("from ");
501                         color(BRIGHT_CYAN);
502                         scr_printf("%s ", message->author);
503                         if (!IsEmptyStr(message->email)) {
504                                 color(DIM_WHITE);
505                                 scr_printf("<");
506                                 color(BRIGHT_BLUE);
507                                 scr_printf("%s", message->email);
508                                         color(DIM_WHITE);
509                                 scr_printf("> ");
510                         }
511                 }
512                 if (!IsEmptyStr(message->node)) {
513                         if ((room_flags & QR_NETWORK)
514                             || ((strcasecmp(message->node, ipc->ServInfo.nodename)
515                              && (strcasecmp(message->node, ipc->ServInfo.fqdn))))) {
516                                 if (IsEmptyStr(message->email)) {
517                                         if (dest) {
518                                                 fprintf(dest, "@%s ", message->node);
519                                         } else {
520                                                 color(DIM_WHITE);
521                                                 scr_printf("@");
522                                                 color(BRIGHT_YELLOW);
523                                                 scr_printf("%s ", message->node);
524                                         }
525                                 }
526                         }
527                 }
528                 if (strcasecmp(message->hnod, ipc->ServInfo.humannode)
529                     && (!IsEmptyStr(message->hnod)) && (IsEmptyStr(message->email))) {
530                         if (dest) {
531                                 fprintf(dest, "(%s) ", message->hnod);
532                         } else {
533                                 color(DIM_WHITE);
534                                 scr_printf("(");
535                                 color(BRIGHT_WHITE);
536                                 scr_printf("%s", message->hnod);
537                                 color(DIM_WHITE);
538                                 scr_printf(") ");
539                         }
540                 }
541                 if (strcasecmp(message->room, room_name) && (IsEmptyStr(message->email))) {
542                         if (dest) {
543                                 fprintf(dest, "in %s> ", message->room);
544                         } else {
545                                 color(DIM_WHITE);
546                                 scr_printf("in ");
547                                 color(BRIGHT_MAGENTA);
548                                 scr_printf("%s> ", message->room);
549                         }
550                 }
551                 if (!IsEmptyStr(message->recipient)) {
552                         if (dest) {
553                                 fprintf(dest, "to %s ", message->recipient);
554                         } else {
555                                 color(DIM_WHITE);
556                                 scr_printf("to ");
557                                 color(BRIGHT_CYAN);
558                                 scr_printf("%s ", message->recipient);
559                         }
560                 }
561         }
562         
563         if (dest) {
564                 fprintf(dest, "\n");
565         } else {
566                 scr_printf("\n");
567         }
568
569         /* Set the reply-to address to an Internet e-mail address if possible
570          */
571         if (message->email != NULL) if (!IsEmptyStr(message->email)) {
572                 if (!IsEmptyStr(message->author)) {
573                         snprintf(reply_to, sizeof reply_to, "%s <%s>", message->author, message->email);
574                 }
575                 else {
576                         safestrncpy(reply_to, message->email, sizeof reply_to);
577                 }
578         }
579
580         /* But if we can't do that, set it to a Citadel address.
581          */
582         if (!strcmp(reply_to, NO_REPLY_TO)) {
583                 snprintf(reply_to, sizeof(reply_to), "%s @ %s",
584                          message->author, message->node);
585         }
586
587         if (!dest) {
588                 ++lines_printed;
589                 lines_printed = checkpagin(lines_printed, pagin, screenheight);
590         }
591
592         if (message->subject != NULL) {
593                 safestrncpy(reply_subject, message->subject,
594                                                 sizeof reply_subject);
595                 if (!IsEmptyStr(message->subject)) {
596                         if (dest) {
597                                 fprintf(dest, "Subject: %s\n",
598                                                         message->subject);
599                         } else {
600                                 color(DIM_WHITE);
601                                 scr_printf("Subject: ");
602                                 color(BRIGHT_CYAN);
603                                 scr_printf("%s\n", message->subject);
604                                 ++lines_printed;
605                                 lines_printed = checkpagin(lines_printed,
606                                                 pagin, screenheight);
607                         }
608                 }
609         }
610
611         if (pagin == 1 && !dest) {
612                 color(BRIGHT_WHITE);
613         }
614
615         /******* end of header output, start of message text output *******/
616
617         /*
618          * Convert HTML to plain text, formatting for the actual width
619          * of the client screen.
620          */
621         if (!strcasecmp(message->content_type, "text/html")) {
622                 converted_text = html_to_ascii(message->text, 0, screenwidth, 0);
623                 if (converted_text != NULL) {
624                         free(message->text);
625                         message->text = converted_text;
626                         format_type = 1;
627                 }
628         }
629
630         /* Text/plain is a different type */
631         if (!strcasecmp(message->content_type, "text/plain")) {
632                 format_type = 1;
633         }
634
635         /* Extract URL's */
636         num_urls = 0;   /* Start with a clean slate */
637         searchptr = message->text;
638         while ( (searchptr != NULL) && (num_urls < MAXURLS) ) {
639                 searchptr = strstr(searchptr, "http://");
640                 if (searchptr != NULL) {
641                         safestrncpy(urls[num_urls], searchptr, sizeof(urls[num_urls]));
642                         for (i = 0; i < strlen(urls[num_urls]); i++) {
643                                 ch = urls[num_urls][i];
644                                 if (ch == '>' || ch == '\"' || ch == ')' ||
645                                     ch == ' ' || ch == '\n') {
646                                         urls[num_urls][i] = 0;
647                                         break;
648                                 }
649                         }
650                         num_urls++;
651                         ++searchptr;
652                 }
653         }
654
655         /*
656          * Here we go
657          */
658         if (format_type == 0) {
659                 fr = fmout(screenwidth, NULL, message->text, dest,
660                            ((pagin == 1) ? 1 : 0), screenheight, (-1), 1);
661         } else {
662                 /* renderer for text/plain */
663
664                 lineptr = message->text;
665
666                 do {
667                         nextline = strchr(lineptr, '\n');
668                         if (nextline != NULL) {
669                                 *nextline = 0;
670                                 ++nextline;
671                                 if (*nextline == 0) nextline = NULL;
672                         }
673
674                         if (sigcaught == 0) {
675                                 linelen = strlen(lineptr);
676                                 if (linelen && (lineptr[linelen-1] == '\r')) {
677                                         lineptr[--linelen] = 0;
678                                 }
679                                 if (dest) {
680                                         fprintf(dest, "%s\n", lineptr);
681                                 } else {
682                                         scr_printf("%s\n", lineptr);
683                                         lines_printed = lines_printed + 1 +
684                                             (linelen / screenwidth);
685                                         lines_printed =
686                                             checkpagin(lines_printed, pagin,
687                                                        screenheight);
688                                 }
689                         }
690                         if (lineptr[0] == 0) final_line_is_blank = 1;
691                         else final_line_is_blank = 0;
692                         lineptr = nextline;
693                 } while (nextline);
694                 fr = sigcaught;
695         }
696         if (!final_line_is_blank) {
697                 if (dest) {
698                         fprintf(dest, "\n");
699                 }
700                 else {
701                         scr_printf("\n");
702                         ++lines_printed;
703                         lines_printed = checkpagin(lines_printed, pagin, screenheight);
704                         fr = sigcaught;         
705                 }
706         }
707
708         /* Enumerate any attachments */
709         if ( (pagin == 1) && (message->attachments) ) {
710                 struct parts *ptr;
711
712                 for (ptr = message->attachments; ptr; ptr = ptr->next) {
713                         if ( (!strcasecmp(ptr->disposition, "attachment"))
714                            || (!strcasecmp(ptr->disposition, "inline"))
715                            || (!strcasecmp(ptr->disposition, ""))
716                         ) {
717                                 if ( (strcasecmp(ptr->number, message->mime_chosen))
718                                    && (!IsEmptyStr(ptr->mimetype))
719                                 ) {
720                                         color(DIM_WHITE);
721                                         pprintf("Part ");
722                                         color(BRIGHT_MAGENTA);
723                                         pprintf("%s", ptr->number);
724                                         color(DIM_WHITE);
725                                         pprintf(": ");
726                                         color(BRIGHT_CYAN);
727                                         pprintf("%s", ptr->filename);
728                                         color(DIM_WHITE);
729                                         pprintf(" (%s, %ld bytes)\n", ptr->mimetype, ptr->length);
730                                         if (!strncmp(ptr->mimetype, "image/", 6)) {
731                                                 has_images++;
732                                         }
733                                 }
734                         }
735                 }
736         }
737
738         /* Save the attachments info for later */
739         last_message_parts = message->attachments;
740
741         /* Now we're done */
742         free(message->text);
743         free(message);
744
745         if (pagin == 1 && !dest)
746                 color(DIM_WHITE);
747         stty_ctdl(0);
748         return (fr);
749 }
750
751 /*
752  * replace string function for the built-in editor
753  */
754 void replace_string(char *filename, long int startpos)
755 {
756         char buf[512];
757         char srch_str[128];
758         char rplc_str[128];
759         FILE *fp;
760         int a;
761         long rpos, wpos;
762         char *ptr;
763         int substitutions = 0;
764         long msglen = 0L;
765
766         scr_printf("Enter text to be replaced:\n: ");
767         ctdl_getline(srch_str, (sizeof(srch_str)-1) );
768         if (IsEmptyStr(srch_str))
769                 return;
770
771         scr_printf("Enter text to replace it with:\n: ");
772         ctdl_getline(rplc_str, (sizeof(rplc_str)-1) );
773
774         fp = fopen(filename, "r+");
775         if (fp == NULL)
776                 return;
777
778         wpos = startpos;
779         fseek(fp, startpos, 0);
780         strcpy(buf, "");
781         while (a = getc(fp), a > 0) {
782                 ++msglen;
783                 buf[strlen(buf) + 1] = 0;
784                 buf[strlen(buf)] = a;
785                 if (strlen(buf) >= strlen(srch_str)) {
786                         ptr = (&buf[strlen(buf) - strlen(srch_str)]);
787                         if (!strncmp(ptr, srch_str, strlen(srch_str))) {
788                                 strcpy(ptr, rplc_str);
789                                 ++substitutions;
790                         }
791                 }
792                 if (strlen(buf) > 384) {
793                         rpos = ftell(fp);
794                         fseek(fp, wpos, 0);
795                         fwrite((char *) buf, 128, 1, fp);
796                         strcpy(buf, &buf[128]);
797                         wpos = ftell(fp);
798                         fseek(fp, rpos, 0);
799                 }
800         }
801         fseek(fp, wpos, 0);
802         if (!IsEmptyStr(buf))
803                 fwrite((char *) buf, strlen(buf), 1, fp);
804         wpos = ftell(fp);
805         fclose(fp);
806         truncate(filename, wpos);
807         scr_printf("<R>eplace made %d substitution(s).\n\n", substitutions);
808 }
809
810 /*
811  * Function to begin composing a new message
812  */
813 int client_make_message(CtdlIPC *ipc,
814                                                 char *filename,         /* temporary file name */
815                                                 char *recipient,        /* NULL if it's not mail */
816                                                 int is_anonymous,
817                                                 int format_type,
818                                                 int mode,
819                                                 char *subject,          /* buffer to store subject line */
820                                                 int subject_required)
821 {
822         FILE *fp;
823         int a, b, e_ex_code;
824         long beg;
825         char datestr[SIZ];
826         char header[SIZ];
827         char *editor_path = NULL;
828         int cksum = 0;
829
830         if (mode >= 2)
831         {
832                 if((mode-2) < MAX_EDITORS && !IsEmptyStr(editor_paths[mode-2])) {
833                         editor_path = editor_paths[mode-2];
834                 } else if (!IsEmptyStr(editor_paths[0])) {
835                         editor_path = editor_paths[0];
836                 } else {
837                         err_printf("*** No editor available, "
838                                 "using built-in editor\n");
839                         mode = 0;
840                 }
841         }
842
843         fmt_date(datestr, sizeof datestr, time(NULL), 0);
844         header[0] = 0;
845
846         if (room_flags & QR_ANONONLY && !recipient) {
847                 snprintf(header, sizeof header, " ****");
848         }
849         else {
850                 snprintf(header, sizeof header,
851                         " %s from %s",
852                         datestr,
853                         (is_anonymous ? "[anonymous]" : fullname)
854                         );
855                 if (!IsEmptyStr(recipient)) {
856                         size_t tmp = strlen(header);
857                         snprintf(&header[tmp], sizeof header - tmp,
858                                 " to %s", recipient);
859                 }
860         }
861         scr_printf("%s\n", header);
862         if (subject != NULL) if (!IsEmptyStr(subject)) {
863                 scr_printf("Subject: %s\n", subject);
864         }
865         
866         if ( (subject_required) && (IsEmptyStr(subject)) ) {
867                 newprompt("Subject: ", subject, 70);
868         }
869
870         beg = 0L;
871
872         if (mode == 1) {
873                 scr_printf("(Press ctrl-d when finished)\n");
874         }
875
876         if (mode == 0) {
877                 fp = fopen(filename, "r");
878                 if (fp != NULL) {
879                         fmout(screenwidth, fp, NULL, NULL, 0,
880                                 screenheight, 0, 0);
881                         beg = ftell(fp);
882                         fclose(fp);
883                 } else {
884                         fp = fopen(filename, "w");
885                         if (fp == NULL) {
886                                 err_printf("*** Error opening temp file!\n"
887                                         "    %s: %s\n",
888                                         filename, strerror(errno));
889                         return(1);
890                         }
891                         fclose(fp);
892                 }
893         }
894
895 ME1:    switch (mode) {
896
897         case 0:
898                 fp = fopen(filename, "r+");
899                 if (fp == NULL) {
900                         err_printf("*** Error opening temp file!\n"
901                                 "    %s: %s\n",
902                                 filename, strerror(errno));
903                         return(1);
904                 }
905                 citedit(ipc, fp);
906                 fclose(fp);
907                 goto MECR;
908
909         case 1:
910                 fp = fopen(filename, "a");
911                 if (fp == NULL) {
912                         err_printf("*** Error opening temp file!\n"
913                                 "    %s: %s\n",
914                                 filename, strerror(errno));
915                         return(1);
916                 }
917                 do {
918                         a = inkey();
919                         if (a == 255)
920                                 a = 32;
921                         if (a == 13)
922                                 a = 10;
923                         if (a != 4) {
924                                 putc(a, fp);
925                                 scr_putc(a);
926                         }
927                         if (a == 10)
928                                 scr_putc(10);
929                 } while (a != 4);
930                 fclose(fp);
931                 break;
932
933         case 2:
934         default:        /* allow 2+ modes */
935                 e_ex_code = 1;  /* start with a failed exit code */
936                 screen_reset();
937                 stty_ctdl(SB_RESTORE);
938                 editor_pid = fork();
939                 cksum = file_checksum(filename);
940                 if (editor_pid == 0) {
941                         char tmp[SIZ];
942
943                         chmod(filename, 0600);
944                         snprintf(tmp, sizeof tmp, "WINDOW_TITLE=%s", header);
945                         putenv(tmp);
946                         execlp(editor_path, editor_path, filename, NULL);
947                         exit(1);
948                 }
949                 if (editor_pid > 0)
950                         do {
951                                 e_ex_code = 0;
952                                 b = ka_wait(&e_ex_code);
953                         } while ((b != editor_pid) && (b >= 0));
954                 editor_pid = (-1);
955                 stty_ctdl(0);
956                 screen_set();
957                 break;
958         }
959
960 MECR:   if (mode >= 2) {
961                 if (file_checksum(filename) == cksum) {
962                         err_printf("*** Aborted message.\n");
963                         e_ex_code = 1;
964                 }
965                 if (e_ex_code == 0) {
966                         goto MEFIN;
967                 }
968                 goto MEABT2;
969         }
970
971         b = keymenu("Entry command (? for options)",
972                     "<A>bort|<C>ontinue|<S>ave message|<P>rint formatted|"
973                     "add s<U>bject|"
974                     "<R>eplace string|<H>old message");
975
976         if (b == 'a') goto MEABT;
977         if (b == 'c') goto ME1;
978         if (b == 's') goto MEFIN;
979         if (b == 'p') {
980                 scr_printf(" %s from %s", datestr, fullname);
981                 if (!IsEmptyStr(recipient)) {
982                         scr_printf(" to %s", recipient);
983                 }
984                 scr_printf("\n");
985                 if (subject != NULL) if (!IsEmptyStr(subject)) {
986                         scr_printf("Subject: %s\n", subject);
987                 }
988                 fp = fopen(filename, "r");
989                 if (fp != NULL) {
990                         fmout(screenwidth, fp, NULL, NULL,
991                               ((userflags & US_PAGINATOR) ? 1 : 0),
992                               screenheight, 0, 0);
993                         beg = ftell(fp);
994                         fclose(fp);
995                 }
996                 goto MECR;
997         }
998         if (b == 'r') {
999                 replace_string(filename, 0L);
1000                 goto MECR;
1001         }
1002         if (b == 'h') {
1003                 return (2);
1004         }
1005         if (b == 'u') {
1006                 if (subject != NULL) {
1007                         newprompt("Subject: ", subject, 70);
1008                 }
1009                 goto MECR;
1010         }
1011
1012 MEFIN:  return (0);
1013
1014 MEABT:  scr_printf("Are you sure? ");
1015         if (yesno() == 0) {
1016                 goto ME1;
1017         }
1018 MEABT2: unlink(filename);
1019         return (2);
1020 }
1021
1022
1023 /*
1024  * Make sure there's room in msg_arr[] for at least one more.
1025  */
1026 void check_msg_arr_size(void) {
1027         if ((num_msgs + 1) > msg_arr_size) {
1028                 msg_arr_size += 512;
1029                 msg_arr = realloc(msg_arr,
1030                         ((sizeof(long)) * msg_arr_size) );
1031         }
1032 }
1033
1034
1035 /*
1036  * break_big_lines()  -  break up lines that are >1024 characters
1037  *                       otherwise the server will truncate
1038  */
1039 void break_big_lines(char *msg) {
1040         char *ptr;
1041         char *break_here;
1042
1043         if (msg == NULL) {
1044                 return;
1045         }
1046
1047         ptr = msg;
1048         while (strlen(ptr) > 1000) {
1049                 break_here = strchr(&ptr[900], ' ');
1050                 if ((break_here == NULL) || (break_here > &ptr[999])) {
1051                         break_here = &ptr[999];
1052                 }
1053                 *break_here = '\n';
1054                 ptr = break_here++;
1055         }
1056 }
1057
1058
1059 /*
1060  * entmsg()  -  edit and create a message
1061  *              returns 0 if message was saved
1062  */
1063 int entmsg(CtdlIPC *ipc,
1064                 int is_reply,   /* nonzero if this was a <R>eply command */
1065                 int c,          /* mode */
1066                 int masquerade  /* prompt for a non-default display name? */
1067 ) {
1068         char buf[SIZ];
1069         int a, b;
1070         int need_recp = 0;
1071         int mode;
1072         long highmsg = 0L;
1073         FILE *fp;
1074         char subject[SIZ];
1075         struct ctdlipcmessage message;
1076         unsigned long *msgarr = NULL;
1077         int r;                  /* IPC response code */
1078         int subject_required = 0;
1079
1080         if (c > 0)
1081                 mode = 1;
1082         else
1083                 mode = 0;
1084
1085         strcpy(subject, "");
1086
1087         /*
1088          * First, check to see if we have permission to enter a message in
1089          * this room.  The server will return an error code if we can't.
1090          */
1091         strcpy(message.recipient, "");
1092         strcpy(message.author, "");
1093         strcpy(message.subject, "");
1094         message.text = "";              /* point to "", changes later */
1095         message.anonymous = 0;
1096         message.type = mode;
1097
1098         if (masquerade) {
1099                 newprompt("Display name for this message: ", message.author, 40);
1100         }
1101
1102         r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf);
1103
1104         if (r / 100 != 2 && r / 10 != 57) {
1105                 scr_printf("%s\n", buf);
1106                 return (1);
1107         }
1108
1109         /* Error code 570 is special.  It means that we CAN enter a message
1110          * in this room, but a recipient needs to be specified.
1111          */
1112         need_recp = 0;
1113         if (r / 10 == 57) {
1114                 need_recp = 1;
1115         }
1116
1117         /* If the user is a dumbass, tell them how to type. */
1118         if ((userflags & US_EXPERT) == 0) {
1119                 formout(ipc, "entermsg");
1120         }
1121
1122         /* Handle the selection of a recipient, if necessary. */
1123         strcpy(buf, "");
1124         if (need_recp == 1) {
1125                 if (axlevel >= 2) {
1126                         if (is_reply) {
1127                                 strcpy(buf, reply_to);
1128                         } else {
1129                                 scr_printf("Enter recipient: ");
1130                                 ctdl_getline(buf, (SIZ-100) );
1131                                 if (IsEmptyStr(buf))
1132                                         return (1);
1133                         }
1134                 } else
1135                         strcpy(buf, "sysop");
1136         }
1137         strcpy(message.recipient, buf);
1138
1139         if (is_reply) {
1140                 if (!IsEmptyStr(reply_subject)) {
1141                         if (!strncasecmp(reply_subject,
1142                            "Re: ", 3)) {
1143                                 strcpy(message.subject, reply_subject);
1144                         }
1145                         else {
1146                                 snprintf(message.subject,
1147                                         sizeof message.subject,
1148                                         "Re: %s",
1149                                         reply_subject);
1150                         }
1151                 }
1152         }
1153
1154         if (room_flags & QR_ANONOPT) {
1155                 scr_printf("Anonymous (Y/N)? ");
1156                 if (yesno() == 1)
1157                         message.anonymous = 1;
1158         }
1159
1160         /* If it's mail, we've got to check the validity of the recipient... */
1161         if (!IsEmptyStr(message.recipient)) {
1162                 r = CtdlIPCPostMessage(ipc, 0, &subject_required,  &message, buf);
1163                 if (r / 100 != 2) {
1164                         scr_printf("%s\n", buf);
1165                         return (1);
1166                 }
1167         }
1168
1169         /* Learn the number of the newest message in in the room, so we can
1170          * tell upon saving whether someone else has posted too.
1171          */
1172         num_msgs = 0;
1173         r = CtdlIPCGetMessages(ipc, LastMessages, 1, NULL, &msgarr, buf);
1174         if (r / 100 != 1) {
1175                 scr_printf("%s\n", buf);
1176         } else {
1177                 for (num_msgs = 0; msgarr[num_msgs]; num_msgs++)
1178                         ;
1179         }
1180
1181         /* Now compose the message... */
1182         if (client_make_message(ipc, temp, message.recipient,
1183            message.anonymous, 0, c, message.subject, subject_required) != 0) {
1184             if (msgarr) free(msgarr);   
1185                 return (2);
1186         }
1187
1188         /* Reopen the temp file that was created, so we can send it */
1189         fp = fopen(temp, "r");
1190
1191         if (!fp || !(message.text = load_message_from_file(fp))) {
1192                 err_printf("*** Internal error while trying to save message!\n"
1193                         "%s: %s\n",
1194                         temp, strerror(errno));
1195                 unlink(temp);
1196                 return(errno);
1197         }
1198
1199         if (fp) fclose(fp);
1200
1201         /* Break lines that are >1024 characters, otherwise the server
1202          * will truncate them.
1203          */
1204         break_big_lines(message.text);
1205
1206         /* Transmit message to the server */
1207         r = CtdlIPCPostMessage(ipc, 1, NULL, &message, buf);
1208         if (r / 100 != 4) {
1209                 scr_printf("%s\n", buf);
1210                 return (1);
1211         }
1212
1213         /* Yes, unlink it now, so it doesn't stick around if we crash */
1214         unlink(temp);
1215
1216         if (num_msgs >= 1) highmsg = msgarr[num_msgs - 1];
1217
1218         if (msgarr) free(msgarr);
1219         msgarr = NULL;
1220         r = CtdlIPCGetMessages(ipc, NewMessages, 0, NULL, &msgarr, buf);
1221         if (r / 100 != 1) {
1222                 scr_printf("%s\n", buf);
1223         } else {
1224                 for (num_msgs = 0; msgarr[num_msgs]; num_msgs++)
1225                         ;
1226         }
1227
1228         /* get new highest message number in room to set lrp for goto... */
1229         maxmsgnum = msgarr[num_msgs - 1];
1230
1231         /* now see if anyone else has posted in here */
1232         b = (-1);
1233         for (a = 0; a < num_msgs; ++a) {
1234                 if (msgarr[a] > highmsg) {
1235                         ++b;
1236                 }
1237         }
1238         if (msgarr) free(msgarr);
1239         msgarr = NULL;
1240
1241         /* In the Mail> room, this algorithm always counts one message
1242          * higher than in public rooms, so we decrement it by one.
1243          */
1244         if (need_recp) {
1245                 --b;
1246         }
1247
1248         if (b == 1) {
1249                 scr_printf("*** 1 additional message has been entered "
1250                         "in this room by another user.\n");
1251         }
1252         else if (b > 1) {
1253                 scr_printf("*** %d additional messages have been entered "
1254                         "in this room by other users.\n", b);
1255         }
1256     free(message.text);
1257
1258         return(0);
1259 }
1260
1261 /*
1262  * Do editing on a quoted file
1263  */
1264 void process_quote(void)
1265 {
1266         FILE *qfile, *tfile;
1267         char buf[128];
1268         int line, qstart, qend;
1269
1270         /* Unlink the second temp file as soon as it's opened, so it'll get
1271          * deleted even if the program dies
1272          */
1273         qfile = fopen(temp2, "r");
1274         unlink(temp2);
1275
1276         /* Display the quotable text with line numbers added */
1277         line = 0;
1278         fgets(buf, 128, qfile);
1279         while (fgets(buf, 128, qfile) != NULL) {
1280                 scr_printf("%3d %s", ++line, buf);
1281         }
1282         scr_printf("Begin quoting at [1] : ");
1283         ctdl_getline(buf, 4);
1284         qstart = (buf[0] == 0) ? (1) : atoi(buf);
1285         scr_printf("  End quoting at [%d] : ", line);
1286         ctdl_getline(buf, 4);
1287         qend = (buf[0] == 0) ? (line) : atoi(buf);
1288         rewind(qfile);
1289         line = 0;
1290         fgets(buf, 128, qfile);
1291         tfile = fopen(temp, "w");
1292         while (fgets(buf, 128, qfile) != NULL) {
1293                 if ((++line >= qstart) && (line <= qend))
1294                         fprintf(tfile, " >%s", buf);
1295         }
1296         fprintf(tfile, " \n");
1297         fclose(qfile);
1298         fclose(tfile);
1299         chmod(temp, 0666);
1300 }
1301
1302
1303
1304 /*
1305  * List the URL's which were embedded in the previous message
1306  */
1307 void list_urls(CtdlIPC *ipc)
1308 {
1309         int i;
1310         char cmd[SIZ];
1311
1312         if (num_urls == 0) {
1313                 scr_printf("There were no URL's in the previous message.\n\n");
1314                 return;
1315         }
1316
1317         for (i = 0; i < num_urls; ++i) {
1318                 scr_printf("%3d %s\n", i + 1, urls[i]);
1319         }
1320
1321         if ((i = num_urls) != 1)
1322                 i = intprompt("Display which one", 1, 1, num_urls);
1323
1324         snprintf(cmd, sizeof cmd, rc_url_cmd, urls[i - 1]);
1325         system(cmd);
1326         scr_printf("\n");
1327 }
1328
1329
1330 /*
1331  * Run image viewer in background
1332  */
1333 int do_image_view(const char *filename)
1334 {
1335         char cmd[SIZ];
1336         pid_t childpid;
1337
1338         snprintf(cmd, sizeof cmd, imagecmd, filename);
1339         childpid = fork();
1340         if (childpid < 0) {
1341                 unlink(filename);
1342                 return childpid;
1343         }
1344
1345         if (childpid == 0) {
1346                 int retcode;
1347                 pid_t grandchildpid;
1348
1349                 grandchildpid = fork();
1350                 if (grandchildpid < 0) {
1351                         return grandchildpid;
1352                 }
1353
1354                 if (grandchildpid == 0) {
1355                         int nullfd;
1356                         int outfd = -1;
1357                         int errfd = -1;
1358
1359                         nullfd = open("/dev/null", O_WRONLY);
1360                         if (nullfd > -1) {
1361                                 dup2(1, outfd);
1362                                 dup2(2, errfd);
1363                                 dup2(nullfd, 1);
1364                                 dup2(nullfd, 2);
1365                         }
1366                         retcode = system(cmd);
1367                         if (nullfd > -1) {
1368                                 dup2(outfd, 1);
1369                                 dup2(errfd, 2);
1370                                 close(nullfd);
1371                         }
1372                         unlink(filename);
1373                         exit(retcode);
1374                 }
1375
1376                 if (grandchildpid > 0) {
1377                         exit(0);
1378                 }
1379         }
1380
1381         if (childpid > 0) {
1382                 int retcode;
1383
1384                 waitpid(childpid, &retcode, 0);
1385                 return retcode;
1386         }
1387         
1388         return -1;
1389 }
1390
1391
1392 /*
1393  * View an image attached to a message
1394  */
1395 void image_view(CtdlIPC *ipc, unsigned long msg)
1396 {
1397         struct parts *ptr = last_message_parts;
1398         char part[SIZ];
1399         int found = 0;
1400
1401         /* Run through available parts */
1402         for (ptr = last_message_parts; ptr; ptr = ptr->next) {
1403                 if ((!strcasecmp(ptr->disposition, "attachment")
1404                    || !strcasecmp(ptr->disposition, "inline"))
1405                    && !strncmp(ptr->mimetype, "image/", 6)) {
1406                         found++;
1407                         if (found == 1) {
1408                                 strcpy(part, ptr->number);
1409                         }
1410                 }
1411         }
1412
1413         while (found > 0) {
1414                 if (found > 1)
1415                         strprompt("View which part (0 when done)", part, SIZ-1);
1416                 found = -found;
1417                 for (ptr = last_message_parts; ptr; ptr = ptr->next) {
1418                         if ((!strcasecmp(ptr->disposition, "attachment")
1419                            || !strcasecmp(ptr->disposition, "inline"))
1420                            && !strncmp(ptr->mimetype, "image/", 6)
1421                            && !strcasecmp(ptr->number, part)) {
1422                                 char tmp[PATH_MAX];
1423                                 char buf[SIZ];
1424                                 void *file = NULL; /* The downloaded file */
1425                                 int r;
1426         
1427                                 /* view image */
1428                                 found = -found;
1429                                 r = CtdlIPCAttachmentDownload(ipc, msg, ptr->number, &file, progress, buf);
1430                                 if (r / 100 != 2) {
1431                                         scr_printf("%s\n", buf);
1432                                 } else {
1433                                         size_t len;
1434         
1435                                         len = (size_t)extract_long(buf, 0);
1436                                         progress(ipc, len, len);
1437                                         scr_flush();
1438                                         CtdlMakeTempFileName(tmp, sizeof tmp);
1439                                         strcat(tmp, ptr->filename);
1440                                         save_buffer(file, len, tmp);
1441                                         free(file);
1442                                         do_image_view(tmp);
1443                                 }
1444                                 break;
1445                         }
1446                 }
1447                 if (found == 1)
1448                         break;
1449         }
1450 }
1451  
1452
1453 /*
1454  * Read the messages in the current room
1455  */
1456 void readmsgs(CtdlIPC *ipc,
1457         enum MessageList c,             /* see listing in citadel_ipc.h */
1458         enum MessageDirection rdir,     /* 1=Forward (-1)=Reverse */
1459         int q           /* Number of msgs to read (if c==3) */
1460 ) {
1461         int a, b, e, f, g, start;
1462         int savedpos;
1463         int hold_sw = 0;
1464         char arcflag = 0;
1465         char quotflag = 0;
1466         int hold_color = 0;
1467         char prtfile[PATH_MAX];
1468         char pagin;
1469         char cmd[SIZ];
1470         char targ[ROOMNAMELEN];
1471         char filename[PATH_MAX];
1472         char save_to[PATH_MAX];
1473         void *attachment = NULL;        /* Downloaded attachment */
1474         FILE *dest = NULL;              /* Alternate destination other than screen */
1475         int r;                          /* IPC response code */
1476         static int att_seq = 0;         /* Attachment download sequence number */
1477
1478         if (c < 0)
1479                 b = (num_msgs - 1);
1480         else
1481                 b = 0;
1482
1483         CtdlMakeTempFileName(prtfile, sizeof prtfile);
1484
1485         if (msg_arr) {
1486                 free(msg_arr);
1487                 msg_arr = NULL;
1488         }
1489         r = CtdlIPCGetMessages(ipc, c, q, NULL, &msg_arr, cmd);
1490         if (r / 100 != 1) {
1491                 scr_printf("%s\n", cmd);
1492         } else {
1493                 for (num_msgs = 0; msg_arr[num_msgs]; num_msgs++)
1494                         ;
1495         }
1496
1497         if (num_msgs == 0) {    /* TODO look at this later */
1498                 if (c == LastMessages) return;
1499                 scr_printf("*** There are no ");
1500                 if (c == NewMessages) scr_printf("new ");
1501                 if (c == OldMessages) scr_printf("old ");
1502                 scr_printf("messages in this room.\n");
1503                 return;
1504         }
1505
1506         lines_printed = 0;
1507
1508         /* this loop cycles through each message... */
1509         start = ((rdir == 1) ? 0 : (num_msgs - 1));
1510         for (a = start; ((a < num_msgs) && (a >= 0)); a = a + rdir) {
1511                 while (msg_arr[a] == 0L) {
1512                         a = a + rdir;
1513                         if ((a == num_msgs) || (a == (-1)))
1514                                 return;
1515                 }
1516
1517 RAGAIN:         pagin = ((arcflag == 0)
1518                          && (quotflag == 0)
1519                          && (userflags & US_PAGINATOR)) ? 1 : 0;
1520
1521                 /* If we're doing a quote, set the screenwidth to 72 */
1522                 if (quotflag) {
1523                         hold_sw = screenwidth;
1524                         screenwidth = 72;
1525                 }
1526
1527                 /* If printing or archiving, set the screenwidth to 80 */
1528                 if (arcflag) {
1529                         hold_sw = screenwidth;
1530                         screenwidth = 80;
1531                 }
1532
1533                 /* clear parts list */
1534                 free_parts(last_message_parts);
1535                 last_message_parts = NULL;
1536
1537                 /* now read the message... */
1538                 e = read_message(ipc, msg_arr[a], pagin, dest);
1539
1540                 /* ...and set the screenwidth back if we have to */
1541                 if ((quotflag) || (arcflag)) {
1542                         screenwidth = hold_sw;
1543                 }
1544 RMSGREAD:       scr_flush();
1545                 highest_msg_read = msg_arr[a];
1546                 if (quotflag) {
1547                         fclose(dest);
1548                         dest = NULL;
1549                         quotflag = 0;
1550                         enable_color = hold_color;
1551                         process_quote();
1552                 }
1553                 if (arcflag) {
1554                         fclose(dest);
1555                         dest = NULL;
1556                         arcflag = 0;
1557                         enable_color = hold_color;
1558                         f = fork();
1559                         if (f == 0) {
1560                                 freopen(prtfile, "r", stdin);
1561                                 screen_reset();
1562                                 stty_ctdl(SB_RESTORE);
1563                                 ka_system(printcmd);
1564                                 stty_ctdl(SB_NO_INTR);
1565                                 screen_set();
1566                                 unlink(prtfile);
1567                                 exit(0);
1568                         }
1569                         if (f > 0)
1570                                 do {
1571                                         g = wait(NULL);
1572                                 } while ((g != f) && (g >= 0));
1573                         scr_printf("Message printed.\n");
1574                 }
1575                 if (rc_alt_semantics && c == 1) {
1576                         char buf[SIZ];
1577
1578                         r = CtdlIPCSetMessageSeen(ipc, msg_arr[a], 1, buf);
1579                 }
1580                 if (e == SIGQUIT)
1581                         return;
1582                 if (((userflags & US_NOPROMPT) || (e == SIGINT))
1583                         && (((room_flags & QR_MAILBOX) == 0)
1584                         || (rc_force_mail_prompts == 0))) {
1585                         e = 'n';
1586                 } else {
1587                         color(DIM_WHITE);
1588                         scr_printf("(");
1589                         color(BRIGHT_WHITE);
1590                         scr_printf("%d", num_msgs - a - 1);
1591                         color(DIM_WHITE);
1592                         scr_printf(") ");
1593
1594                         keyopt("<B>ack <A>gain <Q>uote <R>eply <N>ext <S>top ");
1595                         if (rc_url_cmd[0] && num_urls)
1596                                 keyopt("<U>RLview ");
1597                         if (has_images > 0 && !IsEmptyStr(imagecmd))
1598                                 keyopt("<I>mages ");
1599                         keyopt("<?>help -> ");
1600
1601                         do {
1602                                 lines_printed = 2;
1603                                 e = (inkey() & 127);
1604                                 e = tolower(e);
1605 /* return key same as <N> */ if (e == 10)
1606                                         e = 'n';
1607 /* space key same as <N> */ if (e == 32)
1608                                         e = 'n';
1609 /* del/move for aides only */
1610                                     if (  (!is_room_aide)
1611                                        && ((room_flags & QR_MAILBOX) == 0)
1612                                        && ((room_flags2 & QR2_COLLABDEL) == 0)
1613                                        ) {
1614                                         if ((e == 'd') || (e == 'm'))
1615                                                 e = 0;
1616                                 }
1617 /* print only if available */
1618                                 if ((e == 'p') && (IsEmptyStr(printcmd)))
1619                                         e = 0;
1620 /* can't file if not allowed */
1621                                     if ((e == 'f')
1622                                         && (rc_allow_attachments == 0))
1623                                         e = 0;
1624 /* link only if browser avail*/
1625                                     if ((e == 'u')
1626                                         && (IsEmptyStr(rc_url_cmd)))
1627                                         e = 0;
1628                                 if ((e == 'i')
1629                                         && (IsEmptyStr(imagecmd) || !has_images))
1630                                         e = 0;
1631                         } while ((e != 'a') && (e != 'n') && (e != 's')
1632                                  && (e != 'd') && (e != 'm') && (e != 'p')
1633                                  && (e != 'q') && (e != 'b') && (e != 'h')
1634                                  && (e != 'r') && (e != 'f') && (e != '?')
1635                                  && (e != 'u') && (e != 'c') && (e != 'y')
1636                                  && (e != 'i') && (e != 'o') );
1637                         switch (e) {
1638                         case 's':
1639                                 scr_printf("Stop");
1640                                 break;
1641                         case 'a':
1642                                 scr_printf("Again");
1643                                 break;
1644                         case 'd':
1645                                 scr_printf("Delete");
1646                                 break;
1647                         case 'm':
1648                                 scr_printf("Move");
1649                                 break;
1650                         case 'c':
1651                                 scr_printf("Copy");
1652                                 break;
1653                         case 'n':
1654                                 scr_printf("Next");
1655                                 break;
1656                         case 'p':
1657                                 scr_printf("Print");
1658                                 break;
1659                         case 'q':
1660                                 scr_printf("Quote");
1661                                 break;
1662                         case 'b':
1663                                 scr_printf("Back");
1664                                 break;
1665                         case 'h':
1666                                 scr_printf("Header");
1667                                 break;
1668                         case 'r':
1669                                 scr_printf("Reply");
1670                                 break;
1671                         case 'o':
1672                                 scr_printf("Open attachments");
1673                                 break;
1674                         case 'f':
1675                                 scr_printf("File");
1676                                 break;
1677                         case 'u':
1678                                 scr_printf("URL's");
1679                                 break;
1680                         case 'y':
1681                                 scr_printf("mY next");
1682                                 break;
1683                         case 'i':
1684                                 break;
1685                         case '?':
1686                                 scr_printf("? <help>");
1687                                 break;
1688                         }
1689                         if (userflags & US_DISAPPEAR || e == 'i')
1690                                 scr_printf("\r%79s\r", "");
1691                         else
1692                                 scr_printf("\n");
1693                         scr_flush();
1694                 }
1695                 switch (e) {
1696                 case '?':
1697                         scr_printf("Options available here:\n"
1698                                 " ?  Help (prints this message)\n"
1699                                 " S  Stop reading immediately\n"
1700                                 " A  Again (repeats last message)\n"
1701                                 " N  Next (continue with next message)\n"
1702                                 " Y  My Next (continue with next message you authored)\n"
1703                                 " B  Back (go back to previous message)\n");
1704                         if (  (is_room_aide)
1705                            || (room_flags & QR_MAILBOX)
1706                            || (room_flags2 & QR2_COLLABDEL)
1707                         ) {
1708                                 scr_printf(" D  Delete this message\n"
1709                                         " M  Move message to another room\n");
1710                         }
1711                         scr_printf(" C  Copy message to another room\n");
1712                         if (!IsEmptyStr(printcmd))
1713                                 scr_printf(" P  Print this message\n");
1714                         scr_printf(
1715                                 " Q  Quote portions of this message for your next post\n"
1716                                 " H  Headers (display message headers only)\n");
1717                         if (is_mail)
1718                                 scr_printf(" R  Reply to this message\n");
1719                         if (rc_allow_attachments) {
1720                                 scr_printf(" O  (Open attachments)\n");
1721                                 scr_printf(" F  (save attachments to a File)\n");
1722                         }
1723                         if (!IsEmptyStr(rc_url_cmd))
1724                                 scr_printf(" U  (list URL's for display)\n");
1725                         if (!IsEmptyStr(imagecmd) && has_images > 0)
1726                                 scr_printf(" I  Image viewer\n");
1727                         scr_printf("\n");
1728                         goto RMSGREAD;
1729                 case 'p':
1730                         scr_flush();
1731                         dest = fopen(prtfile, "w");
1732                         arcflag = 1;
1733                         hold_color = enable_color;
1734                         enable_color = 0;
1735                         goto RAGAIN;
1736                 case 'q':
1737                         scr_flush();
1738                         dest = fopen(temp2, "w");
1739                         quotflag = 1;
1740                         hold_color = enable_color;
1741                         enable_color = 0;
1742                         goto RAGAIN;
1743                 case 's':
1744                         return;
1745                 case 'a':
1746                         goto RAGAIN;
1747                 case 'b':
1748                         a = a - (rdir * 2);
1749                         break;
1750                 case 'm':
1751                 case 'c':
1752                         newprompt("Enter target room: ",
1753                                   targ, ROOMNAMELEN - 1);
1754                         if (!IsEmptyStr(targ)) {
1755                                 r = CtdlIPCMoveMessage(ipc, (e == 'c' ? 1 : 0),
1756                                                        msg_arr[a], targ, cmd);
1757                                 scr_printf("%s\n", cmd);
1758                                 if (r / 100 == 2)
1759                                         msg_arr[a] = 0L;
1760                         } else {
1761                                 goto RMSGREAD;
1762                         }
1763                         if (r / 100 != 2)       /* r will be init'ed, FIXME */
1764                                 goto RMSGREAD;  /* the logic here sucks */
1765                         break;
1766                 case 'o':
1767                 case 'f':
1768                         newprompt("Which section? ", filename, ((sizeof filename) - 1));
1769                         r = CtdlIPCAttachmentDownload(ipc, msg_arr[a],
1770                                         filename, &attachment, progress, cmd);
1771                         if (r / 100 != 2) {
1772                                 scr_printf("%s\n", cmd);
1773                         } else {
1774                                 extract_token(filename, cmd, 2, '|', sizeof filename);
1775                                 /*
1776                                  * Part 1 won't have a filename; use the
1777                                  * subject of the message instead. IO
1778                                  */
1779                                 if (IsEmptyStr(filename)) {
1780                                         strcpy(filename, reply_subject);
1781                                 }
1782                                 if (e == 'o') {         /* open attachment */
1783                                         mkdir(tempdir, 0700);
1784                                         snprintf(save_to, sizeof save_to, "%s/%04x.%s",
1785                                                 tempdir,
1786                                                 ++att_seq,
1787                                                 filename);
1788                                         save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to);
1789                                         snprintf(cmd, sizeof cmd, rc_open_cmd, save_to);
1790                                         system(cmd);
1791                                 }
1792                                 else {                  /* save attachment to disk */
1793                                         destination_directory(save_to, filename);
1794                                         save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to);
1795                                 }
1796                         }
1797                         if (attachment) {
1798                                 free(attachment);
1799                                 attachment = NULL;
1800                         }
1801                         goto RMSGREAD;
1802                 case 'd':
1803                         scr_printf("*** Delete this message? ");
1804                         if (yesno() == 1) {
1805                                 r = CtdlIPCDeleteMessage(ipc, msg_arr[a], cmd);
1806                                 scr_printf("%s\n", cmd);
1807                                 if (r / 100 == 2)
1808                                         msg_arr[a] = 0L;
1809                         } else {
1810                                 goto RMSGREAD;
1811                         }
1812                         break;
1813                 case 'h':
1814                         read_message(ipc, msg_arr[a], READ_HEADER, NULL);
1815                         goto RMSGREAD;
1816                 case 'r':
1817                         savedpos = num_msgs;
1818                         entmsg(ipc, 1, ((userflags & US_EXTEDIT) ? 2 : 0), 0);
1819                         num_msgs = savedpos;
1820                         goto RMSGREAD;
1821                 case 'u':
1822                         list_urls(ipc);
1823                         goto RMSGREAD;
1824                 case 'i':
1825                         image_view(ipc, msg_arr[a]);
1826                         goto RMSGREAD;
1827             case 'y':
1828           { /* hack hack hack */
1829             /* find the next message by me, stay here if we find nothing */
1830             int finda;
1831             int lasta = a;
1832             for (finda = (a + rdir); ((finda < num_msgs) && (finda >= 0)); finda += rdir)
1833               {
1834                 /* This is repetitively dumb, but that's what computers are for.
1835                    We have to load up messages until we find one by us */
1836                 char buf[SIZ];
1837                 int founda = 0;
1838                 struct ctdlipcmessage *msg = NULL;
1839                 
1840                 /* read the header so we can get 'from=' */
1841                 r = CtdlIPCGetSingleMessage(ipc, msg_arr[finda], 1, 0, &msg, buf);
1842                 if (!strncasecmp(msg->author, fullname, sizeof(fullname))) {
1843                         a = lasta; /* meesa current */
1844                         founda = 1;
1845                 }
1846
1847                 free(msg);
1848
1849                 if (founda)
1850                         break; /* for */
1851                 lasta = finda; /* keep one behind or we skip on the reentrance to the for */
1852               } /* for */
1853           } /* case 'y' */
1854       } /* switch */
1855         }                       /* end for loop */
1856 }                               /* end read routine */
1857
1858
1859
1860
1861 /*
1862  * View and edit a system message
1863  */
1864 void edit_system_message(CtdlIPC *ipc, char *which_message)
1865 {
1866         char desc[SIZ];
1867         char read_cmd[SIZ];
1868         char write_cmd[SIZ];
1869
1870         snprintf(desc, sizeof desc, "system message '%s'", which_message);
1871         snprintf(read_cmd, sizeof read_cmd, "MESG %s", which_message);
1872         snprintf(write_cmd, sizeof write_cmd, "EMSG %s", which_message);
1873         do_edit(ipc, desc, read_cmd, "NOOP", write_cmd);
1874 }
1875
1876
1877
1878
1879 /*
1880  * Verify the message base
1881  */
1882 void check_message_base(CtdlIPC *ipc)
1883 {
1884         char buf[SIZ];
1885         char *transcript = NULL;
1886         int r;          /* IPC response code */
1887
1888         scr_printf
1889             ("Please read the documentation before running this command.\n"
1890             "Having done so, do you still want to check the message base? ");
1891         if (yesno() == 0)
1892                 return;
1893
1894         r = CtdlIPCMessageBaseCheck(ipc, &transcript, buf);
1895         if (r / 100 != 1) {
1896                 scr_printf("%s\n", buf);
1897                 return;
1898         }
1899
1900         while (transcript && !IsEmptyStr(transcript)) {
1901                 lines_printed = 1;
1902                 extract_token(buf, transcript, 0, '\n', sizeof buf);
1903                 remove_token(transcript, 0, '\n');
1904                 pprintf("%s\n", buf);
1905         }
1906         if (transcript) free(transcript);
1907         return;
1908 }
1909
1910
1911 /*
1912  * Loads the contents of a file into memory.  Caller must free the allocated
1913  * memory.
1914  */
1915 char *load_message_from_file(FILE *src)
1916 {
1917         size_t i;
1918         size_t got = 0;
1919         char *dest = NULL;
1920
1921         fseek(src, 0, SEEK_END);
1922         i = ftell(src);
1923         rewind(src);
1924
1925         dest = (char *)calloc(1, i + 1);
1926         if (!dest)
1927                 return NULL;
1928
1929         while (got < i) {
1930                 size_t g;
1931
1932                 g = fread(dest + got, 1, i - got, src);
1933                 got += g;
1934                 if (g < i - got) {
1935                         /* Interrupted system call, keep going */
1936                         if (errno == EINTR)
1937                                 continue;
1938                         /* At this point we have either EOF or error */
1939                         i = got;
1940                         break;
1941                 }
1942                 dest[i] = 0;
1943         }
1944
1945         return dest;
1946 }