22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
33 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
34 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
35 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
37 extern struct config config;
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
73 * This function is self explanatory.
74 * (What can I say, I'm in a weird mood today...)
76 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
80 for (i = 0; i < strlen(name); ++i)
83 if (isspace(name[i - 1])) {
84 strcpy(&name[i - 1], &name[i]);
87 while (isspace(name[i + 1])) {
88 strcpy(&name[i + 1], &name[i + 2]);
95 * Aliasing for network mail.
96 * (Error messages have been commented out, because this is a server.)
99 { /* process alias and routing info for mail */
102 char aaa[300], bbb[300];
104 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
106 fp = fopen("network/mail.aliases", "r");
108 fp = fopen("/dev/null", "r");
113 while (fgets(aaa, sizeof aaa, fp) != NULL) {
114 while (isspace(name[0]))
115 strcpy(name, &name[1]);
116 aaa[strlen(aaa) - 1] = 0;
118 for (a = 0; a < strlen(aaa); ++a) {
120 strcpy(bbb, &aaa[a + 1]);
124 if (!strcasecmp(name, aaa))
128 lprintf(7, "Mail is being forwarded to %s\n", name);
130 /* determine local or remote type, see citadel.h */
131 for (a = 0; a < strlen(name); ++a)
133 return (MES_INTERNET);
134 for (a = 0; a < strlen(name); ++a)
136 for (b = a; b < strlen(name); ++b)
138 return (MES_INTERNET);
140 for (a = 0; a < strlen(name); ++a)
144 lprintf(7, "Too many @'s in address\n");
148 for (a = 0; a < strlen(name); ++a)
150 strcpy(bbb, &name[a + 1]);
152 strcpy(bbb, &bbb[1]);
153 fp = fopen("network/mail.sysinfo", "r");
157 a = getstring(fp, aaa);
158 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
159 a = getstring(fp, aaa);
160 if (!strncmp(aaa, "use ", 4)) {
161 strcpy(bbb, &aaa[4]);
166 if (!strncmp(aaa, "uum", 3)) {
168 for (a = 0; a < strlen(bbb); ++a) {
174 while (bbb[strlen(bbb) - 1] == '_')
175 bbb[strlen(bbb) - 1] = 0;
176 sprintf(name, &aaa[4], bbb);
177 return (MES_INTERNET);
179 if (!strncmp(aaa, "bin", 3)) {
182 while (aaa[strlen(aaa) - 1] != '@')
183 aaa[strlen(aaa) - 1] = 0;
184 aaa[strlen(aaa) - 1] = 0;
185 while (aaa[strlen(aaa) - 1] == ' ')
186 aaa[strlen(aaa) - 1] = 0;
187 while (bbb[0] != '@')
188 strcpy(bbb, &bbb[1]);
189 strcpy(bbb, &bbb[1]);
190 while (bbb[0] == ' ')
191 strcpy(bbb, &bbb[1]);
192 sprintf(name, "%s @%s", aaa, bbb);
205 fp = fopen("citadel.control", "r");
206 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
212 void simple_listing(long msgnum)
214 cprintf("%ld\n", msgnum);
219 /* Determine if a given message matches the fields in a message template.
220 * Return 0 for a successful match.
222 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
225 /* If there aren't any fields in the template, all messages will
228 if (template == NULL) return(0);
230 /* Null messages are bogus. */
231 if (msg == NULL) return(1);
233 for (i='A'; i<='Z'; ++i) {
234 if (template->cm_fields[i] != NULL) {
235 if (msg->cm_fields[i] == NULL) {
238 if (strcasecmp(msg->cm_fields[i],
239 template->cm_fields[i])) return 1;
243 /* All compares succeeded: we have a match! */
251 * API function to perform an operation for each qualifying message in the
254 void CtdlForEachMessage(int mode, long ref,
256 struct CtdlMessage *compare,
257 void (*CallBack) (long msgnum))
262 struct cdbdata *cdbfr;
263 long *msglist = NULL;
266 struct SuppMsgInfo smi;
267 struct CtdlMessage *msg;
269 /* Learn about the user and room in question */
271 getuser(&CC->usersupp, CC->curr_user);
272 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
274 /* Load the message list */
275 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
277 msglist = mallok(cdbfr->len);
278 memcpy(msglist, cdbfr->ptr, cdbfr->len);
279 num_msgs = cdbfr->len / sizeof(long);
282 return; /* No messages at all? No further action. */
286 /* If the caller is looking for a specific MIME type, then filter
287 * out all messages which are not of the type requested.
290 if (content_type != NULL)
291 if (strlen(content_type) > 0)
292 for (a = 0; a < num_msgs; ++a) {
293 GetSuppMsgInfo(&smi, msglist[a]);
294 if (strcasecmp(smi.smi_content_type, content_type)) {
299 num_msgs = sort_msglist(msglist, num_msgs);
301 /* If a template was supplied, filter out the messages which
302 * don't match. (This could induce some delays!)
305 if (compare != NULL) {
306 for (a = 0; a < num_msgs; ++a) {
307 msg = CtdlFetchMessage(msglist[a]);
309 if (CtdlMsgCmp(msg, compare)) {
312 CtdlFreeMessage(msg);
320 * Now iterate through the message list, according to the
321 * criteria supplied by the caller.
324 for (a = 0; a < num_msgs; ++a) {
325 thismsg = msglist[a];
330 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
331 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
332 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
333 && (CC->usersupp.flags & US_LASTOLD))
334 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
335 || ((mode == MSGS_FIRST) && (a < ref))
336 || ((mode == MSGS_GT) && (thismsg > ref))
342 phree(msglist); /* Clean up */
348 * cmd_msgs() - get list of message #'s in this room
349 * implements the MSGS server command using CtdlForEachMessage()
351 void cmd_msgs(char *cmdbuf)
360 int with_template = 0;
361 struct CtdlMessage *template = NULL;
363 extract(which, cmdbuf, 0);
364 cm_ref = extract_int(cmdbuf, 1);
365 with_template = extract_int(cmdbuf, 2);
369 if (!strncasecmp(which, "OLD", 3))
371 else if (!strncasecmp(which, "NEW", 3))
373 else if (!strncasecmp(which, "FIRST", 5))
375 else if (!strncasecmp(which, "LAST", 4))
377 else if (!strncasecmp(which, "GT", 2))
380 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
381 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
386 cprintf("%d Send template then receive message list\n",
388 template = (struct CtdlMessage *)
389 mallok(sizeof(struct CtdlMessage));
390 memset(template, 0, sizeof(struct CtdlMessage));
391 while(client_gets(buf), strcmp(buf,"000")) {
392 extract(tfield, buf, 0);
393 extract(tvalue, buf, 1);
394 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
395 if (!strcasecmp(tfield, msgkeys[i])) {
396 template->cm_fields[i] =
403 cprintf("%d Message list...\n", LISTING_FOLLOWS);
406 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
407 if (template != NULL) CtdlFreeMessage(template);
415 * help_subst() - support routine for help file viewer
417 void help_subst(char *strbuf, char *source, char *dest)
422 while (p = pattern2(strbuf, source), (p >= 0)) {
423 strcpy(workbuf, &strbuf[p + strlen(source)]);
424 strcpy(&strbuf[p], dest);
425 strcat(strbuf, workbuf);
430 void do_help_subst(char *buffer)
434 help_subst(buffer, "^nodename", config.c_nodename);
435 help_subst(buffer, "^humannode", config.c_humannode);
436 help_subst(buffer, "^fqdn", config.c_fqdn);
437 help_subst(buffer, "^username", CC->usersupp.fullname);
438 sprintf(buf2, "%ld", CC->usersupp.usernum);
439 help_subst(buffer, "^usernum", buf2);
440 help_subst(buffer, "^sysadm", config.c_sysadm);
441 help_subst(buffer, "^variantname", CITADEL);
442 sprintf(buf2, "%d", config.c_maxsessions);
443 help_subst(buffer, "^maxsessions", buf2);
449 * memfmout() - Citadel text formatter and paginator.
450 * Although the original purpose of this routine was to format
451 * text to the reader's screen width, all we're really using it
452 * for here is to format text out to 80 columns before sending it
453 * to the client. The client software may reformat it again.
455 void memfmout(int width, char *mptr, char subst)
456 /* screen width to use */
457 /* where are we going to get our text from? */
458 /* nonzero if we should use hypertext mode */
470 c = 1; /* c is the current pos */
473 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
475 buffer[strlen(buffer) + 1] = 0;
476 buffer[strlen(buffer)] = ch;
479 if (buffer[0] == '^')
480 do_help_subst(buffer);
482 buffer[strlen(buffer) + 1] = 0;
484 strcpy(buffer, &buffer[1]);
494 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
496 if (((old == 13) || (old == 10)) && (isspace(real))) {
504 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
505 cprintf("\n%s", aaa);
514 if ((strlen(aaa) + c) > (width - 5)) {
524 if ((ch == 13) || (ch == 10)) {
525 cprintf("%s\n", aaa);
532 FMTEND: cprintf("%s\n", aaa);
538 * Callback function for mime parser that simply lists the part
540 void list_this_part(char *name, char *filename, char *partnum, char *disp,
541 void *content, char *cbtype, size_t length)
544 cprintf("part=%s|%s|%s|%s|%s|%d\n",
545 name, filename, partnum, disp, cbtype, length);
550 * Callback function for mime parser that wants to display text
552 void fixed_output(char *name, char *filename, char *partnum, char *disp,
553 void *content, char *cbtype, size_t length)
557 if (!strcasecmp(cbtype, "multipart/alternative")) {
558 strcpy(ma->prefix, partnum);
559 strcat(ma->prefix, ".");
565 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
567 && (ma->did_print == 1) ) {
568 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
574 if (!strcasecmp(cbtype, "text/plain")) {
575 client_write(content, length);
577 else if (!strcasecmp(cbtype, "text/html")) {
578 ptr = html_to_ascii(content, 80, 0);
579 client_write(ptr, strlen(ptr));
582 else if (strncasecmp(cbtype, "multipart/", 10)) {
583 cprintf("Part %s: %s (%s) (%d bytes)\n",
584 partnum, filename, cbtype, length);
590 * Callback function for mime parser that opens a section for downloading
592 void mime_download(char *name, char *filename, char *partnum, char *disp,
593 void *content, char *cbtype, size_t length)
596 /* Silently go away if there's already a download open... */
597 if (CC->download_fp != NULL)
600 /* ...or if this is not the desired section */
601 if (strcasecmp(desired_section, partnum))
604 CC->download_fp = tmpfile();
605 if (CC->download_fp == NULL)
608 fwrite(content, length, 1, CC->download_fp);
609 fflush(CC->download_fp);
610 rewind(CC->download_fp);
612 OpenCmdResult(filename, cbtype);
618 * Load a message from disk into memory.
619 * This is used by output_message() and other fetch functions.
621 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
622 * using the CtdlMessageFree() function.
624 struct CtdlMessage *CtdlFetchMessage(long msgnum)
626 struct cdbdata *dmsgtext;
627 struct CtdlMessage *ret = NULL;
630 CIT_UBYTE field_header;
634 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
635 if (dmsgtext == NULL) {
638 mptr = dmsgtext->ptr;
640 /* Parse the three bytes that begin EVERY message on disk.
641 * The first is always 0xFF, the on-disk magic number.
642 * The second is the anonymous/public type byte.
643 * The third is the format type byte (vari, fixed, or MIME).
647 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
651 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
652 memset(ret, 0, sizeof(struct CtdlMessage));
654 ret->cm_magic = CTDLMESSAGE_MAGIC;
655 ret->cm_anon_type = *mptr++; /* Anon type byte */
656 ret->cm_format_type = *mptr++; /* Format type byte */
659 * The rest is zero or more arbitrary fields. Load them in.
660 * We're done when we encounter either a zero-length field or
661 * have just processed the 'M' (message text) field.
664 field_length = strlen(mptr);
665 if (field_length == 0)
667 field_header = *mptr++;
668 ret->cm_fields[field_header] = mallok(field_length);
669 strcpy(ret->cm_fields[field_header], mptr);
671 while (*mptr++ != 0); /* advance to next field */
673 } while ((field_length > 0) && (field_header != 'M'));
677 /* Perform "before read" hooks (aborting if any return nonzero) */
678 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
679 CtdlFreeMessage(ret);
688 * Returns 1 if the supplied pointer points to a valid Citadel message.
689 * If the pointer is NULL or the magic number check fails, returns 0.
691 int is_valid_message(struct CtdlMessage *msg) {
694 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
695 lprintf(3, "is_valid_message() -- self-check failed\n");
703 * 'Destructor' for struct CtdlMessage
705 void CtdlFreeMessage(struct CtdlMessage *msg)
709 if (is_valid_message(msg) == 0) return;
711 for (i = 0; i < 256; ++i)
712 if (msg->cm_fields[i] != NULL)
713 phree(msg->cm_fields[i]);
715 msg->cm_magic = 0; /* just in case */
722 * Get a message off disk. (return value is the message's timestamp)
725 void output_message(char *msgid, int mode, int headers_only)
733 char display_name[256];
735 struct CtdlMessage *TheMessage = NULL;
739 /* buffers needed for RFC822 translation */
747 msg_num = atol(msgid);
749 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
750 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
754 /* FIX ... small security issue
755 * We need to check to make sure the requested message is actually
756 * in the current room, and set msg_ok to 1 only if it is. This
757 * functionality is currently missing because I'm in a hurry to replace
758 * broken production code with nonbroken pre-beta code. :( -- ajc
761 cprintf("%d Message %ld is not in this room.\n",
768 * Fetch the message from disk
770 TheMessage = CtdlFetchMessage(msg_num);
771 if (TheMessage == NULL) {
772 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
776 /* Are we downloading a MIME component? */
777 if (mode == MT_DOWNLOAD) {
778 if (TheMessage->cm_format_type != 4) {
779 cprintf("%d This is not a MIME message.\n",
781 } else if (CC->download_fp != NULL) {
782 cprintf("%d You already have a download open.\n",
785 /* Parse the message text component */
786 mptr = TheMessage->cm_fields['M'];
787 mime_parser(mptr, NULL, *mime_download);
788 /* If there's no file open by this time, the requested
789 * section wasn't found, so print an error
791 if (CC->download_fp == NULL) {
792 cprintf("%d Section %s not found.\n",
793 ERROR + FILE_NOT_FOUND,
797 CtdlFreeMessage(TheMessage);
801 /* now for the user-mode message reading loops */
802 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
804 /* Tell the client which format type we're using. If this is a
805 * MIME message, *lie* about it and tell the user it's fixed-format.
807 if (mode == MT_CITADEL) {
808 if (TheMessage->cm_format_type == 4)
811 cprintf("type=%d\n", TheMessage->cm_format_type);
814 /* nhdr=yes means that we're only displaying headers, no body */
815 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
816 cprintf("nhdr=yes\n");
819 /* begin header processing loop for Citadel message format */
821 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
823 strcpy(display_name, "<unknown>");
824 if (TheMessage->cm_fields['A']) {
825 strcpy(buf, TheMessage->cm_fields['A']);
826 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
827 if (TheMessage->cm_anon_type == MES_ANON)
828 strcpy(display_name, "****");
829 else if (TheMessage->cm_anon_type == MES_AN2)
830 strcpy(display_name, "anonymous");
832 strcpy(display_name, buf);
834 && ((TheMessage->cm_anon_type == MES_ANON)
835 || (TheMessage->cm_anon_type == MES_AN2))) {
836 sprintf(&display_name[strlen(display_name)],
841 strcpy(allkeys, FORDER);
842 for (i=0; i<strlen(allkeys); ++i) {
843 k = (int) allkeys[i];
845 if (TheMessage->cm_fields[k] != NULL) {
854 TheMessage->cm_fields[k]
863 /* begin header processing loop for RFC822 transfer format */
867 strcpy(snode, NODENAME);
868 strcpy(lnode, HUMANNODE);
869 if (mode == MT_RFC822) {
870 for (i = 0; i < 256; ++i) {
871 if (TheMessage->cm_fields[i]) {
872 mptr = TheMessage->cm_fields[i];
876 } else if (i == 'P') {
877 cprintf("Path: %s\n", mptr);
878 for (a = 0; a < strlen(mptr); ++a) {
879 if (mptr[a] == '!') {
880 strcpy(mptr, &mptr[a + 1]);
886 cprintf("Subject: %s\n", mptr);
892 cprintf("X-Citadel-Room: %s\n", mptr);
896 cprintf("To: %s\n", mptr);
899 cprintf("Date: %s", asctime(localtime(&xtime)));
905 if (mode == MT_RFC822) {
906 if (!strcasecmp(snode, NODENAME)) {
909 cprintf("Message-ID: <%s@%s>\n", mid, snode);
910 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
911 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
912 cprintf("Organization: %s\n", lnode);
915 /* end header processing loop ... at this point, we're in the text */
917 mptr = TheMessage->cm_fields['M'];
919 /* Tell the client about the MIME parts in this message */
920 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
921 if (mode == MT_CITADEL) {
922 mime_parser(mptr, NULL, *list_this_part);
924 else if (mode == MT_MIME) { /* list parts only */
925 mime_parser(mptr, NULL, *list_this_part);
927 CtdlFreeMessage(TheMessage);
934 CtdlFreeMessage(TheMessage);
938 /* signify start of msg text */
939 if (mode == MT_CITADEL)
941 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
944 /* If the format type on disk is 1 (fixed-format), then we want
945 * everything to be output completely literally ... regardless of
946 * what message transfer format is in use.
948 if (TheMessage->cm_format_type == 1) {
950 while (ch = *mptr++, ch > 0) {
953 if ((ch == 10) || (strlen(buf) > 250)) {
954 cprintf("%s\n", buf);
957 buf[strlen(buf) + 1] = 0;
958 buf[strlen(buf)] = ch;
962 cprintf("%s\n", buf);
965 /* If the message on disk is format 0 (Citadel vari-format), we
966 * output using the formatter at 80 columns. This is the final output
967 * form if the transfer format is RFC822, but if the transfer format
968 * is Citadel proprietary, it'll still work, because the indentation
969 * for new paragraphs is correct and the client will reformat the
970 * message to the reader's screen width.
972 if (TheMessage->cm_format_type == 0) {
973 memfmout(80, mptr, 0);
976 /* If the message on disk is format 4 (MIME), we've gotta hand it
977 * off to the MIME parser. The client has already been told that
978 * this message is format 1 (fixed format), so the callback function
979 * we use will display those parts as-is.
981 if (TheMessage->cm_format_type == 4) {
982 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
983 memset(ma, 0, sizeof(struct ma_info));
984 mime_parser(mptr, NULL, *fixed_output);
989 CtdlFreeMessage(TheMessage);
996 * display a message (mode 0 - Citadel proprietary)
998 void cmd_msg0(char *cmdbuf)
1001 int headers_only = 0;
1003 extract(msgid, cmdbuf, 0);
1004 headers_only = extract_int(cmdbuf, 1);
1006 output_message(msgid, MT_CITADEL, headers_only);
1012 * display a message (mode 2 - RFC822)
1014 void cmd_msg2(char *cmdbuf)
1017 int headers_only = 0;
1019 extract(msgid, cmdbuf, 0);
1020 headers_only = extract_int(cmdbuf, 1);
1022 output_message(msgid, MT_RFC822, headers_only);
1028 * display a message (mode 3 - IGnet raw format - internal programs only)
1030 void cmd_msg3(char *cmdbuf)
1033 struct CtdlMessage *msg;
1036 if (CC->internal_pgm == 0) {
1037 cprintf("%d This command is for internal programs only.\n",
1042 msgnum = extract_long(cmdbuf, 0);
1043 msg = CtdlFetchMessage(msgnum);
1045 cprintf("%d Message %ld not found.\n",
1050 serialize_message(&smr, msg);
1051 CtdlFreeMessage(msg);
1054 cprintf("%d Unable to serialize message\n",
1055 ERROR+INTERNAL_ERROR);
1059 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1060 client_write(smr.ser, smr.len);
1067 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1069 void cmd_msg4(char *cmdbuf)
1073 extract(msgid, cmdbuf, 0);
1075 output_message(msgid, MT_MIME, 0);
1079 * Open a component of a MIME message as a download file
1081 void cmd_opna(char *cmdbuf)
1085 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1087 extract(msgid, cmdbuf, 0);
1088 extract(desired_section, cmdbuf, 1);
1090 output_message(msgid, MT_DOWNLOAD, 0);
1095 * Save a message pointer into a specified room
1096 * (Returns 0 for success, nonzero for failure)
1097 * roomname may be NULL to use the current room
1099 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1101 char hold_rm[ROOMNAMELEN];
1102 struct cdbdata *cdbfr;
1105 long highest_msg = 0L;
1106 struct CtdlMessage *msg = NULL;
1108 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1109 roomname, msgid, flags);
1111 strcpy(hold_rm, CC->quickroom.QRname);
1113 /* We may need to check to see if this message is real */
1114 if ( (flags & SM_VERIFY_GOODNESS)
1115 || (flags & SM_DO_REPL_CHECK)
1117 msg = CtdlFetchMessage(msgid);
1118 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1121 /* Perform replication checks if necessary */
1122 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1124 if (getroom(&CC->quickroom,
1125 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1127 lprintf(9, "No such room <%s>\n", roomname);
1128 if (msg != NULL) CtdlFreeMessage(msg);
1129 return(ERROR + ROOM_NOT_FOUND);
1132 if (ReplicationChecks(msg) != 0) {
1133 getroom(&CC->quickroom, hold_rm);
1134 if (msg != NULL) CtdlFreeMessage(msg);
1135 lprintf(9, "Did replication, and newer exists\n");
1140 /* Now the regular stuff */
1141 if (lgetroom(&CC->quickroom,
1142 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1144 lprintf(9, "No such room <%s>\n", roomname);
1145 if (msg != NULL) CtdlFreeMessage(msg);
1146 return(ERROR + ROOM_NOT_FOUND);
1149 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1150 if (cdbfr == NULL) {
1154 msglist = mallok(cdbfr->len);
1155 if (msglist == NULL)
1156 lprintf(3, "ERROR malloc msglist!\n");
1157 num_msgs = cdbfr->len / sizeof(long);
1158 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1163 /* Make sure the message doesn't already exist in this room. It
1164 * is absolutely taboo to have more than one reference to the same
1165 * message in a room.
1167 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1168 if (msglist[i] == msgid) {
1169 lputroom(&CC->quickroom); /* unlock the room */
1170 getroom(&CC->quickroom, hold_rm);
1171 if (msg != NULL) CtdlFreeMessage(msg);
1172 return(ERROR + ALREADY_EXISTS);
1176 /* Now add the new message */
1178 msglist = reallok(msglist,
1179 (num_msgs * sizeof(long)));
1181 if (msglist == NULL) {
1182 lprintf(3, "ERROR: can't realloc message list!\n");
1184 msglist[num_msgs - 1] = msgid;
1186 /* Sort the message list, so all the msgid's are in order */
1187 num_msgs = sort_msglist(msglist, num_msgs);
1189 /* Determine the highest message number */
1190 highest_msg = msglist[num_msgs - 1];
1192 /* Write it back to disk. */
1193 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1194 msglist, num_msgs * sizeof(long));
1196 /* Free up the memory we used. */
1199 /* Update the highest-message pointer and unlock the room. */
1200 CC->quickroom.QRhighest = highest_msg;
1201 lputroom(&CC->quickroom);
1202 getroom(&CC->quickroom, hold_rm);
1204 /* Bump the reference count for this message. */
1205 AdjRefCount(msgid, +1);
1207 /* Return success. */
1208 if (msg != NULL) CtdlFreeMessage(msg);
1215 * Message base operation to send a message to the master file
1216 * (returns new message number)
1218 * This is the back end for CtdlSaveMsg() and should not be directly
1219 * called by server-side modules.
1222 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1223 int generate_id, /* generate 'I' field? */
1224 FILE *save_a_copy) /* save a copy to disk? */
1231 /* Get a new message number */
1232 newmsgid = get_new_message_number();
1233 sprintf(msgidbuf, "%ld", newmsgid);
1236 msg->cm_fields['I'] = strdoop(msgidbuf);
1239 serialize_message(&smr, msg);
1242 cprintf("%d Unable to serialize message\n",
1243 ERROR+INTERNAL_ERROR);
1247 /* Write our little bundle of joy into the message base */
1248 begin_critical_section(S_MSGMAIN);
1249 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1250 smr.ser, smr.len) < 0) {
1251 lprintf(2, "Can't store message\n");
1256 end_critical_section(S_MSGMAIN);
1258 /* If the caller specified that a copy should be saved to a particular
1259 * file handle, do that now too.
1261 if (save_a_copy != NULL) {
1262 fwrite(smr.ser, smr.len, 1, save_a_copy);
1265 /* Free the memory we used for the serialized message */
1268 /* Return the *local* message ID to the caller
1269 * (even if we're storing an incoming network message)
1277 * Serialize a struct CtdlMessage into the format used on disk and network.
1279 * This function loads up a "struct ser_ret" (defined in server.h) which
1280 * contains the length of the serialized message and a pointer to the
1281 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1283 void serialize_message(struct ser_ret *ret, /* return values */
1284 struct CtdlMessage *msg) /* unserialized msg */
1288 static char *forder = FORDER;
1290 if (is_valid_message(msg) == 0) return; /* self check */
1293 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1294 ret->len = ret->len +
1295 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1297 lprintf(9, "calling malloc\n");
1298 ret->ser = mallok(ret->len);
1299 if (ret->ser == NULL) {
1305 ret->ser[1] = msg->cm_anon_type;
1306 ret->ser[2] = msg->cm_format_type;
1309 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1310 ret->ser[wlen++] = (char)forder[i];
1311 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1312 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1314 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1323 * Back end for the ReplicationChecks() function
1325 void check_repl(long msgnum) {
1326 struct CtdlMessage *msg;
1327 time_t timestamp = (-1L);
1329 lprintf(9, "check_repl() found message %ld\n", msgnum);
1330 msg = CtdlFetchMessage(msgnum);
1331 if (msg == NULL) return;
1332 if (msg->cm_fields['T'] != NULL) {
1333 timestamp = atol(msg->cm_fields['T']);
1335 CtdlFreeMessage(msg);
1337 if (timestamp > msg_repl->highest) {
1338 msg_repl->highest = timestamp; /* newer! */
1339 lprintf(9, "newer!\n");
1342 lprintf(9, "older!\n");
1344 /* Existing isn't newer? Then delete the old one(s). */
1345 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1350 * Check to see if any messages already exist which carry the same Extended ID
1354 * -> With older timestamps: delete them and return 0. Message will be saved.
1355 * -> With newer timestamps: return 1. Message save will be aborted.
1357 int ReplicationChecks(struct CtdlMessage *msg) {
1358 struct CtdlMessage *template;
1361 lprintf(9, "ReplicationChecks() started\n");
1362 /* No extended id? Don't do anything. */
1363 if (msg->cm_fields['E'] == NULL) return 0;
1364 if (strlen(msg->cm_fields['E']) == 0) return 0;
1365 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1367 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1368 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1369 msg_repl->highest = atol(msg->cm_fields['T']);
1371 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1372 memset(template, 0, sizeof(struct CtdlMessage));
1373 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1375 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1377 /* If a newer message exists with the same Extended ID, abort
1380 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1384 CtdlFreeMessage(template);
1385 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1393 * Save a message to disk
1395 void CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1396 char *rec, /* Recipient (mail) */
1397 char *force, /* force a particular room? */
1398 int mailtype, /* local or remote type */
1399 int generate_id) /* 1 = generate 'I' field */
1402 char hold_rm[ROOMNAMELEN];
1403 char actual_rm[ROOMNAMELEN];
1404 char force_room[ROOMNAMELEN];
1405 char content_type[256]; /* We have to learn this */
1406 char recipient[256];
1409 struct usersupp userbuf;
1411 struct SuppMsgInfo smi;
1412 FILE *network_fp = NULL;
1413 static int seqnum = 1;
1415 lprintf(9, "CtdlSaveMsg() called\n");
1416 if (is_valid_message(msg) == 0) return; /* self check */
1418 /* If this message has no timestamp, we take the liberty of
1419 * giving it one, right now.
1421 if (msg->cm_fields['T'] == NULL) {
1422 sprintf(aaa, "%ld", time(NULL));
1423 msg->cm_fields['T'] = strdoop(aaa);
1426 /* If this message has no path, we generate one.
1428 if (msg->cm_fields['P'] == NULL) {
1429 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1430 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1431 if (isspace(msg->cm_fields['P'][a])) {
1432 msg->cm_fields['P'][a] = ' ';
1437 strcpy(force_room, force);
1439 /* Strip non-printable characters out of the recipient name */
1440 strcpy(recipient, rec);
1441 for (a = 0; a < strlen(recipient); ++a)
1442 if (!isprint(recipient[a]))
1443 strcpy(&recipient[a], &recipient[a + 1]);
1445 /* Learn about what's inside, because it's what's inside that counts */
1447 switch (msg->cm_format_type) {
1449 strcpy(content_type, "text/x-citadel-variformat");
1452 strcpy(content_type, "text/plain");
1455 strcpy(content_type, "text/plain");
1456 /* advance past header fields */
1457 mptr = msg->cm_fields['M'];
1460 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1461 safestrncpy(content_type, mptr,
1462 sizeof(content_type));
1463 strcpy(content_type, &content_type[14]);
1464 for (a = 0; a < strlen(content_type); ++a)
1465 if ((content_type[a] == ';')
1466 || (content_type[a] == ' ')
1467 || (content_type[a] == 13)
1468 || (content_type[a] == 10))
1469 content_type[a] = 0;
1476 /* Goto the correct room */
1477 strcpy(hold_rm, CC->quickroom.QRname);
1478 strcpy(actual_rm, CC->quickroom.QRname);
1480 /* If the user is a twit, move to the twit room for posting */
1482 if (CC->usersupp.axlevel == 2) {
1483 strcpy(hold_rm, actual_rm);
1484 strcpy(actual_rm, config.c_twitroom);
1488 /* ...or if this message is destined for Aide> then go there. */
1489 if (strlen(force_room) > 0) {
1490 strcpy(actual_rm, force_room);
1493 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1494 getroom(&CC->quickroom, actual_rm);
1496 /* Perform "before save" hooks (aborting if any return nonzero) */
1497 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return;
1499 /* If this message has an Extended ID, perform replication checks */
1500 if (ReplicationChecks(msg) > 0) return;
1502 /* Network mail - send a copy to the network program. */
1503 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1504 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1505 (long) getpid(), CC->cs_pid, ++seqnum);
1506 lprintf(9, "Saving a copy to %s\n", aaa);
1507 network_fp = fopen(aaa, "ab+");
1508 if (network_fp == NULL)
1509 lprintf(2, "ERROR: %s\n", strerror(errno));
1512 /* Save it to disk */
1513 newmsgid = send_message(msg, generate_id, network_fp);
1514 if (network_fp != NULL) {
1516 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1519 if (newmsgid <= 0L) return;
1521 /* Write a supplemental message info record. This doesn't have to
1522 * be a critical section because nobody else knows about this message
1525 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1526 smi.smi_msgnum = newmsgid;
1527 smi.smi_refcount = 0;
1528 safestrncpy(smi.smi_content_type, content_type, 64);
1529 PutSuppMsgInfo(&smi);
1531 /* Now figure out where to store the pointers */
1534 /* If this is being done by the networker delivering a private
1535 * message, we want to BYPASS saving the sender's copy (because there
1536 * is no local sender; it would otherwise go to the Trashcan).
1538 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1539 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1542 /* Bump this user's messages posted counter. */
1543 lgetuser(&CC->usersupp, CC->curr_user);
1544 CC->usersupp.posted = CC->usersupp.posted + 1;
1545 lputuser(&CC->usersupp);
1547 /* If this is private, local mail, make a copy in the
1548 * recipient's mailbox and bump the reference count.
1550 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1551 if (getuser(&userbuf, recipient) == 0) {
1552 MailboxName(actual_rm, &userbuf, MAILROOM);
1553 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1557 /* Perform "after save" hooks */
1558 PerformMessageHooks(msg, EVT_AFTERSAVE);
1561 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1562 getroom(&CC->quickroom, hold_rm);
1568 * Convenience function for generating small administrative messages.
1570 void quickie_message(char *from, char *to, char *room, char *text)
1572 struct CtdlMessage *msg;
1574 msg = mallok(sizeof(struct CtdlMessage));
1575 memset(msg, 0, sizeof(struct CtdlMessage));
1576 msg->cm_magic = CTDLMESSAGE_MAGIC;
1577 msg->cm_anon_type = MES_NORMAL;
1578 msg->cm_format_type = 0;
1579 msg->cm_fields['A'] = strdoop(from);
1580 msg->cm_fields['O'] = strdoop(room);
1581 msg->cm_fields['N'] = strdoop(NODENAME);
1583 msg->cm_fields['R'] = strdoop(to);
1584 msg->cm_fields['M'] = strdoop(text);
1586 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1587 CtdlFreeMessage(msg);
1588 syslog(LOG_NOTICE, text);
1593 * Build a binary message to be saved on disk.
1596 struct CtdlMessage *make_message(
1597 struct usersupp *author, /* author's usersupp structure */
1598 char *recipient, /* NULL if it's not mail */
1599 char *room, /* room where it's going */
1600 int type, /* see MES_ types in header file */
1601 int net_type, /* see MES_ types in header file */
1602 int format_type, /* local or remote (see citadel.h) */
1603 char *fake_name) /* who we're masquerading as */
1609 size_t message_len = 0;
1610 size_t buffer_len = 0;
1612 struct CtdlMessage *msg;
1614 msg = mallok(sizeof(struct CtdlMessage));
1615 memset(msg, 0, sizeof(struct CtdlMessage));
1616 msg->cm_magic = CTDLMESSAGE_MAGIC;
1617 msg->cm_anon_type = type;
1618 msg->cm_format_type = format_type;
1620 /* Don't confuse the poor folks if it's not routed mail. */
1621 strcpy(dest_node, "");
1623 /* If net_type is MES_BINARY, split out the destination node. */
1624 if (net_type == MES_BINARY) {
1625 strcpy(dest_node, NODENAME);
1626 for (a = 0; a < strlen(recipient); ++a) {
1627 if (recipient[a] == '@') {
1629 strcpy(dest_node, &recipient[a + 1]);
1634 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1635 if (net_type == MES_INTERNET) {
1636 strcpy(dest_node, "internet");
1639 while (isspace(recipient[strlen(recipient) - 1]))
1640 recipient[strlen(recipient) - 1] = 0;
1642 sprintf(buf, "cit%ld", author->usernum); /* Path */
1643 msg->cm_fields['P'] = strdoop(buf);
1645 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1646 msg->cm_fields['T'] = strdoop(buf);
1648 if (fake_name[0]) /* author */
1649 msg->cm_fields['A'] = strdoop(fake_name);
1651 msg->cm_fields['A'] = strdoop(author->fullname);
1653 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1654 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1656 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1658 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1659 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1661 if (recipient[0] != 0)
1662 msg->cm_fields['R'] = strdoop(recipient);
1663 if (dest_node[0] != 0)
1664 msg->cm_fields['D'] = strdoop(dest_node);
1666 msg->cm_fields['M'] = mallok(4096);
1667 if (msg->cm_fields['M'] == NULL) {
1668 while (client_gets(buf), strcmp(buf, "000")) ;; /* flush */
1672 msg->cm_fields['M'][0] = 0;
1676 /* read in the lines of message text one by one */
1677 while (client_gets(buf), strcmp(buf, "000")) {
1679 /* augment the buffer if we have to */
1680 if ((message_len + strlen(buf) + 2) > buffer_len) {
1681 ptr = reallok(msg->cm_fields['M'], (buffer_len * 2) );
1682 if (ptr == NULL) { /* flush if can't allocate */
1683 while (client_gets(buf), strcmp(buf, "000")) ;;
1686 buffer_len = (buffer_len * 2);
1687 msg->cm_fields['M'] = ptr;
1691 strcat(msg->cm_fields['M'], buf);
1692 strcat(msg->cm_fields['M'], "\n");
1694 /* if we've hit the max msg length, flush the rest */
1695 if (message_len >= config.c_maxmsglen) {
1696 while (client_gets(buf), strcmp(buf, "000")) ;;
1709 * message entry - mode 0 (normal)
1711 void cmd_ent0(char *entargs)
1714 char recipient[256];
1716 int format_type = 0;
1717 char newusername[256];
1718 struct CtdlMessage *msg;
1722 struct usersupp tempUS;
1725 post = extract_int(entargs, 0);
1726 extract(recipient, entargs, 1);
1727 anon_flag = extract_int(entargs, 2);
1728 format_type = extract_int(entargs, 3);
1730 /* first check to make sure the request is valid. */
1732 if (!(CC->logged_in)) {
1733 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1736 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1737 cprintf("%d Need to be validated to enter ",
1738 ERROR + HIGHER_ACCESS_REQUIRED);
1739 cprintf("(except in %s> to sysop)\n", MAILROOM);
1742 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1743 cprintf("%d Need net privileges to enter here.\n",
1744 ERROR + HIGHER_ACCESS_REQUIRED);
1747 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1748 cprintf("%d Sorry, this is a read-only room.\n",
1749 ERROR + HIGHER_ACCESS_REQUIRED);
1756 if (CC->usersupp.axlevel < 6) {
1757 cprintf("%d You don't have permission to masquerade.\n",
1758 ERROR + HIGHER_ACCESS_REQUIRED);
1761 extract(newusername, entargs, 4);
1762 memset(CC->fake_postname, 0, 32);
1763 strcpy(CC->fake_postname, newusername);
1764 cprintf("%d Ok\n", OK);
1767 CC->cs_flags |= CS_POSTING;
1770 if (CC->quickroom.QRflags & QR_MAILBOX) {
1771 if (CC->usersupp.axlevel >= 2) {
1772 strcpy(buf, recipient);
1774 strcpy(buf, "sysop");
1775 e = alias(buf); /* alias and mail type */
1776 if ((buf[0] == 0) || (e == MES_ERROR)) {
1777 cprintf("%d Unknown address - cannot send message.\n",
1778 ERROR + NO_SUCH_USER);
1781 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1782 cprintf("%d Net privileges required for network mail.\n",
1783 ERROR + HIGHER_ACCESS_REQUIRED);
1786 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1787 && ((CC->usersupp.flags & US_INTERNET) == 0)
1788 && (!CC->internal_pgm)) {
1789 cprintf("%d You don't have access to Internet mail.\n",
1790 ERROR + HIGHER_ACCESS_REQUIRED);
1793 if (!strcasecmp(buf, "sysop")) {
1798 goto SKFALL; /* don't search local file */
1799 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1800 cprintf("%d Can't send mail to yourself!\n",
1801 ERROR + NO_SUCH_USER);
1804 /* Check to make sure the user exists; also get the correct
1805 * upper/lower casing of the name.
1807 a = getuser(&tempUS, buf);
1809 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1812 strcpy(buf, tempUS.fullname);
1815 SKFALL: b = MES_NORMAL;
1816 if (CC->quickroom.QRflags & QR_ANONONLY)
1818 if (CC->quickroom.QRflags & QR_ANONOPT) {
1822 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1825 /* If we're only checking the validity of the request, return
1826 * success without creating the message.
1829 cprintf("%d %s\n", OK, buf);
1833 cprintf("%d send message\n", SEND_LISTING);
1835 /* Read in the message from the client. */
1836 if (CC->fake_postname[0])
1837 msg = make_message(&CC->usersupp, buf,
1838 CC->quickroom.QRname, b, e, format_type,
1840 else if (CC->fake_username[0])
1841 msg = make_message(&CC->usersupp, buf,
1842 CC->quickroom.QRname, b, e, format_type,
1845 msg = make_message(&CC->usersupp, buf,
1846 CC->quickroom.QRname, b, e, format_type, "");
1849 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1850 CtdlFreeMessage(msg);
1851 CC->fake_postname[0] = '\0';
1858 * message entry - mode 3 (raw)
1860 void cmd_ent3(char *entargs)
1865 unsigned char ch, which_field;
1866 struct usersupp tempUS;
1868 struct CtdlMessage *msg;
1871 if (CC->internal_pgm == 0) {
1872 cprintf("%d This command is for internal programs only.\n",
1877 /* See if there's a recipient, but make sure it's a real one */
1878 extract(recp, entargs, 1);
1879 for (a = 0; a < strlen(recp); ++a)
1880 if (!isprint(recp[a]))
1881 strcpy(&recp[a], &recp[a + 1]);
1882 while (isspace(recp[0]))
1883 strcpy(recp, &recp[1]);
1884 while (isspace(recp[strlen(recp) - 1]))
1885 recp[strlen(recp) - 1] = 0;
1887 /* If we're in Mail, check the recipient */
1888 if (strlen(recp) > 0) {
1889 e = alias(recp); /* alias and mail type */
1890 if ((recp[0] == 0) || (e == MES_ERROR)) {
1891 cprintf("%d Unknown address - cannot send message.\n",
1892 ERROR + NO_SUCH_USER);
1895 if (e == MES_LOCAL) {
1896 a = getuser(&tempUS, recp);
1898 cprintf("%d No such user.\n",
1899 ERROR + NO_SUCH_USER);
1905 /* At this point, message has been approved. */
1906 if (extract_int(entargs, 0) == 0) {
1907 cprintf("%d OK to send\n", OK);
1911 msglen = extract_long(entargs, 2);
1912 msg = mallok(sizeof(struct CtdlMessage));
1914 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1918 memset(msg, 0, sizeof(struct CtdlMessage));
1919 tempbuf = mallok(msglen);
1920 if (tempbuf == NULL) {
1921 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1926 cprintf("%d %ld\n", SEND_BINARY, msglen);
1928 client_read(&ch, 1); /* 0xFF magic number */
1929 msg->cm_magic = CTDLMESSAGE_MAGIC;
1930 client_read(&ch, 1); /* anon type */
1931 msg->cm_anon_type = ch;
1932 client_read(&ch, 1); /* format type */
1933 msg->cm_format_type = ch;
1934 msglen = msglen - 3;
1936 while (msglen > 0) {
1937 client_read(&which_field, 1);
1941 client_read(&ch, 1);
1943 a = strlen(tempbuf);
1946 } while ( (ch != 0) && (msglen > 0) );
1947 msg->cm_fields[which_field] = strdoop(tempbuf);
1950 msg->cm_flags = CM_SKIP_HOOKS;
1951 CtdlSaveMsg(msg, recp, "", e, 0);
1952 CtdlFreeMessage(msg);
1958 * API function to delete messages which match a set of criteria
1959 * (returns the actual number of messages deleted)
1961 int CtdlDeleteMessages(char *room_name, /* which room */
1962 long dmsgnum, /* or "0" for any */
1963 char *content_type /* or NULL for any */
1967 struct quickroom qrbuf;
1968 struct cdbdata *cdbfr;
1969 long *msglist = NULL;
1972 int num_deleted = 0;
1974 struct SuppMsgInfo smi;
1976 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1977 room_name, dmsgnum, content_type);
1979 /* get room record, obtaining a lock... */
1980 if (lgetroom(&qrbuf, room_name) != 0) {
1981 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1983 return (0); /* room not found */
1985 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1987 if (cdbfr != NULL) {
1988 msglist = mallok(cdbfr->len);
1989 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1990 num_msgs = cdbfr->len / sizeof(long);
1994 for (i = 0; i < num_msgs; ++i) {
1997 /* Set/clear a bit for each criterion */
1999 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2000 delete_this |= 0x01;
2002 if (content_type == NULL) {
2003 delete_this |= 0x02;
2005 GetSuppMsgInfo(&smi, msglist[i]);
2006 if (!strcasecmp(smi.smi_content_type,
2008 delete_this |= 0x02;
2012 /* Delete message only if all bits are set */
2013 if (delete_this == 0x03) {
2014 AdjRefCount(msglist[i], -1);
2020 num_msgs = sort_msglist(msglist, num_msgs);
2021 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2022 msglist, (num_msgs * sizeof(long)));
2024 qrbuf.QRhighest = msglist[num_msgs - 1];
2028 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2029 return (num_deleted);
2035 * Delete message from current room
2037 void cmd_dele(char *delstr)
2042 getuser(&CC->usersupp, CC->curr_user);
2043 if ((CC->usersupp.axlevel < 6)
2044 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2045 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2046 && (!(CC->internal_pgm))) {
2047 cprintf("%d Higher access required.\n",
2048 ERROR + HIGHER_ACCESS_REQUIRED);
2051 delnum = extract_long(delstr, 0);
2053 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2056 cprintf("%d %d message%s deleted.\n", OK,
2057 num_deleted, ((num_deleted != 1) ? "s" : ""));
2059 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2065 * move or copy a message to another room
2067 void cmd_move(char *args)
2071 struct quickroom qtemp;
2075 num = extract_long(args, 0);
2076 extract(targ, args, 1);
2077 is_copy = extract_int(args, 2);
2079 getuser(&CC->usersupp, CC->curr_user);
2080 if ((CC->usersupp.axlevel < 6)
2081 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2082 cprintf("%d Higher access required.\n",
2083 ERROR + HIGHER_ACCESS_REQUIRED);
2087 if (getroom(&qtemp, targ) != 0) {
2088 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2092 err = CtdlSaveMsgPointerInRoom(targ, num,
2093 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2095 cprintf("%d Cannot store message in %s: error %d\n",
2100 /* Now delete the message from the source room,
2101 * if this is a 'move' rather than a 'copy' operation.
2103 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2105 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2111 * GetSuppMsgInfo() - Get the supplementary record for a message
2113 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2116 struct cdbdata *cdbsmi;
2119 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2120 smibuf->smi_msgnum = msgnum;
2121 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2123 /* Use the negative of the message number for its supp record index */
2124 TheIndex = (0L - msgnum);
2126 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2127 if (cdbsmi == NULL) {
2128 return; /* record not found; go with defaults */
2130 memcpy(smibuf, cdbsmi->ptr,
2131 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2132 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2139 * PutSuppMsgInfo() - (re)write supplementary record for a message
2141 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2145 /* Use the negative of the message number for its supp record index */
2146 TheIndex = (0L - smibuf->smi_msgnum);
2148 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2149 smibuf->smi_msgnum, smibuf->smi_refcount);
2151 cdb_store(CDB_MSGMAIN,
2152 &TheIndex, sizeof(long),
2153 smibuf, sizeof(struct SuppMsgInfo));
2158 * AdjRefCount - change the reference count for a message;
2159 * delete the message if it reaches zero
2161 void AdjRefCount(long msgnum, int incr)
2164 struct SuppMsgInfo smi;
2167 /* This is a *tight* critical section; please keep it that way, as
2168 * it may get called while nested in other critical sections.
2169 * Complicating this any further will surely cause deadlock!
2171 begin_critical_section(S_SUPPMSGMAIN);
2172 GetSuppMsgInfo(&smi, msgnum);
2173 smi.smi_refcount += incr;
2174 PutSuppMsgInfo(&smi);
2175 end_critical_section(S_SUPPMSGMAIN);
2177 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2178 msgnum, smi.smi_refcount);
2180 /* If the reference count is now zero, delete the message
2181 * (and its supplementary record as well).
2183 if (smi.smi_refcount == 0) {
2184 lprintf(9, "Deleting message <%ld>\n", msgnum);
2186 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2187 delnum = (0L - msgnum);
2188 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2193 * Write a generic object to this room
2195 * Note: this could be much more efficient. Right now we use two temporary
2196 * files, and still pull the message into memory as with all others.
2198 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2199 char *content_type, /* MIME type of this object */
2200 char *tempfilename, /* Where to fetch it from */
2201 struct usersupp *is_mailbox, /* Mailbox room? */
2202 int is_binary, /* Is encoding necessary? */
2203 int is_unique, /* Del others of this type? */
2204 unsigned int flags /* Internal save flags */
2209 char filename[PATH_MAX];
2212 struct quickroom qrbuf;
2213 char roomname[ROOMNAMELEN];
2214 struct CtdlMessage *msg;
2217 if (is_mailbox != NULL)
2218 MailboxName(roomname, is_mailbox, req_room);
2220 safestrncpy(roomname, req_room, sizeof(roomname));
2221 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2223 strcpy(filename, tmpnam(NULL));
2224 fp = fopen(filename, "w");
2228 tempfp = fopen(tempfilename, "r");
2229 if (tempfp == NULL) {
2235 fprintf(fp, "Content-type: %s\n", content_type);
2236 lprintf(9, "Content-type: %s\n", content_type);
2238 if (is_binary == 0) {
2239 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2240 while (ch = getc(tempfp), ch > 0)
2246 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2249 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2250 tempfilename, filename);
2254 lprintf(9, "Allocating\n");
2255 msg = mallok(sizeof(struct CtdlMessage));
2256 memset(msg, 0, sizeof(struct CtdlMessage));
2257 msg->cm_magic = CTDLMESSAGE_MAGIC;
2258 msg->cm_anon_type = MES_NORMAL;
2259 msg->cm_format_type = 4;
2260 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2261 msg->cm_fields['O'] = strdoop(req_room);
2262 msg->cm_fields['N'] = strdoop(config.c_nodename);
2263 msg->cm_fields['H'] = strdoop(config.c_humannode);
2264 msg->cm_flags = flags;
2266 lprintf(9, "Loading\n");
2267 fp = fopen(filename, "rb");
2268 fseek(fp, 0L, SEEK_END);
2271 msg->cm_fields['M'] = mallok(len);
2272 fread(msg->cm_fields['M'], len, 1, fp);
2276 /* Create the requested room if we have to. */
2277 if (getroom(&qrbuf, roomname) != 0) {
2278 create_room(roomname, 4, "", 0);
2280 /* If the caller specified this object as unique, delete all
2281 * other objects of this type that are currently in the room.
2284 lprintf(9, "Deleted %d other msgs of this type\n",
2285 CtdlDeleteMessages(roomname, 0L, content_type));
2287 /* Now write the data */
2288 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2289 CtdlFreeMessage(msg);