// Text client functions for reading and writing of messages // // Beware: this is really old and crappy code, written in the // late 1980s when my coding style was absolute garbage. It // works, but we probably should replace most of it. // // Copyright (c) 1987-2022 by the citadel.org team // // This program is open source software. Use, duplication, and/or // disclosure are subject to the GNU General Purpose License version 3. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. #include "textclient.h" #define MAXWORDBUF SIZ #define NO_REPLY_TO "nobody ... xxxxxx" char reply_to[SIZ]; char reply_subject[SIZ]; char reply_references[SIZ]; char reply_inreplyto[SIZ]; struct cittext { struct cittext *next; char text[MAXWORDBUF]; }; void stty_ctdl(int cmd); int haschar(const char *st, int ch); int file_checksum(char *filename); void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax); unsigned long *msg_arr = NULL; int msg_arr_size = 0; int num_msgs; extern char room_name[]; extern char tempdir[]; extern unsigned room_flags; extern unsigned room_flags2; extern int entmsg_ok; extern long highest_msg_read; extern char temp[]; extern char temp2[]; extern int screenwidth; extern int screenheight; extern long maxmsgnum; extern char is_mail; extern char is_aide; extern char is_room_aide; extern char fullname[]; extern char axlevel; extern unsigned userflags; extern char sigcaught; extern char printcmd[]; extern int rc_allow_attachments; extern int rc_display_message_numbers; extern int rc_force_mail_prompts; extern int editor_pid; extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */ int num_urls = 0; char urls[MAXURLS][SIZ]; char imagecmd[SIZ]; int has_images = 0; /* Current msg has images */ struct parts *last_message_parts = NULL; /* Parts from last msg */ void ka_sigcatch(int signum) { alarm(S_KEEPALIVE); signal(SIGALRM, ka_sigcatch); CtdlIPCNoop(ipc_for_signal_handlers); } /* * server keep-alive version of wait() (needed for external editor) */ pid_t ka_wait(int *kstatus) { pid_t p; alarm(S_KEEPALIVE); signal(SIGALRM, ka_sigcatch); do { errno = 0; p = wait(kstatus); } while (errno == EINTR); signal(SIGALRM, SIG_IGN); alarm(0); return (p); } /* * version of system() that uses ka_wait() */ int ka_system(char *shc) { pid_t childpid; pid_t waitpid; int retcode; childpid = fork(); if (childpid < 0) { color(BRIGHT_RED); perror("Cannot fork"); color(DIM_WHITE); return ((pid_t) childpid); } if (childpid == 0) { execlp("/bin/sh", "sh", "-c", shc, NULL); exit(127); } if (childpid > 0) { do { waitpid = ka_wait(&retcode); } while (waitpid != childpid); return (retcode); } return (-1); } /* * add a newline to the buffer... */ void add_newline(struct cittext *textlist) { struct cittext *ptr; ptr = textlist; while (ptr->next != NULL) ptr = ptr->next; while (ptr->text[strlen(ptr->text) - 1] == 32) ptr->text[strlen(ptr->text) - 1] = 0; ptr->next = (struct cittext *) malloc(sizeof(struct cittext)); ptr = ptr->next; ptr->next = NULL; strcpy(ptr->text, ""); } /* * add a word to the buffer... */ void add_word(struct cittext *textlist, char *wordbuf) { struct cittext *ptr; ptr = textlist; while (ptr->next != NULL) ptr = ptr->next; if (3 + strlen(ptr->text) + strlen(wordbuf) > screenwidth) { ptr->next = (struct cittext *) malloc(sizeof(struct cittext)); ptr = ptr->next; ptr->next = NULL; strcpy(ptr->text, ""); } strcat(ptr->text, wordbuf); strcat(ptr->text, " "); } /* * begin editing of an opened file pointed to by fp */ void citedit(FILE * fp) { int a, prev, finished, b, last_space; int appending = 0; struct cittext *textlist = NULL; struct cittext *ptr; char wordbuf[MAXWORDBUF]; int rv = 0; /* first, load the text into the buffer */ fseek(fp, 0L, 0); textlist = (struct cittext *) malloc(sizeof(struct cittext)); textlist->next = NULL; strcpy(textlist->text, ""); strcpy(wordbuf, ""); prev = (-1); while (a = getc(fp), a >= 0) { appending = 1; if ((a == 32) || (a == 9) || (a == 13) || (a == 10)) { add_word(textlist, wordbuf); strcpy(wordbuf, ""); if ((prev == 13) || (prev == 10)) { add_word(textlist, "\n"); add_newline(textlist); add_word(textlist, ""); } } else { wordbuf[strlen(wordbuf) + 1] = 0; wordbuf[strlen(wordbuf)] = a; } if (strlen(wordbuf) + 3 > screenwidth) { add_word(textlist, wordbuf); strcpy(wordbuf, ""); } prev = a; } /* get text */ finished = 0; prev = (appending ? 13 : (-1)); strcpy(wordbuf, ""); do { a = inkey(); if (a == 10) a = 13; if (a == 9) a = 32; if (a == 127) a = 8; if ((a != 32) && (prev == 13)) { add_word(textlist, "\n"); scr_printf(" "); } if ((a == 32) && (prev == 13)) { add_word(textlist, "\n"); add_newline(textlist); } if (a == 8) { if (!IsEmptyStr(wordbuf)) { wordbuf[strlen(wordbuf) - 1] = 0; scr_putc(8); scr_putc(32); scr_putc(8); } } else if (a == 23) { do { wordbuf[strlen(wordbuf) - 1] = 0; scr_putc(8); scr_putc(32); scr_putc(8); } while (!IsEmptyStr(wordbuf) && wordbuf[strlen(wordbuf) - 1] != ' '); } else if (a == 13) { scr_printf("\n"); if (IsEmptyStr(wordbuf)) finished = 1; else { for (b = 0; b < strlen(wordbuf); ++b) if (wordbuf[b] == 32) { wordbuf[b] = 0; add_word(textlist, wordbuf); strcpy(wordbuf, &wordbuf[b + 1]); b = 0; } add_word(textlist, wordbuf); strcpy(wordbuf, ""); } } else { scr_putc(a); wordbuf[strlen(wordbuf) + 1] = 0; wordbuf[strlen(wordbuf)] = a; } if ((strlen(wordbuf) + 3) > screenwidth) { last_space = (-1); for (b = 0; b < strlen(wordbuf); ++b) if (wordbuf[b] == 32) last_space = b; if (last_space >= 0) { for (b = 0; b < strlen(wordbuf); ++b) if (wordbuf[b] == 32) { wordbuf[b] = 0; add_word(textlist, wordbuf); strcpy(wordbuf, &wordbuf[b + 1]); b = 0; } for (b = 0; b < strlen(wordbuf); ++b) { scr_putc(8); scr_putc(32); scr_putc(8); } scr_printf("\n%s", wordbuf); } else { add_word(textlist, wordbuf); strcpy(wordbuf, ""); scr_printf("\n"); } } prev = a; } while (finished == 0); /* write the buffer back to disk */ fseek(fp, 0L, 0); for (ptr = textlist; ptr != NULL; ptr = ptr->next) { fprintf(fp, "%s", ptr->text); } putc(10, fp); fflush(fp); rv = ftruncate(fileno(fp), ftell(fp)); if (rv < 0) scr_printf("failed to set message buffer: %s\n", strerror(errno)); /* and deallocate the memory we used */ while (textlist != NULL) { ptr = textlist->next; free(textlist); textlist = ptr; } } /* * Free the struct parts */ void free_parts(struct parts *p) { struct parts *a_part = p; while (a_part) { struct parts *q; q = a_part; a_part = a_part->next; free(q); } } /* * This is a mini RFC2047 decoder. * It only handles strings encoded from UTF-8 as Quoted-printable. * We can do this "in place" because the converted string will always be smaller than the source string. */ void mini_2047_decode(char *s) { if (!s) { // no null strings allowed! return; } char *qstart = strstr(s, "=?UTF-8?Q?"); // Must start with this string if (!qstart) { return; } char *qend = strstr(qstart + 10, "?="); // Must end with this string if (!qend) { return; } if (qend <= qstart) { // And there must be something in between them. return; } // The string has qualified for conversion. strcpy(qend, ""); // Strip the trailer strcpy(qstart, &qstart[10]); // Strip the header char *r = qstart; // Pointer to where in the string we're reading char *w = s; // Pointer to where in the string we're writing while (*r) { // Loop through the source string if (r[0] == '=') { // "=" means read a hex character char ch[3]; ch[0] = r[1]; ch[1] = r[2]; ch[2] = r[3]; int c; sscanf(ch, "%02x", &c); w[0] = c; r += 3; ++w; } else if (r[0] == '_') { // "_" is a space w[0] = ' '; ++r; ++w; } else { // anything else pass through literally w[0] = r[0]; ++r; ++w; } } w[0] = 0; // null terminate } /* * Read a message from the server */ int read_message(CtdlIPC * ipc, long num, /* message number */ int pagin, /* 0 = normal read, 1 = read with pagination, 2 = header */ FILE * dest /* Destination file, NULL for screen */ ) { char buf[SIZ]; char now[256]; int format_type = 0; int fr = 0; int nhdr = 0; struct ctdlipcmessage *message = NULL; int r; /* IPC response code */ char *converted_text = NULL; char *lineptr; char *nextline; char *searchptr; int i; char ch; int linelen; int final_line_is_blank = 0; has_images = 0; sigcaught = 0; stty_ctdl(1); strcpy(reply_to, NO_REPLY_TO); strcpy(reply_subject, ""); strcpy(reply_references, ""); strcpy(reply_inreplyto, ""); r = CtdlIPCGetSingleMessage(ipc, num, (pagin == READ_HEADER ? 1 : 0), 4, &message, buf); if (r / 100 != 1) { scr_printf("*** msg #%ld: %d %s\n", num, r, buf); stty_ctdl(0); free(message->text); free_parts(message->attachments); free(message); return (0); } if (dest) { fprintf(dest, "\n "); } else { scr_printf("\n"); if (pagin != 2) { scr_printf(" "); } } if (pagin == 1 && !dest) { color(BRIGHT_CYAN); } /* View headers only */ if (pagin == 2) { scr_printf("nhdr=%s\nfrom=%s\ntype=%d\nmsgn=%s\n", message->nhdr ? "yes" : "no", message->author, message->type, message->msgid); if (!IsEmptyStr(message->subject)) { scr_printf("subj=%s\n", message->subject); } if (!IsEmptyStr(message->email)) { scr_printf("rfca=%s\n", message->email); } scr_printf("room=%s\ntime=%s", message->room, asctime(localtime(&message->time))); if (!IsEmptyStr(message->recipient)) { scr_printf("rcpt=%s\n", message->recipient); } if (message->attachments) { struct parts *ptr; for (ptr = message->attachments; ptr; ptr = ptr->next) { scr_printf("part=%s|%s|%s|%s|%s|%ld\n", ptr->name, ptr->filename, ptr->number, ptr->disposition, ptr->mimetype, ptr->length); } } scr_printf("\n"); stty_ctdl(0); free(message->text); free_parts(message->attachments); free(message); return (0); } if (rc_display_message_numbers) { if (dest) { fprintf(dest, "[#%s] ", message->msgid); } else { color(DIM_WHITE); scr_printf("["); color(BRIGHT_WHITE); scr_printf("#%s", message->msgid); color(DIM_WHITE); scr_printf("] "); } } if (nhdr == 1 && !is_room_aide) { if (dest) { fprintf(dest, " ****"); } else { scr_printf(" ****"); } } else { struct tm thetime; localtime_r(&message->time, &thetime); strftime(now, sizeof now, "%F %R", &thetime); if (dest) { fprintf(dest, "%s from %s ", now, message->author); if (!message->is_local) { fprintf(dest, "<%s> ", message->email); } } else { color(BRIGHT_CYAN); scr_printf("%s ", now); color(DIM_WHITE); scr_printf("from "); color(BRIGHT_CYAN); scr_printf("%s ", message->author); if (!message->is_local) { color(DIM_WHITE); scr_printf("<"); color(BRIGHT_BLUE); scr_printf("%s", message->email); color(DIM_WHITE); scr_printf("> "); } } if (strcasecmp(message->room, room_name) && (IsEmptyStr(message->email))) { if (dest) { fprintf(dest, "in %s> ", message->room); } else { color(DIM_WHITE); scr_printf("in "); color(BRIGHT_MAGENTA); scr_printf("%s> ", message->room); } } if (!IsEmptyStr(message->recipient)) { if (dest) { fprintf(dest, "to %s ", message->recipient); } else { color(DIM_WHITE); scr_printf("to "); color(BRIGHT_CYAN); scr_printf("%s ", message->recipient); } } } if (dest) { fprintf(dest, "\n"); } else { scr_printf("\n"); } // Set the reply-to address to an Internet e-mail address if possible if ((message->email != NULL) && (!IsEmptyStr(message->email))) { if (!IsEmptyStr(message->author)) { snprintf(reply_to, sizeof reply_to, "%s <%s>", message->author, message->email); } else { strncpy(reply_to, message->email, sizeof reply_to); } } // But if we can't do that, set it to a Citadel address. if (!strcmp(reply_to, NO_REPLY_TO)) { strncpy(reply_to, message->author, sizeof(reply_to)); } if (message->msgid != NULL) { strncpy(reply_inreplyto, message->msgid, sizeof reply_inreplyto); } if (message->references != NULL) { if (!IsEmptyStr(message->references)) { strncpy(reply_references, message->references, sizeof reply_references); } } if (message->subject != NULL) { strncpy(reply_subject, message->subject, sizeof reply_subject); if (!IsEmptyStr(message->subject)) { if (dest) { fprintf(dest, "Subject: %s\n", message->subject); } else { color(DIM_WHITE); scr_printf("Subject: "); color(BRIGHT_CYAN); mini_2047_decode(message->subject); scr_printf("%s\n", message->subject); } } } if (pagin == 1 && !dest) { color(BRIGHT_WHITE); } /******* end of header output, start of message text output *******/ /* * Convert HTML to plain text, formatting for the actual width * of the client screen. */ if (!strcasecmp(message->content_type, "text/html")) { converted_text = html_to_ascii(message->text, 0, screenwidth); if (converted_text != NULL) { free(message->text); message->text = converted_text; format_type = 1; } } /* Text/plain is a different type */ if (!strcasecmp(message->content_type, "text/plain")) { format_type = 1; } /* Render text/x-markdown as plain text */ if (!strcasecmp(message->content_type, "text/x-markdown")) { format_type = 1; } /* Extract URL's */ static char *urlprefixes[] = { "http://", "https://", "ftp://" }; int p = 0; num_urls = 0; /* Start with a clean slate */ for (p = 0; p < (sizeof urlprefixes / sizeof(char *)); ++p) { searchptr = message->text; while ((searchptr != NULL) && (num_urls < MAXURLS)) { searchptr = strstr(searchptr, urlprefixes[p]); if (searchptr != NULL) { strncpy(urls[num_urls], searchptr, sizeof(urls[num_urls])); for (i = 0; i < strlen(urls[num_urls]); i++) { ch = urls[num_urls][i]; if (ch == '>' || ch == '\"' || ch == ')' || ch == ' ' || ch == '\n') { urls[num_urls][i] = 0; break; } } num_urls++; ++searchptr; } } } /* * Here we go */ if (format_type == 0) { fr = fmout(screenwidth, NULL, message->text, dest, 1); } else { /* renderer for text/plain */ lineptr = message->text; do { nextline = strchr(lineptr, '\n'); if (nextline != NULL) { *nextline = 0; ++nextline; if (*nextline == 0) nextline = NULL; } if (sigcaught == 0) { linelen = strlen(lineptr); if (linelen && (lineptr[linelen - 1] == '\r')) { lineptr[--linelen] = 0; } if (dest) { fprintf(dest, "%s\n", lineptr); } else { scr_printf("%s\n", lineptr); } } if (lineptr[0] == 0) final_line_is_blank = 1; else final_line_is_blank = 0; lineptr = nextline; } while (nextline); fr = sigcaught; } if (!final_line_is_blank) { if (dest) { fprintf(dest, "\n"); } else { scr_printf("\n"); fr = sigcaught; } } /* Enumerate any attachments */ if ((pagin == 1) && (message->attachments)) { struct parts *ptr; for (ptr = message->attachments; ptr; ptr = ptr->next) { if ((!strcasecmp(ptr->disposition, "attachment")) || (!strcasecmp(ptr->disposition, "inline")) || (!strcasecmp(ptr->disposition, "")) ) { if ((strcasecmp(ptr->number, message->mime_chosen)) && (!IsEmptyStr(ptr->mimetype)) ) { color(DIM_WHITE); scr_printf("Part "); color(BRIGHT_MAGENTA); scr_printf("%s", ptr->number); color(DIM_WHITE); scr_printf(": "); color(BRIGHT_CYAN); scr_printf("%s", ptr->filename); color(DIM_WHITE); scr_printf(" (%s, %ld bytes)\n", ptr->mimetype, ptr->length); if (!strncmp(ptr->mimetype, "image/", 6)) { has_images++; } } } } } /* Save the attachments info for later */ last_message_parts = message->attachments; /* Now we're done */ free(message->text); free(message); if (pagin == 1 && !dest) color(DIM_WHITE); stty_ctdl(0); return (fr); } /* * replace string function for the built-in editor */ void replace_string(char *filename, long int startpos) { char buf[512]; char srch_str[128]; char rplc_str[128]; FILE *fp; int a; long rpos, wpos; char *ptr; int substitutions = 0; long msglen = 0L; int rv; newprompt("Enter text to be replaced: ", srch_str, (sizeof(srch_str) - 1)); if (IsEmptyStr(srch_str)) { return; } newprompt("Enter text to replace it with: ", rplc_str, (sizeof(rplc_str) - 1)); fp = fopen(filename, "r+"); if (fp == NULL) { return; } wpos = startpos; fseek(fp, startpos, 0); strcpy(buf, ""); while (a = getc(fp), a > 0) { ++msglen; buf[strlen(buf) + 1] = 0; buf[strlen(buf)] = a; if (strlen(buf) >= strlen(srch_str)) { ptr = (&buf[strlen(buf) - strlen(srch_str)]); if (!strncmp(ptr, srch_str, strlen(srch_str))) { strcpy(ptr, rplc_str); ++substitutions; } } if (strlen(buf) > 384) { rpos = ftell(fp); fseek(fp, wpos, 0); rv = fwrite((char *) buf, 128, 1, fp); if (rv < 0) { scr_printf("failed to replace string: %s\n", strerror(errno)); break; /*whoopsi! */ } strcpy(buf, &buf[128]); wpos = ftell(fp); fseek(fp, rpos, 0); } } fseek(fp, wpos, 0); if (!IsEmptyStr(buf)) { rv = fwrite((char *) buf, strlen(buf), 1, fp); } wpos = ftell(fp); fclose(fp); rv = truncate(filename, wpos); scr_printf("eplace made %d substitution(s).\n\n", substitutions); } // Function to begin composing a new message int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name char *recipient, // NULL if it's not mail int is_anonymous, int format_type, int mode, char *subject, // buffer to store subject line int subject_required) { FILE *fp; int a, b, e_ex_code; long beg; char datestr[256]; char header[SIZ]; int cksum = 0; if ((mode == 2) && (IsEmptyStr(editor_path))) { scr_printf("*** No editor available; using built-in editor.\n"); mode = 0; } struct tm thetime; time_t now = time(NULL); localtime_r(&now, &thetime); strftime(datestr, sizeof datestr, "%F %R", &thetime); header[0] = 0; if (room_flags & QR_ANONONLY && !recipient) { snprintf(header, sizeof header, " ****"); } else { snprintf(header, sizeof header, " %s from %s", datestr, (is_anonymous ? "[anonymous]" : fullname)); if (!IsEmptyStr(recipient)) { size_t tmp = strlen(header); snprintf(&header[tmp], sizeof header - tmp, " to %s", recipient); } } scr_printf("%s\n", header); if (subject != NULL) if (!IsEmptyStr(subject)) { scr_printf("Subject: %s\n", subject); } if ((subject_required) && (IsEmptyStr(subject))) { newprompt("Subject: ", subject, 70); } if (mode == 1) { scr_printf("(Press ctrl-d when finished)\n"); } if (mode == 0) { fp = fopen(filename, "r"); if (fp != NULL) { fmout(screenwidth, fp, NULL, NULL, 0); beg = ftell(fp); if (beg < 0) scr_printf("failed to get stream position %s\n", strerror(errno)); fclose(fp); } else { fp = fopen(filename, "w"); if (fp == NULL) { scr_printf("*** Error opening temp file!\n %s: %s\n", filename, strerror(errno)); return (1); } fclose(fp); } } ME1:switch (mode) { case 0: fp = fopen(filename, "r+"); if (fp == NULL) { scr_printf("*** Error opening temp file!\n %s: %s\n", filename, strerror(errno) ); return (1); } citedit(fp); fclose(fp); goto MECR; case 1: fp = fopen(filename, "a"); if (fp == NULL) { scr_printf("*** Error opening temp file!\n" " %s: %s\n", filename, strerror(errno)); return (1); } do { a = inkey(); if (a == 255) a = 32; if (a == 13) a = 10; if (a != 4) { putc(a, fp); scr_putc(a); } if (a == 10) scr_putc(10); } while (a != 4); fclose(fp); break; case 2: default: /* allow 2+ modes */ e_ex_code = 1; /* start with a failed exit code */ stty_ctdl(SB_RESTORE); editor_pid = fork(); cksum = file_checksum(filename); if (editor_pid == 0) { char tmp[SIZ]; chmod(filename, 0600); snprintf(tmp, sizeof tmp, "WINDOW_TITLE=%s", header); putenv(tmp); execlp(editor_path, editor_path, filename, NULL); exit(1); } if (editor_pid > 0) do { e_ex_code = 0; b = ka_wait(&e_ex_code); } while ((b != editor_pid) && (b >= 0)); editor_pid = (-1); stty_ctdl(0); break; } MECR:if (mode >= 2) { if (file_checksum(filename) == cksum) { scr_printf("*** Aborted message.\n"); e_ex_code = 1; } if (e_ex_code == 0) { goto MEFIN; } goto MEABT2; } b = keymenu("Entry command (? for options)", "bort|" "ontinue|" "ave message|" "

rint formatted|" "add sbject|" "eplace string|" "old message"); if (b == 'a') goto MEABT; if (b == 'c') goto ME1; if (b == 's') goto MEFIN; if (b == 'p') { scr_printf(" %s from %s", datestr, fullname); if (!IsEmptyStr(recipient)) { scr_printf(" to %s", recipient); } scr_printf("\n"); if (subject != NULL) if (!IsEmptyStr(subject)) { scr_printf("Subject: %s\n", subject); } fp = fopen(filename, "r"); if (fp != NULL) { fmout(screenwidth, fp, NULL, NULL, 0); beg = ftell(fp); if (beg < 0) scr_printf("failed to get stream position %s\n", strerror(errno)); fclose(fp); } goto MECR; } if (b == 'r') { replace_string(filename, 0L); goto MECR; } if (b == 'h') { return (2); } if (b == 'u') { if (subject != NULL) { newprompt("Subject: ", subject, 70); } goto MECR; } MEFIN:return (0); MEABT:scr_printf("Are you sure? "); if (yesno() == 0) { goto ME1; } MEABT2:unlink(filename); return (2); } /* * Make sure there's room in msg_arr[] for at least one more. */ void check_msg_arr_size(void) { if ((num_msgs + 1) > msg_arr_size) { msg_arr_size += 512; msg_arr = realloc(msg_arr, ((sizeof(long)) * msg_arr_size)); } } /* * break_big_lines() - break up lines that are >1024 characters * otherwise the server will truncate */ void break_big_lines(char *msg) { char *ptr; char *break_here; if (msg == NULL) { return; } ptr = msg; while (strlen(ptr) > 1000) { break_here = strchr(&ptr[900], ' '); if ((break_here == NULL) || (break_here > &ptr[999])) { break_here = &ptr[999]; } *break_here = '\n'; ptr = break_here++; } } /* * entmsg() - edit and create a message * returns 0 if message was saved */ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a eply command */ int c, /* mode */ int masquerade /* prompt for a non-default display name? */ ) { char buf[SIZ]; int a, b; int need_recp = 0; int mode; long highmsg = 0L; FILE *fp; char subject[SIZ]; struct ctdlipcmessage message; unsigned long *msgarr = NULL; int r; /* IPC response code */ int subject_required = 0; /* * First, check to see if we have permission to enter a message in * this room. The server will return an error code if we can't. */ if (entmsg_ok == ENTMSG_OK_YES) { /* no problem, go right ahead */ } else if (entmsg_ok == ENTMSG_OK_BLOG) { if (!is_reply) { scr_printf("WARNING: this is a BLOG room.\n"); scr_printf("The 'nter Message' command will create a BLOG POST.\n"); scr_printf("If you want to leave a comment or reply to a comment, use the 'eply' command.\n"); scr_printf("Do you really want to create a new blog post? "); if (!yesno()) { return (1); } } } else { scr_printf("You may not enter messages in this type of room.\n"); return (1); } if (c > 0) { mode = 1; } else { mode = 0; } strcpy(subject, ""); strcpy(message.recipient, ""); strcpy(message.author, ""); strcpy(message.subject, ""); strcpy(message.references, ""); message.text = ""; /* point to "", changes later */ message.anonymous = 0; message.type = mode; if (masquerade) { newprompt("Display name for this message: ", message.author, 40); } if (is_reply) { if (!IsEmptyStr(reply_subject)) { if (!strncasecmp(reply_subject, "Re: ", 3)) { strcpy(message.subject, reply_subject); } else { snprintf(message.subject, sizeof message.subject, "Re: %s", reply_subject); } } /* Trim down excessively long lists of thread references. We eliminate the * second one in the list so that the thread root remains intact. */ int rrtok = num_tokens(reply_references, '|'); int rrlen = strlen(reply_references); if (((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10)) { remove_token(reply_references, 1, '|'); } snprintf(message.references, sizeof message.references, "%s%s%s", reply_references, (IsEmptyStr(reply_references) ? "" : "|"), reply_inreplyto); } r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf); if (r / 100 != 2 && r / 10 != 57) { scr_printf("%s\n", buf); return (1); } /* Error code 570 is special. It means that we CAN enter a message * in this room, but a recipient needs to be specified. */ need_recp = 0; if (r / 10 == 57) { need_recp = 1; } /* If the user is a dumbass, tell them how to type. */ if ((userflags & US_EXPERT) == 0) { scr_printf("Entering message. Word wrap will give you soft linebreaks. Pressing the\n"); scr_printf("'enter' key will give you a hard linebreak and an indent. Press 'enter' twice\n"); scr_printf("when finished.\n"); } /* Handle the selection of a recipient, if necessary. */ strcpy(buf, ""); if (need_recp == 1) { if (axlevel >= AxProbU) { if (is_reply) { strcpy(buf, reply_to); } else { newprompt("Enter recipient: ", buf, SIZ - 100); if (IsEmptyStr(buf)) { return (1); } } } else { strcpy(buf, "sysop"); } } strcpy(message.recipient, buf); if (room_flags & QR_ANONOPT) { scr_printf("Anonymous (Y/N)? "); if (yesno() == 1) message.anonymous = 1; } /* If it's mail, we've got to check the validity of the recipient... */ if (!IsEmptyStr(message.recipient)) { r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf); if (r / 100 != 2) { scr_printf("%s\n", buf); return (1); } } /* Learn the number of the newest message in in the room, so we can * tell upon saving whether someone else has posted too. */ num_msgs = 0; r = CtdlIPCGetMessages(ipc, LastMessages, 1, NULL, &msgarr, buf); if (r / 100 != 1) { scr_printf("%s\n", buf); } else { for (num_msgs = 0; msgarr[num_msgs]; num_msgs++); } /* Now compose the message... */ if (client_make_message(ipc, temp, message.recipient, message.anonymous, 0, c, message.subject, subject_required) != 0) { if (msgarr) free(msgarr); return (2); } /* Reopen the temp file that was created, so we can send it */ fp = fopen(temp, "r"); if (!fp || !(message.text = load_message_from_file(fp))) { scr_printf("*** Internal error while trying to save message!\n" "%s: %s\n", temp, strerror(errno)); unlink(temp); return (errno); } if (fp) fclose(fp); /* Break lines that are >1024 characters, otherwise the server * will truncate them. */ break_big_lines(message.text); /* Transmit message to the server */ r = CtdlIPCPostMessage(ipc, 1, NULL, &message, buf); if (r / 100 != 4) { scr_printf("%s\n", buf); return (1); } /* Yes, unlink it now, so it doesn't stick around if we crash */ unlink(temp); if (num_msgs >= 1) highmsg = msgarr[num_msgs - 1]; if (msgarr) free(msgarr); msgarr = NULL; r = CtdlIPCGetMessages(ipc, NewMessages, 0, NULL, &msgarr, buf); if (r / 100 != 1) { scr_printf("%s\n", buf); } else { for (num_msgs = 0; msgarr[num_msgs]; num_msgs++); } /* get new highest message number in room to set lrp for goto... */ maxmsgnum = msgarr[num_msgs - 1]; /* now see if anyone else has posted in here */ b = (-1); for (a = 0; a < num_msgs; ++a) { if (msgarr[a] > highmsg) { ++b; } } if (msgarr) { free(msgarr); } msgarr = NULL; /* In the Mail> room, this algorithm always counts one message * higher than in public rooms, so we decrement it by one. */ if (need_recp) { --b; } if (b == 1) { scr_printf("*** 1 additional message has been entered in this room by another user.\n"); } else if (b > 1) { scr_printf("*** %d additional messages have been entered in this room by other users.\n", b); } free(message.text); return (0); } /* * Do editing on a quoted file */ void process_quote(void) { FILE *qfile, *tfile; char buf[128]; int line, qstart, qend; // Unlink the second temp file as soon as it's opened, so it'll get // deleted even if the program dies qfile = fopen(temp2, "r"); unlink(temp2); /* Display the quotable text with line numbers added */ line = 0; if (fgets(buf, 128, qfile) == NULL) { /* we're skipping a line here */ } while (fgets(buf, 128, qfile) != NULL) { scr_printf("%3d %s", ++line, buf); } qstart = intprompt("Begin quoting at", 1, 1, line); qend = intprompt(" End quoting at", line, qstart, line); rewind(qfile); line = 0; if (fgets(buf, 128, qfile) == NULL) { /* we're skipping a line here */ } tfile = fopen(temp, "w"); while (fgets(buf, 128, qfile) != NULL) { if ((++line >= qstart) && (line <= qend)) { fprintf(tfile, " >%s", buf); } } fprintf(tfile, " \n"); fclose(qfile); fclose(tfile); chmod(temp, 0666); } /* * List the URLs which were embedded in the previous message */ void list_urls(CtdlIPC * ipc) { int i; char cmd[SIZ]; int rv; if (num_urls == 0) { scr_printf("There were no URLs in the previous message.\n\n"); return; } for (i = 0; i < num_urls; ++i) { scr_printf("%3d %s\n", i + 1, urls[i]); } if ((i = num_urls) != 1) { i = intprompt("Display which one", 1, 1, num_urls); } snprintf(cmd, sizeof cmd, rc_url_cmd, urls[i - 1]); rv = system(cmd); scr_printf("\n"); } /* * Run image viewer in background */ int do_image_view(const char *filename) { char cmd[SIZ]; pid_t childpid; snprintf(cmd, sizeof cmd, imagecmd, filename); childpid = fork(); if (childpid < 0) { unlink(filename); return childpid; } if (childpid == 0) { int retcode; pid_t grandchildpid; grandchildpid = fork(); if (grandchildpid < 0) { return grandchildpid; } if (grandchildpid == 0) { int nullfd; int outfd = -1; int errfd = -1; nullfd = open("/dev/null", O_WRONLY); if (nullfd > -1) { dup2(1, outfd); dup2(2, errfd); dup2(nullfd, 1); dup2(nullfd, 2); } retcode = system(cmd); if (nullfd > -1) { dup2(outfd, 1); dup2(errfd, 2); close(nullfd); } unlink(filename); exit(retcode); } if (grandchildpid > 0) { exit(0); } } if (childpid > 0) { int retcode; waitpid(childpid, &retcode, 0); return retcode; } return -1; } /* * View an image attached to a message */ void image_view(CtdlIPC * ipc, unsigned long msg) { struct parts *ptr = last_message_parts; char part[SIZ]; int found = 0; /* Run through available parts */ for (ptr = last_message_parts; ptr; ptr = ptr->next) { if ((!strcasecmp(ptr->disposition, "attachment") || !strcasecmp(ptr->disposition, "inline")) && !strncmp(ptr->mimetype, "image/", 6)) { found++; if (found == 1) { strcpy(part, ptr->number); } } } while (found > 0) { if (found > 1) strprompt("View which part (0 when done)", part, SIZ - 1); found = -found; for (ptr = last_message_parts; ptr; ptr = ptr->next) { if ((!strcasecmp(ptr->disposition, "attachment") || !strcasecmp(ptr->disposition, "inline")) && !strncmp(ptr->mimetype, "image/", 6) && !strcasecmp(ptr->number, part)) { char tmp[PATH_MAX]; char buf[SIZ]; void *file = NULL; /* The downloaded file */ int r; /* view image */ found = -found; r = CtdlIPCAttachmentDownload(ipc, msg, ptr->number, &file, progress, buf); if (r / 100 != 2) { scr_printf("%s\n", buf); } else { size_t len; len = (size_t) extract_long(buf, 0); progress(ipc, len, len); scr_flush(); CtdlMakeTempFileName(tmp, sizeof tmp); strcat(tmp, ptr->filename); save_buffer(file, len, tmp); free(file); do_image_view(tmp); } break; } } if (found == 1) break; } } /* * Read the messages in the current room */ void readmsgs(CtdlIPC * ipc, enum MessageList c, // see listing in citadel_ipc.h enum MessageDirection rdir, // 1=Forward (-1)=Reverse int q // Number of msgs to read (if c==3) ) { int a, e, f, g, start; int savedpos; int hold_sw = 0; char arcflag = 0; char quotflag = 0; int hold_color = 0; char prtfile[PATH_MAX]; char pagin; char cmd[SIZ]; char targ[ROOMNAMELEN]; char filename[PATH_MAX]; char save_to[PATH_MAX]; void *attachment = NULL; /* Downloaded attachment */ FILE *dest = NULL; /* Alternate destination other than screen */ int r; /* IPC response code */ static int att_seq = 0; /* Attachment download sequence number */ int rv = 0; /* silence the stupid warn_unused_result warnings */ CtdlMakeTempFileName(prtfile, sizeof prtfile); if (msg_arr) { free(msg_arr); msg_arr = NULL; } r = CtdlIPCGetMessages(ipc, c, q, NULL, &msg_arr, cmd); if (r / 100 != 1) { scr_printf("%s\n", cmd); } else { for (num_msgs = 0; msg_arr[num_msgs]; num_msgs++); } if (num_msgs == 0) { if (c == LastMessages) { return; } scr_printf("*** There are no "); if (c == NewMessages) scr_printf("new "); if (c == OldMessages) scr_printf("old "); scr_printf("messages in this room.\n"); return; } /* this loop cycles through each message... */ start = ((rdir == 1) ? 0 : (num_msgs - 1)); for (a = start; ((a < num_msgs) && (a >= 0)); a = a + rdir) { while (msg_arr[a] == 0L) { a = a + rdir; if ((a == num_msgs) || (a == (-1))) return; } RAGAIN:pagin = ((arcflag == 0) && (quotflag == 0) && (userflags & US_PAGINATOR)) ? 1 : 0; /* If we're doing a quote, set the screenwidth to 72 */ if (quotflag) { hold_sw = screenwidth; screenwidth = 72; } /* If printing or archiving, set the screenwidth to 80 */ if (arcflag) { hold_sw = screenwidth; screenwidth = 80; } /* clear parts list */ free_parts(last_message_parts); last_message_parts = NULL; /* now read the message... */ e = read_message(ipc, msg_arr[a], pagin, dest); /* ...and set the screenwidth back if we have to */ if ((quotflag) || (arcflag)) { screenwidth = hold_sw; } RMSGREAD: highest_msg_read = msg_arr[a]; if (quotflag) { fclose(dest); dest = NULL; quotflag = 0; enable_color = hold_color; process_quote(); e = 'r'; goto DONE_QUOTING; } if (arcflag) { fclose(dest); dest = NULL; arcflag = 0; enable_color = hold_color; f = fork(); if (f == 0) { if (freopen(prtfile, "r", stdin) == NULL) { /* we probably should handle the error condition here */ } stty_ctdl(SB_RESTORE); ka_system(printcmd); stty_ctdl(SB_NO_INTR); unlink(prtfile); exit(0); } if (f > 0) do { g = wait(NULL); } while ((g != f) && (g >= 0)); scr_printf("Message printed.\n"); } if (e == SIGQUIT) return; if (((userflags & US_NOPROMPT) || (e == SIGINT)) && (((room_flags & QR_MAILBOX) == 0) || (rc_force_mail_prompts == 0))) { e = 'n'; } else { color(DIM_WHITE); scr_printf("("); color(BRIGHT_WHITE); scr_printf("%d", num_msgs - a - 1); color(DIM_WHITE); scr_printf(") "); keyopt("ack gain eply replyuoted ext top "); if (rc_url_cmd[0] && num_urls) keyopt("RLview "); if (has_images > 0 && !IsEmptyStr(imagecmd)) keyopt("mages "); keyopt("help -> "); do { e = (inkey() & 127); e = tolower(e); /* return key same as */ if (e == 10) e = 'n'; /* space key same as */ if (e == 32) e = 'n'; /* del/move for aides only */ if ((!is_room_aide) && ((room_flags & QR_MAILBOX) == 0) && ((room_flags2 & QR2_COLLABDEL) == 0) ) { if ((e == 'd') || (e == 'm')) e = 0; } /* print only if available */ if ((e == 'p') && (IsEmptyStr(printcmd))) e = 0; /* can't file if not allowed */ if ((e == 'f') && (rc_allow_attachments == 0)) e = 0; /* link only if browser avail*/ if ((e == 'u') && (IsEmptyStr(rc_url_cmd))) e = 0; if ((e == 'i') && (IsEmptyStr(imagecmd) || !has_images)) e = 0; } while ((e != 'a') && (e != 'n') && (e != 's') && (e != 'd') && (e != 'm') && (e != 'p') && (e != 'q') && (e != 'b') && (e != 'h') && (e != 'r') && (e != 'f') && (e != '?') && (e != 'u') && (e != 'c') && (e != 'y') && (e != 'i') && (e != 'o')); switch (e) { case 's': scr_printf("Stop"); break; case 'a': scr_printf("Again"); break; case 'd': scr_printf("Delete"); break; case 'm': scr_printf("Move"); break; case 'c': scr_printf("Copy"); break; case 'n': scr_printf("Next"); break; case 'p': scr_printf("Print"); break; case 'q': scr_printf("reply Quoted"); break; case 'b': scr_printf("Back"); break; case 'h': scr_printf("Header"); break; case 'r': scr_printf("Reply"); break; case 'o': scr_printf("Open attachments"); break; case 'f': scr_printf("File"); break; case 'u': scr_printf("URL's"); break; case 'y': scr_printf("mY next"); break; case 'i': break; case '?': scr_printf("? "); break; } if (userflags & US_DISAPPEAR || e == 'i') scr_printf("\r%79s\r", ""); else scr_printf("\n"); } DONE_QUOTING:switch (e) { case '?': scr_printf("Options available here:\n" " ? Help (prints this message)\n" " S Stop reading immediately\n" " A Again (repeats last message)\n" " N Next (continue with next message)\n" " Y My Next (continue with next message you authored)\n" " B Back (go back to previous message)\n"); if ((is_room_aide) || (room_flags & QR_MAILBOX) || (room_flags2 & QR2_COLLABDEL)) { scr_printf(" D Delete this message\n" " M Move message to another room\n"); } scr_printf(" C Copy message to another room\n"); if (!IsEmptyStr(printcmd)) scr_printf(" P Print this message\n"); scr_printf(" Q Reply to this message, quoting portions of it\n" " H Headers (display message headers only)\n"); if (is_mail) scr_printf(" R Reply to this message\n"); if (rc_allow_attachments) { scr_printf(" O (Open attachments)\n"); scr_printf(" F (save attachments to a File)\n"); } if (!IsEmptyStr(rc_url_cmd)) scr_printf(" U (list URL's for display)\n"); if (!IsEmptyStr(imagecmd) && has_images > 0) scr_printf(" I Image viewer\n"); scr_printf("\n"); goto RMSGREAD; case 'p': scr_flush(); dest = fopen(prtfile, "w"); arcflag = 1; hold_color = enable_color; enable_color = 0; goto RAGAIN; case 'q': scr_flush(); dest = fopen(temp2, "w"); quotflag = 1; hold_color = enable_color; enable_color = 0; goto RAGAIN; case 's': return; case 'a': goto RAGAIN; case 'b': a = a - (rdir * 2); break; case 'm': case 'c': newprompt("Enter target room: ", targ, ROOMNAMELEN - 1); if (!IsEmptyStr(targ)) { r = CtdlIPCMoveMessage(ipc, (e == 'c' ? 1 : 0), msg_arr[a], targ, cmd); scr_printf("%s\n", cmd); if (r / 100 == 2) msg_arr[a] = 0L; } else { goto RMSGREAD; } if (r / 100 != 2) /* r will be init'ed, FIXME */ goto RMSGREAD; /* the logic here sucks */ break; case 'o': case 'f': newprompt("Which section? ", filename, ((sizeof filename) - 1)); r = CtdlIPCAttachmentDownload(ipc, msg_arr[a], filename, &attachment, progress, cmd); if (r / 100 != 2) { scr_printf("%s\n", cmd); } else { extract_token(filename, cmd, 2, '|', sizeof filename); /* * Part 1 won't have a filename; use the * subject of the message instead. IO */ if (IsEmptyStr(filename)) { strcpy(filename, reply_subject); } if (e == 'o') { /* open attachment */ mkdir(tempdir, 0700); snprintf(save_to, sizeof save_to, "%s/%04x.%s", tempdir, ++att_seq, filename); save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to); snprintf(cmd, sizeof cmd, rc_open_cmd, save_to); rv = system(cmd); if (rv != 0) scr_printf("failed to save %s Reason %d\n", cmd, rv); } else { /* save attachment to disk */ destination_directory(save_to, filename); save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to); } } if (attachment) { free(attachment); attachment = NULL; } goto RMSGREAD; case 'd': scr_printf("*** Delete this message? "); if (yesno() == 1) { r = CtdlIPCDeleteMessage(ipc, msg_arr[a], cmd); scr_printf("%s\n", cmd); if (r / 100 == 2) msg_arr[a] = 0L; } else { goto RMSGREAD; } break; case 'h': read_message(ipc, msg_arr[a], READ_HEADER, NULL); goto RMSGREAD; case 'r': savedpos = num_msgs; entmsg(ipc, 1, ((userflags & US_EXTEDIT) ? 2 : 0), 0); num_msgs = savedpos; goto RMSGREAD; case 'u': list_urls(ipc); goto RMSGREAD; case 'i': image_view(ipc, msg_arr[a]); goto RMSGREAD; case 'y': { /* hack hack hack */ /* find the next message by me, stay here if we find nothing */ int finda; int lasta = a; for (finda = (a + rdir); ((finda < num_msgs) && (finda >= 0)); finda += rdir) { /* This is repetitively dumb, but that's what computers are for. We have to load up messages until we find one by us */ char buf[SIZ]; int founda = 0; struct ctdlipcmessage *msg = NULL; /* read the header so we can get 'from=' */ r = CtdlIPCGetSingleMessage(ipc, msg_arr[finda], 1, 0, &msg, buf); if (!strncasecmp(msg->author, fullname, sizeof(fullname))) { a = lasta; /* meesa current */ founda = 1; } free(msg); if (founda) break; /* for */ lasta = finda; /* keep one behind or we skip on the reentrance to the for */ } /* for */ } /* case 'y' */ } /* switch */ } /* end for loop */ } /* end read routine */ /* * View and edit a system message */ void edit_system_message(CtdlIPC * ipc, char *which_message) { char desc[SIZ]; char read_cmd[SIZ]; char write_cmd[SIZ]; snprintf(desc, sizeof desc, "system message '%s'", which_message); snprintf(read_cmd, sizeof read_cmd, "MESG %s", which_message); snprintf(write_cmd, sizeof write_cmd, "EMSG %s", which_message); do_edit(ipc, desc, read_cmd, "NOOP", write_cmd); } /* * Loads the contents of a file into memory. Caller must free the allocated memory. */ char *load_message_from_file(FILE * src) { size_t i; size_t got = 0; char *dest = NULL; fseek(src, 0, SEEK_END); i = ftell(src); rewind(src); dest = (char *) calloc(1, i + 1); if (!dest) return NULL; while (got < i) { size_t g; g = fread(dest + got, 1, i - got, src); got += g; if (g < i - got) { /* Interrupted system call, keep going */ if (errno == EINTR) continue; /* At this point we have either EOF or error */ i = got; break; } dest[i] = 0; } return dest; }