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 lprintf(9, "alias() called for <%s>\n", name);
106 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
108 fp = fopen("network/mail.aliases", "r");
110 fp = fopen("/dev/null", "r");
115 while (fgets(aaa, sizeof aaa, fp) != NULL) {
116 while (isspace(name[0]))
117 strcpy(name, &name[1]);
118 aaa[strlen(aaa) - 1] = 0;
120 for (a = 0; a < strlen(aaa); ++a) {
122 strcpy(bbb, &aaa[a + 1]);
126 if (!strcasecmp(name, aaa))
130 lprintf(7, "Mail is being forwarded to %s\n", name);
132 /* determine local or remote type, see citadel.h */
133 for (a = 0; a < strlen(name); ++a)
135 return (MES_INTERNET);
136 for (a = 0; a < strlen(name); ++a)
138 for (b = a; b < strlen(name); ++b)
140 return (MES_INTERNET);
142 for (a = 0; a < strlen(name); ++a)
146 lprintf(7, "Too many @'s in address\n");
150 for (a = 0; a < strlen(name); ++a)
152 strcpy(bbb, &name[a + 1]);
154 strcpy(bbb, &bbb[1]);
155 fp = fopen("network/mail.sysinfo", "r");
159 a = getstring(fp, aaa);
160 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
161 a = getstring(fp, aaa);
162 if (!strncmp(aaa, "use ", 4)) {
163 strcpy(bbb, &aaa[4]);
168 if (!strncmp(aaa, "uum", 3)) {
170 for (a = 0; a < strlen(bbb); ++a) {
176 while (bbb[strlen(bbb) - 1] == '_')
177 bbb[strlen(bbb) - 1] = 0;
178 sprintf(name, &aaa[4], bbb);
179 return (MES_INTERNET);
181 if (!strncmp(aaa, "bin", 3)) {
184 while (aaa[strlen(aaa) - 1] != '@')
185 aaa[strlen(aaa) - 1] = 0;
186 aaa[strlen(aaa) - 1] = 0;
187 while (aaa[strlen(aaa) - 1] == ' ')
188 aaa[strlen(aaa) - 1] = 0;
189 while (bbb[0] != '@')
190 strcpy(bbb, &bbb[1]);
191 strcpy(bbb, &bbb[1]);
192 while (bbb[0] == ' ')
193 strcpy(bbb, &bbb[1]);
194 sprintf(name, "%s @%s", aaa, bbb);
207 fp = fopen("citadel.control", "r");
208 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
214 void simple_listing(long msgnum)
216 cprintf("%ld\n", msgnum);
221 /* Determine if a given message matches the fields in a message template.
222 * Return 0 for a successful match.
224 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
227 /* If there aren't any fields in the template, all messages will
230 if (template == NULL) return(0);
232 /* Null messages are bogus. */
233 if (msg == NULL) return(1);
235 for (i='A'; i<='Z'; ++i) {
236 if (template->cm_fields[i] != NULL) {
237 if (msg->cm_fields[i] == NULL) {
240 if (strcasecmp(msg->cm_fields[i],
241 template->cm_fields[i])) return 1;
245 /* All compares succeeded: we have a match! */
253 * API function to perform an operation for each qualifying message in the
256 void CtdlForEachMessage(int mode, long ref,
258 struct CtdlMessage *compare,
259 void (*CallBack) (long msgnum))
264 struct cdbdata *cdbfr;
265 long *msglist = NULL;
268 struct SuppMsgInfo smi;
269 struct CtdlMessage *msg;
271 /* Learn about the user and room in question */
273 getuser(&CC->usersupp, CC->curr_user);
274 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
276 /* Load the message list */
277 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
279 msglist = mallok(cdbfr->len);
280 memcpy(msglist, cdbfr->ptr, cdbfr->len);
281 num_msgs = cdbfr->len / sizeof(long);
284 return; /* No messages at all? No further action. */
288 /* If the caller is looking for a specific MIME type, then filter
289 * out all messages which are not of the type requested.
292 if (content_type != NULL)
293 if (strlen(content_type) > 0)
294 for (a = 0; a < num_msgs; ++a) {
295 GetSuppMsgInfo(&smi, msglist[a]);
296 if (strcasecmp(smi.smi_content_type, content_type)) {
301 num_msgs = sort_msglist(msglist, num_msgs);
303 /* If a template was supplied, filter out the messages which
304 * don't match. (This could induce some delays!)
307 if (compare != NULL) {
308 for (a = 0; a < num_msgs; ++a) {
309 msg = CtdlFetchMessage(msglist[a]);
311 if (CtdlMsgCmp(msg, compare)) {
314 CtdlFreeMessage(msg);
322 * Now iterate through the message list, according to the
323 * criteria supplied by the caller.
326 for (a = 0; a < num_msgs; ++a) {
327 thismsg = msglist[a];
332 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
333 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
334 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
335 && (CC->usersupp.flags & US_LASTOLD))
336 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
337 || ((mode == MSGS_FIRST) && (a < ref))
338 || ((mode == MSGS_GT) && (thismsg > ref))
344 phree(msglist); /* Clean up */
350 * cmd_msgs() - get list of message #'s in this room
351 * implements the MSGS server command using CtdlForEachMessage()
353 void cmd_msgs(char *cmdbuf)
362 int with_template = 0;
363 struct CtdlMessage *template = NULL;
365 extract(which, cmdbuf, 0);
366 cm_ref = extract_int(cmdbuf, 1);
367 with_template = extract_int(cmdbuf, 2);
371 if (!strncasecmp(which, "OLD", 3))
373 else if (!strncasecmp(which, "NEW", 3))
375 else if (!strncasecmp(which, "FIRST", 5))
377 else if (!strncasecmp(which, "LAST", 4))
379 else if (!strncasecmp(which, "GT", 2))
382 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
383 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
388 cprintf("%d Send template then receive message list\n",
390 template = (struct CtdlMessage *)
391 mallok(sizeof(struct CtdlMessage));
392 memset(template, 0, sizeof(struct CtdlMessage));
393 while(client_gets(buf), strcmp(buf,"000")) {
394 extract(tfield, buf, 0);
395 extract(tvalue, buf, 1);
396 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
397 if (!strcasecmp(tfield, msgkeys[i])) {
398 template->cm_fields[i] =
405 cprintf("%d Message list...\n", LISTING_FOLLOWS);
408 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
409 if (template != NULL) CtdlFreeMessage(template);
417 * help_subst() - support routine for help file viewer
419 void help_subst(char *strbuf, char *source, char *dest)
424 while (p = pattern2(strbuf, source), (p >= 0)) {
425 strcpy(workbuf, &strbuf[p + strlen(source)]);
426 strcpy(&strbuf[p], dest);
427 strcat(strbuf, workbuf);
432 void do_help_subst(char *buffer)
436 help_subst(buffer, "^nodename", config.c_nodename);
437 help_subst(buffer, "^humannode", config.c_humannode);
438 help_subst(buffer, "^fqdn", config.c_fqdn);
439 help_subst(buffer, "^username", CC->usersupp.fullname);
440 sprintf(buf2, "%ld", CC->usersupp.usernum);
441 help_subst(buffer, "^usernum", buf2);
442 help_subst(buffer, "^sysadm", config.c_sysadm);
443 help_subst(buffer, "^variantname", CITADEL);
444 sprintf(buf2, "%d", config.c_maxsessions);
445 help_subst(buffer, "^maxsessions", buf2);
451 * memfmout() - Citadel text formatter and paginator.
452 * Although the original purpose of this routine was to format
453 * text to the reader's screen width, all we're really using it
454 * for here is to format text out to 80 columns before sending it
455 * to the client. The client software may reformat it again.
457 void memfmout(int width, char *mptr, char subst)
458 /* screen width to use */
459 /* where are we going to get our text from? */
460 /* nonzero if we should use hypertext mode */
472 c = 1; /* c is the current pos */
475 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
477 buffer[strlen(buffer) + 1] = 0;
478 buffer[strlen(buffer)] = ch;
481 if (buffer[0] == '^')
482 do_help_subst(buffer);
484 buffer[strlen(buffer) + 1] = 0;
486 strcpy(buffer, &buffer[1]);
496 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
498 if (((old == 13) || (old == 10)) && (isspace(real))) {
506 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
507 cprintf("\n%s", aaa);
516 if ((strlen(aaa) + c) > (width - 5)) {
526 if ((ch == 13) || (ch == 10)) {
527 cprintf("%s\n", aaa);
534 FMTEND: cprintf("%s\n", aaa);
540 * Callback function for mime parser that simply lists the part
542 void list_this_part(char *name, char *filename, char *partnum, char *disp,
543 void *content, char *cbtype, size_t length)
546 cprintf("part=%s|%s|%s|%s|%s|%d\n",
547 name, filename, partnum, disp, cbtype, length);
552 * Callback function for mime parser that wants to display text
554 void fixed_output(char *name, char *filename, char *partnum, char *disp,
555 void *content, char *cbtype, size_t length)
559 if (!strcasecmp(cbtype, "multipart/alternative")) {
560 strcpy(ma->prefix, partnum);
561 strcat(ma->prefix, ".");
567 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
569 && (ma->did_print == 1) ) {
570 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
576 if (!strcasecmp(cbtype, "text/plain")) {
577 client_write(content, length);
579 else if (!strcasecmp(cbtype, "text/html")) {
580 ptr = html_to_ascii(content, 80, 0);
581 client_write(ptr, strlen(ptr));
584 else if (strncasecmp(cbtype, "multipart/", 10)) {
585 cprintf("Part %s: %s (%s) (%d bytes)\n",
586 partnum, filename, cbtype, length);
592 * Callback function for mime parser that opens a section for downloading
594 void mime_download(char *name, char *filename, char *partnum, char *disp,
595 void *content, char *cbtype, size_t length)
598 /* Silently go away if there's already a download open... */
599 if (CC->download_fp != NULL)
602 /* ...or if this is not the desired section */
603 if (strcasecmp(desired_section, partnum))
606 CC->download_fp = tmpfile();
607 if (CC->download_fp == NULL)
610 fwrite(content, length, 1, CC->download_fp);
611 fflush(CC->download_fp);
612 rewind(CC->download_fp);
614 OpenCmdResult(filename, cbtype);
620 * Load a message from disk into memory.
621 * This is used by output_message() and other fetch functions.
623 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
624 * using the CtdlMessageFree() function.
626 struct CtdlMessage *CtdlFetchMessage(long msgnum)
628 struct cdbdata *dmsgtext;
629 struct CtdlMessage *ret = NULL;
632 CIT_UBYTE field_header;
636 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
637 if (dmsgtext == NULL) {
638 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
641 mptr = dmsgtext->ptr;
643 /* Parse the three bytes that begin EVERY message on disk.
644 * The first is always 0xFF, the on-disk magic number.
645 * The second is the anonymous/public type byte.
646 * The third is the format type byte (vari, fixed, or MIME).
650 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
654 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
655 memset(ret, 0, sizeof(struct CtdlMessage));
657 ret->cm_magic = CTDLMESSAGE_MAGIC;
658 ret->cm_anon_type = *mptr++; /* Anon type byte */
659 ret->cm_format_type = *mptr++; /* Format type byte */
662 * The rest is zero or more arbitrary fields. Load them in.
663 * We're done when we encounter either a zero-length field or
664 * have just processed the 'M' (message text) field.
667 field_length = strlen(mptr);
668 if (field_length == 0)
670 field_header = *mptr++;
671 ret->cm_fields[field_header] = mallok(field_length);
672 strcpy(ret->cm_fields[field_header], mptr);
674 while (*mptr++ != 0); /* advance to next field */
676 } while ((field_length > 0) && (field_header != 'M'));
680 /* Perform "before read" hooks (aborting if any return nonzero) */
681 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
682 CtdlFreeMessage(ret);
691 * Returns 1 if the supplied pointer points to a valid Citadel message.
692 * If the pointer is NULL or the magic number check fails, returns 0.
694 int is_valid_message(struct CtdlMessage *msg) {
697 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
698 lprintf(3, "is_valid_message() -- self-check failed\n");
706 * 'Destructor' for struct CtdlMessage
708 void CtdlFreeMessage(struct CtdlMessage *msg)
712 if (is_valid_message(msg) == 0) return;
714 for (i = 0; i < 256; ++i)
715 if (msg->cm_fields[i] != NULL)
716 phree(msg->cm_fields[i]);
718 msg->cm_magic = 0; /* just in case */
725 * Get a message off disk. (return value is the message's timestamp)
728 void output_message(char *msgid, int mode, int headers_only)
736 char display_name[256];
738 struct CtdlMessage *TheMessage = NULL;
742 /* buffers needed for RFC822 translation */
750 msg_num = atol(msgid);
752 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
753 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
757 /* FIX ... small security issue
758 * We need to check to make sure the requested message is actually
759 * in the current room, and set msg_ok to 1 only if it is. This
760 * functionality is currently missing because I'm in a hurry to replace
761 * broken production code with nonbroken pre-beta code. :( -- ajc
764 cprintf("%d Message %ld is not in this room.\n",
771 * Fetch the message from disk
773 TheMessage = CtdlFetchMessage(msg_num);
774 if (TheMessage == NULL) {
775 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
779 /* Are we downloading a MIME component? */
780 if (mode == MT_DOWNLOAD) {
781 if (TheMessage->cm_format_type != 4) {
782 cprintf("%d This is not a MIME message.\n",
784 } else if (CC->download_fp != NULL) {
785 cprintf("%d You already have a download open.\n",
788 /* Parse the message text component */
789 mptr = TheMessage->cm_fields['M'];
790 mime_parser(mptr, NULL, *mime_download);
791 /* If there's no file open by this time, the requested
792 * section wasn't found, so print an error
794 if (CC->download_fp == NULL) {
795 cprintf("%d Section %s not found.\n",
796 ERROR + FILE_NOT_FOUND,
800 CtdlFreeMessage(TheMessage);
804 /* now for the user-mode message reading loops */
805 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
807 /* Tell the client which format type we're using. If this is a
808 * MIME message, *lie* about it and tell the user it's fixed-format.
810 if (mode == MT_CITADEL) {
811 if (TheMessage->cm_format_type == 4)
814 cprintf("type=%d\n", TheMessage->cm_format_type);
817 /* nhdr=yes means that we're only displaying headers, no body */
818 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
819 cprintf("nhdr=yes\n");
822 /* begin header processing loop for Citadel message format */
824 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
826 strcpy(display_name, "<unknown>");
827 if (TheMessage->cm_fields['A']) {
828 strcpy(buf, TheMessage->cm_fields['A']);
829 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
830 if (TheMessage->cm_anon_type == MES_ANON)
831 strcpy(display_name, "****");
832 else if (TheMessage->cm_anon_type == MES_AN2)
833 strcpy(display_name, "anonymous");
835 strcpy(display_name, buf);
837 && ((TheMessage->cm_anon_type == MES_ANON)
838 || (TheMessage->cm_anon_type == MES_AN2))) {
839 sprintf(&display_name[strlen(display_name)],
844 strcpy(allkeys, FORDER);
845 for (i=0; i<strlen(allkeys); ++i) {
846 k = (int) allkeys[i];
848 if (TheMessage->cm_fields[k] != NULL) {
857 TheMessage->cm_fields[k]
866 /* begin header processing loop for RFC822 transfer format */
870 strcpy(snode, NODENAME);
871 strcpy(lnode, HUMANNODE);
872 if (mode == MT_RFC822) {
873 for (i = 0; i < 256; ++i) {
874 if (TheMessage->cm_fields[i]) {
875 mptr = TheMessage->cm_fields[i];
879 } else if (i == 'P') {
880 cprintf("Path: %s\n", mptr);
881 for (a = 0; a < strlen(mptr); ++a) {
882 if (mptr[a] == '!') {
883 strcpy(mptr, &mptr[a + 1]);
889 cprintf("Subject: %s\n", mptr);
895 cprintf("X-Citadel-Room: %s\n", mptr);
899 cprintf("To: %s\n", mptr);
902 cprintf("Date: %s", asctime(localtime(&xtime)));
908 if (mode == MT_RFC822) {
909 if (!strcasecmp(snode, NODENAME)) {
912 cprintf("Message-ID: <%s@%s>\n", mid, snode);
913 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
914 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
915 cprintf("Organization: %s\n", lnode);
918 /* end header processing loop ... at this point, we're in the text */
920 mptr = TheMessage->cm_fields['M'];
922 /* Tell the client about the MIME parts in this message */
923 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
924 if (mode == MT_CITADEL) {
925 mime_parser(mptr, NULL, *list_this_part);
927 else if (mode == MT_MIME) { /* list parts only */
928 mime_parser(mptr, NULL, *list_this_part);
930 CtdlFreeMessage(TheMessage);
937 CtdlFreeMessage(TheMessage);
941 /* signify start of msg text */
942 if (mode == MT_CITADEL)
944 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
947 /* If the format type on disk is 1 (fixed-format), then we want
948 * everything to be output completely literally ... regardless of
949 * what message transfer format is in use.
951 if (TheMessage->cm_format_type == 1) {
953 while (ch = *mptr++, ch > 0) {
956 if ((ch == 10) || (strlen(buf) > 250)) {
957 cprintf("%s\n", buf);
960 buf[strlen(buf) + 1] = 0;
961 buf[strlen(buf)] = ch;
965 cprintf("%s\n", buf);
968 /* If the message on disk is format 0 (Citadel vari-format), we
969 * output using the formatter at 80 columns. This is the final output
970 * form if the transfer format is RFC822, but if the transfer format
971 * is Citadel proprietary, it'll still work, because the indentation
972 * for new paragraphs is correct and the client will reformat the
973 * message to the reader's screen width.
975 if (TheMessage->cm_format_type == 0) {
976 memfmout(80, mptr, 0);
979 /* If the message on disk is format 4 (MIME), we've gotta hand it
980 * off to the MIME parser. The client has already been told that
981 * this message is format 1 (fixed format), so the callback function
982 * we use will display those parts as-is.
984 if (TheMessage->cm_format_type == 4) {
985 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
986 memset(ma, 0, sizeof(struct ma_info));
987 mime_parser(mptr, NULL, *fixed_output);
992 CtdlFreeMessage(TheMessage);
999 * display a message (mode 0 - Citadel proprietary)
1001 void cmd_msg0(char *cmdbuf)
1004 int headers_only = 0;
1006 extract(msgid, cmdbuf, 0);
1007 headers_only = extract_int(cmdbuf, 1);
1009 output_message(msgid, MT_CITADEL, headers_only);
1015 * display a message (mode 2 - RFC822)
1017 void cmd_msg2(char *cmdbuf)
1020 int headers_only = 0;
1022 extract(msgid, cmdbuf, 0);
1023 headers_only = extract_int(cmdbuf, 1);
1025 output_message(msgid, MT_RFC822, headers_only);
1031 * display a message (mode 3 - IGnet raw format - internal programs only)
1033 void cmd_msg3(char *cmdbuf)
1036 struct CtdlMessage *msg;
1039 if (CC->internal_pgm == 0) {
1040 cprintf("%d This command is for internal programs only.\n",
1045 msgnum = extract_long(cmdbuf, 0);
1046 msg = CtdlFetchMessage(msgnum);
1048 cprintf("%d Message %ld not found.\n",
1053 serialize_message(&smr, msg);
1054 CtdlFreeMessage(msg);
1057 cprintf("%d Unable to serialize message\n",
1058 ERROR+INTERNAL_ERROR);
1062 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1063 client_write(smr.ser, smr.len);
1070 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1072 void cmd_msg4(char *cmdbuf)
1076 extract(msgid, cmdbuf, 0);
1078 output_message(msgid, MT_MIME, 0);
1082 * Open a component of a MIME message as a download file
1084 void cmd_opna(char *cmdbuf)
1088 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1090 extract(msgid, cmdbuf, 0);
1091 extract(desired_section, cmdbuf, 1);
1093 output_message(msgid, MT_DOWNLOAD, 0);
1098 * Save a message pointer into a specified room
1099 * (Returns 0 for success, nonzero for failure)
1101 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1103 struct quickroom qrbuf;
1104 struct cdbdata *cdbfr;
1107 long highest_msg = 0L;
1108 struct CtdlMessage *msg = NULL;
1110 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1111 roomname, msgid, flags);
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 if (lgetroom(&qrbuf, roomname) != 0) {
1122 lprintf(9, "No such room <%s>\n", roomname);
1123 if (msg != NULL) CtdlFreeMessage(msg);
1124 return(ERROR + ROOM_NOT_FOUND);
1127 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1128 if (cdbfr == NULL) {
1132 msglist = mallok(cdbfr->len);
1133 if (msglist == NULL)
1134 lprintf(3, "ERROR malloc msglist!\n");
1135 num_msgs = cdbfr->len / sizeof(long);
1136 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1141 /* Make sure the message doesn't already exist in this room. It
1142 * is absolutely taboo to have more than one reference to the same
1143 * message in a room.
1145 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1146 if (msglist[i] == msgid) {
1147 lputroom(&qrbuf); /* unlock the room */
1148 if (msg != NULL) CtdlFreeMessage(msg);
1149 return(ERROR + ALREADY_EXISTS);
1153 /* Now add the new message */
1155 msglist = reallok(msglist,
1156 (num_msgs * sizeof(long)));
1158 if (msglist == NULL) {
1159 lprintf(3, "ERROR: can't realloc message list!\n");
1161 msglist[num_msgs - 1] = msgid;
1163 /* Sort the message list, so all the msgid's are in order */
1164 num_msgs = sort_msglist(msglist, num_msgs);
1166 /* Determine the highest message number */
1167 highest_msg = msglist[num_msgs - 1];
1169 /* Write it back to disk. */
1170 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1171 msglist, num_msgs * sizeof(long));
1173 /* Free up the memory we used. */
1176 /* Update the highest-message pointer and unlock the room. */
1177 qrbuf.QRhighest = highest_msg;
1180 /* Bump the reference count for this message. */
1181 AdjRefCount(msgid, +1);
1183 /* Return success. */
1184 lprintf(9, "CtdlSaveMsgPointerInRoom() succeeded\n");
1185 if (msg != NULL) CtdlFreeMessage(msg);
1192 * Message base operation to send a message to the master file
1193 * (returns new message number)
1195 * This is the back end for CtdlSaveMsg() and should not be directly
1196 * called by server-side modules.
1199 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1200 int generate_id, /* generate 'I' field? */
1201 FILE *save_a_copy) /* save a copy to disk? */
1208 /* Get a new message number */
1209 newmsgid = get_new_message_number();
1210 sprintf(msgidbuf, "%ld", newmsgid);
1213 msg->cm_fields['I'] = strdoop(msgidbuf);
1216 serialize_message(&smr, msg);
1219 cprintf("%d Unable to serialize message\n",
1220 ERROR+INTERNAL_ERROR);
1224 /* Write our little bundle of joy into the message base */
1225 begin_critical_section(S_MSGMAIN);
1226 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1227 smr.ser, smr.len) < 0) {
1228 lprintf(2, "Can't store message\n");
1233 end_critical_section(S_MSGMAIN);
1235 /* If the caller specified that a copy should be saved to a particular
1236 * file handle, do that now too.
1238 if (save_a_copy != NULL) {
1239 fwrite(smr.ser, smr.len, 1, save_a_copy);
1242 /* Free the memory we used for the serialized message */
1245 /* Return the *local* message ID to the caller
1246 * (even if we're storing an incoming network message)
1254 * Serialize a struct CtdlMessage into the format used on disk and network.
1256 * This function loads up a "struct ser_ret" (defined in server.h) which
1257 * contains the length of the serialized message and a pointer to the
1258 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1260 void serialize_message(struct ser_ret *ret, /* return values */
1261 struct CtdlMessage *msg) /* unserialized msg */
1265 static char *forder = FORDER;
1267 lprintf(9, "serialize_message() called\n");
1269 if (is_valid_message(msg) == 0) return; /* self check */
1271 lprintf(9, "magic number check OK.\n");
1274 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1275 ret->len = ret->len +
1276 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1278 lprintf(9, "message is %d bytes\n", ret->len);
1280 lprintf(9, "calling malloc\n");
1281 ret->ser = mallok(ret->len);
1282 if (ret->ser == NULL) {
1288 ret->ser[1] = msg->cm_anon_type;
1289 ret->ser[2] = msg->cm_format_type;
1292 lprintf(9, "stuff\n");
1293 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1294 ret->ser[wlen++] = (char)forder[i];
1295 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1296 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1298 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1300 lprintf(9, "done serializing\n");
1308 * Back end for the ReplicationChecks() function
1310 void check_repl(long msgnum) {
1311 struct CtdlMessage *msg;
1312 time_t timestamp = (-1L);
1314 lprintf(9, "check_repl() found message %ld\n", msgnum);
1315 msg = CtdlFetchMessage(msgnum);
1316 if (msg == NULL) return;
1317 if (msg->cm_fields['T'] != NULL) {
1318 timestamp = atol(msg->cm_fields['T']);
1320 CtdlFreeMessage(msg);
1322 if (timestamp > msg_repl->highest) {
1323 msg_repl->highest = timestamp; /* newer! */
1324 lprintf(9, "newer!\n");
1327 lprintf(9, "older!\n");
1329 /* Existing isn't newer? Then delete the old one(s). */
1330 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1335 * Check to see if any messages already exist which carry the same Extended ID
1339 * -> With older timestamps: delete them and return 0. Message will be saved.
1340 * -> With newer timestamps: return 1. Message save will be aborted.
1342 int ReplicationChecks(struct CtdlMessage *msg) {
1343 struct CtdlMessage *template;
1346 lprintf(9, "ReplicationChecks() started\n");
1347 /* No extended id? Don't do anything. */
1348 if (msg->cm_fields['E'] == NULL) return 0;
1349 if (strlen(msg->cm_fields['E']) == 0) return 0;
1350 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1352 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1353 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1354 msg_repl->highest = atol(msg->cm_fields['T']);
1356 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1357 memset(template, 0, sizeof(struct CtdlMessage));
1358 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1360 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1362 /* If a newer message exists with the same Extended ID, abort
1365 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1369 CtdlFreeMessage(template);
1370 lprintf(9, "Returning %d\n", abort_this);
1378 * Save a message to disk
1380 void CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1381 char *rec, /* Recipient (mail) */
1382 char *force, /* force a particular room? */
1383 int mailtype, /* local or remote type */
1384 int generate_id) /* 1 = generate 'I' field */
1387 char hold_rm[ROOMNAMELEN];
1388 char actual_rm[ROOMNAMELEN];
1389 char force_room[ROOMNAMELEN];
1390 char content_type[256]; /* We have to learn this */
1391 char recipient[256];
1394 struct usersupp userbuf;
1396 struct SuppMsgInfo smi;
1397 FILE *network_fp = NULL;
1398 static int seqnum = 1;
1400 lprintf(9, "CtdlSaveMsg() called\n");
1401 if (is_valid_message(msg) == 0) return; /* self check */
1403 /* If this message has no timestamp, we take the liberty of
1404 * giving it one, right now.
1406 if (msg->cm_fields['T'] == NULL) {
1407 sprintf(aaa, "%ld", time(NULL));
1408 msg->cm_fields['T'] = strdoop(aaa);
1411 /* If this message has no path, we generate one.
1413 if (msg->cm_fields['P'] == NULL) {
1414 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1415 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1416 if (isspace(msg->cm_fields['P'][a])) {
1417 msg->cm_fields['P'][a] = ' ';
1422 strcpy(force_room, force);
1424 /* Strip non-printable characters out of the recipient name */
1425 strcpy(recipient, rec);
1426 for (a = 0; a < strlen(recipient); ++a)
1427 if (!isprint(recipient[a]))
1428 strcpy(&recipient[a], &recipient[a + 1]);
1430 /* Learn about what's inside, because it's what's inside that counts */
1432 switch (msg->cm_format_type) {
1434 strcpy(content_type, "text/x-citadel-variformat");
1437 strcpy(content_type, "text/plain");
1440 strcpy(content_type, "text/plain");
1441 /* advance past header fields */
1442 mptr = msg->cm_fields['M'];
1445 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1446 safestrncpy(content_type, mptr,
1447 sizeof(content_type));
1448 strcpy(content_type, &content_type[14]);
1449 for (a = 0; a < strlen(content_type); ++a)
1450 if ((content_type[a] == ';')
1451 || (content_type[a] == ' ')
1452 || (content_type[a] == 13)
1453 || (content_type[a] == 10))
1454 content_type[a] = 0;
1461 /* Perform "before save" hooks (aborting if any return nonzero) */
1462 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return;
1464 /* If this message has an Extended ID, perform replication checks */
1465 if (ReplicationChecks(msg) > 0) return;
1467 /* Network mail - send a copy to the network program. */
1468 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1469 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1470 (long) getpid(), CC->cs_pid, ++seqnum);
1471 lprintf(9, "Saving a copy to %s\n", aaa);
1472 network_fp = fopen(aaa, "ab+");
1473 if (network_fp == NULL)
1474 lprintf(2, "ERROR: %s\n", strerror(errno));
1477 /* Save it to disk */
1478 newmsgid = send_message(msg, generate_id, network_fp);
1479 if (network_fp != NULL) {
1481 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1484 if (newmsgid <= 0L) return;
1486 /* Write a supplemental message info record. This doesn't have to
1487 * be a critical section because nobody else knows about this message
1490 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1491 smi.smi_msgnum = newmsgid;
1492 smi.smi_refcount = 0;
1493 safestrncpy(smi.smi_content_type, content_type, 64);
1494 PutSuppMsgInfo(&smi);
1496 /* Now figure out where to store the pointers */
1498 strcpy(actual_rm, CC->quickroom.QRname);
1500 /* If this is being done by the networker delivering a private
1501 * message, we want to BYPASS saving the sender's copy (because there
1502 * is no local sender; it would otherwise go to the Trashcan).
1504 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1505 /* If the user is a twit, move to the twit room for posting */
1507 if (CC->usersupp.axlevel == 2) {
1508 strcpy(hold_rm, actual_rm);
1509 strcpy(actual_rm, config.c_twitroom);
1512 /* ...or if this message is destined for Aide> then go there. */
1513 if (strlen(force_room) > 0) {
1514 strcpy(actual_rm, force_room);
1516 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1519 /* Bump this user's messages posted counter. */
1520 lgetuser(&CC->usersupp, CC->curr_user);
1521 CC->usersupp.posted = CC->usersupp.posted + 1;
1522 lputuser(&CC->usersupp);
1524 /* If this is private, local mail, make a copy in the
1525 * recipient's mailbox and bump the reference count.
1527 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1528 if (getuser(&userbuf, recipient) == 0) {
1529 MailboxName(actual_rm, &userbuf, MAILROOM);
1530 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1534 /* Perform "after save" hooks */
1535 PerformMessageHooks(msg, EVT_AFTERSAVE);
1541 * Convenience function for generating small administrative messages.
1543 void quickie_message(char *from, char *to, char *room, char *text)
1545 struct CtdlMessage *msg;
1547 msg = mallok(sizeof(struct CtdlMessage));
1548 memset(msg, 0, sizeof(struct CtdlMessage));
1549 msg->cm_magic = CTDLMESSAGE_MAGIC;
1550 msg->cm_anon_type = MES_NORMAL;
1551 msg->cm_format_type = 0;
1552 msg->cm_fields['A'] = strdoop(from);
1553 msg->cm_fields['O'] = strdoop(room);
1554 msg->cm_fields['N'] = strdoop(NODENAME);
1556 msg->cm_fields['R'] = strdoop(to);
1557 msg->cm_fields['M'] = strdoop(text);
1559 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1560 CtdlFreeMessage(msg);
1561 syslog(LOG_NOTICE, text);
1566 * Build a binary message to be saved on disk.
1569 struct CtdlMessage *make_message(
1570 struct usersupp *author, /* author's usersupp structure */
1571 char *recipient, /* NULL if it's not mail */
1572 char *room, /* room where it's going */
1573 int type, /* see MES_ types in header file */
1574 int net_type, /* see MES_ types in header file */
1575 int format_type, /* local or remote (see citadel.h) */
1576 char *fake_name) /* who we're masquerading as */
1582 size_t message_len = 0;
1583 size_t buffer_len = 0;
1585 struct CtdlMessage *msg;
1587 msg = mallok(sizeof(struct CtdlMessage));
1588 memset(msg, 0, sizeof(struct CtdlMessage));
1589 msg->cm_magic = CTDLMESSAGE_MAGIC;
1590 msg->cm_anon_type = type;
1591 msg->cm_format_type = format_type;
1593 /* Don't confuse the poor folks if it's not routed mail. */
1594 strcpy(dest_node, "");
1596 /* If net_type is MES_BINARY, split out the destination node. */
1597 if (net_type == MES_BINARY) {
1598 strcpy(dest_node, NODENAME);
1599 for (a = 0; a < strlen(recipient); ++a) {
1600 if (recipient[a] == '@') {
1602 strcpy(dest_node, &recipient[a + 1]);
1607 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1608 if (net_type == MES_INTERNET) {
1609 strcpy(dest_node, "internet");
1612 while (isspace(recipient[strlen(recipient) - 1]))
1613 recipient[strlen(recipient) - 1] = 0;
1615 sprintf(buf, "cit%ld", author->usernum); /* Path */
1616 msg->cm_fields['P'] = strdoop(buf);
1618 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1619 msg->cm_fields['T'] = strdoop(buf);
1621 if (fake_name[0]) /* author */
1622 msg->cm_fields['A'] = strdoop(fake_name);
1624 msg->cm_fields['A'] = strdoop(author->fullname);
1626 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1627 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1629 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1631 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1632 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1634 if (recipient[0] != 0)
1635 msg->cm_fields['R'] = strdoop(recipient);
1636 if (dest_node[0] != 0)
1637 msg->cm_fields['D'] = strdoop(dest_node);
1639 msg->cm_fields['M'] = mallok(4096);
1640 if (msg->cm_fields['M'] == NULL) {
1641 while (client_gets(buf), strcmp(buf, "000")) ;; /* flush */
1645 msg->cm_fields['M'][0] = 0;
1649 /* read in the lines of message text one by one */
1650 while (client_gets(buf), strcmp(buf, "000")) {
1652 /* augment the buffer if we have to */
1653 if ((message_len + strlen(buf) + 2) > buffer_len) {
1654 ptr = reallok(msg->cm_fields['M'], (buffer_len * 2) );
1655 if (ptr == NULL) { /* flush if can't allocate */
1656 while (client_gets(buf), strcmp(buf, "000")) ;;
1659 buffer_len = (buffer_len * 2);
1660 msg->cm_fields['M'] = ptr;
1664 strcat(msg->cm_fields['M'], buf);
1665 strcat(msg->cm_fields['M'], "\n");
1667 /* if we've hit the max msg length, flush the rest */
1668 if (message_len >= config.c_maxmsglen) {
1669 while (client_gets(buf), strcmp(buf, "000")) ;;
1682 * message entry - mode 0 (normal)
1684 void cmd_ent0(char *entargs)
1687 char recipient[256];
1689 int format_type = 0;
1690 char newusername[256];
1691 struct CtdlMessage *msg;
1695 struct usersupp tempUS;
1698 post = extract_int(entargs, 0);
1699 extract(recipient, entargs, 1);
1700 anon_flag = extract_int(entargs, 2);
1701 format_type = extract_int(entargs, 3);
1703 /* first check to make sure the request is valid. */
1705 if (!(CC->logged_in)) {
1706 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1709 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1710 cprintf("%d Need to be validated to enter ",
1711 ERROR + HIGHER_ACCESS_REQUIRED);
1712 cprintf("(except in %s> to sysop)\n", MAILROOM);
1715 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1716 cprintf("%d Need net privileges to enter here.\n",
1717 ERROR + HIGHER_ACCESS_REQUIRED);
1720 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1721 cprintf("%d Sorry, this is a read-only room.\n",
1722 ERROR + HIGHER_ACCESS_REQUIRED);
1729 if (CC->usersupp.axlevel < 6) {
1730 cprintf("%d You don't have permission to masquerade.\n",
1731 ERROR + HIGHER_ACCESS_REQUIRED);
1734 extract(newusername, entargs, 4);
1735 memset(CC->fake_postname, 0, 32);
1736 strcpy(CC->fake_postname, newusername);
1737 cprintf("%d Ok\n", OK);
1740 CC->cs_flags |= CS_POSTING;
1743 if (CC->quickroom.QRflags & QR_MAILBOX) {
1744 if (CC->usersupp.axlevel >= 2) {
1745 strcpy(buf, recipient);
1747 strcpy(buf, "sysop");
1748 e = alias(buf); /* alias and mail type */
1749 if ((buf[0] == 0) || (e == MES_ERROR)) {
1750 cprintf("%d Unknown address - cannot send message.\n",
1751 ERROR + NO_SUCH_USER);
1754 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1755 cprintf("%d Net privileges required for network mail.\n",
1756 ERROR + HIGHER_ACCESS_REQUIRED);
1759 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1760 && ((CC->usersupp.flags & US_INTERNET) == 0)
1761 && (!CC->internal_pgm)) {
1762 cprintf("%d You don't have access to Internet mail.\n",
1763 ERROR + HIGHER_ACCESS_REQUIRED);
1766 if (!strcasecmp(buf, "sysop")) {
1771 goto SKFALL; /* don't search local file */
1772 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1773 cprintf("%d Can't send mail to yourself!\n",
1774 ERROR + NO_SUCH_USER);
1777 /* Check to make sure the user exists; also get the correct
1778 * upper/lower casing of the name.
1780 a = getuser(&tempUS, buf);
1782 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1785 strcpy(buf, tempUS.fullname);
1788 SKFALL: b = MES_NORMAL;
1789 if (CC->quickroom.QRflags & QR_ANONONLY)
1791 if (CC->quickroom.QRflags & QR_ANONOPT) {
1795 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1798 /* If we're only checking the validity of the request, return
1799 * success without creating the message.
1802 cprintf("%d %s\n", OK, buf);
1806 cprintf("%d send message\n", SEND_LISTING);
1808 /* Read in the message from the client. */
1809 if (CC->fake_postname[0])
1810 msg = make_message(&CC->usersupp, buf,
1811 CC->quickroom.QRname, b, e, format_type,
1813 else if (CC->fake_username[0])
1814 msg = make_message(&CC->usersupp, buf,
1815 CC->quickroom.QRname, b, e, format_type,
1818 msg = make_message(&CC->usersupp, buf,
1819 CC->quickroom.QRname, b, e, format_type, "");
1822 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1823 CtdlFreeMessage(msg);
1824 CC->fake_postname[0] = '\0';
1831 * message entry - mode 3 (raw)
1833 void cmd_ent3(char *entargs)
1838 unsigned char ch, which_field;
1839 struct usersupp tempUS;
1841 struct CtdlMessage *msg;
1844 if (CC->internal_pgm == 0) {
1845 cprintf("%d This command is for internal programs only.\n",
1850 /* See if there's a recipient, but make sure it's a real one */
1851 extract(recp, entargs, 1);
1852 for (a = 0; a < strlen(recp); ++a)
1853 if (!isprint(recp[a]))
1854 strcpy(&recp[a], &recp[a + 1]);
1855 while (isspace(recp[0]))
1856 strcpy(recp, &recp[1]);
1857 while (isspace(recp[strlen(recp) - 1]))
1858 recp[strlen(recp) - 1] = 0;
1860 /* If we're in Mail, check the recipient */
1861 if (strlen(recp) > 0) {
1862 e = alias(recp); /* alias and mail type */
1863 if ((recp[0] == 0) || (e == MES_ERROR)) {
1864 cprintf("%d Unknown address - cannot send message.\n",
1865 ERROR + NO_SUCH_USER);
1868 if (e == MES_LOCAL) {
1869 a = getuser(&tempUS, recp);
1871 cprintf("%d No such user.\n",
1872 ERROR + NO_SUCH_USER);
1878 /* At this point, message has been approved. */
1879 if (extract_int(entargs, 0) == 0) {
1880 cprintf("%d OK to send\n", OK);
1884 msglen = extract_long(entargs, 2);
1885 msg = mallok(sizeof(struct CtdlMessage));
1887 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1891 memset(msg, 0, sizeof(struct CtdlMessage));
1892 tempbuf = mallok(msglen);
1893 if (tempbuf == NULL) {
1894 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1899 cprintf("%d %ld\n", SEND_BINARY, msglen);
1901 client_read(&ch, 1); /* 0xFF magic number */
1902 msg->cm_magic = CTDLMESSAGE_MAGIC;
1903 client_read(&ch, 1); /* anon type */
1904 msg->cm_anon_type = ch;
1905 client_read(&ch, 1); /* format type */
1906 msg->cm_format_type = ch;
1907 msglen = msglen - 3;
1909 while (msglen > 0) {
1910 client_read(&which_field, 1);
1914 client_read(&ch, 1);
1916 a = strlen(tempbuf);
1919 } while ( (ch != 0) && (msglen > 0) );
1920 msg->cm_fields[which_field] = strdoop(tempbuf);
1923 msg->cm_flags = CM_SKIP_HOOKS;
1924 CtdlSaveMsg(msg, recp, "", e, 0);
1925 CtdlFreeMessage(msg);
1931 * API function to delete messages which match a set of criteria
1932 * (returns the actual number of messages deleted)
1934 int CtdlDeleteMessages(char *room_name, /* which room */
1935 long dmsgnum, /* or "0" for any */
1936 char *content_type /* or NULL for any */
1940 struct quickroom qrbuf;
1941 struct cdbdata *cdbfr;
1942 long *msglist = NULL;
1945 int num_deleted = 0;
1947 struct SuppMsgInfo smi;
1949 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1950 room_name, dmsgnum, content_type);
1952 /* get room record, obtaining a lock... */
1953 if (lgetroom(&qrbuf, room_name) != 0) {
1954 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1956 return (0); /* room not found */
1958 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1960 if (cdbfr != NULL) {
1961 msglist = mallok(cdbfr->len);
1962 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1963 num_msgs = cdbfr->len / sizeof(long);
1967 for (i = 0; i < num_msgs; ++i) {
1970 /* Set/clear a bit for each criterion */
1972 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1973 delete_this |= 0x01;
1975 if (content_type == NULL) {
1976 delete_this |= 0x02;
1978 GetSuppMsgInfo(&smi, msglist[i]);
1979 if (!strcasecmp(smi.smi_content_type,
1981 delete_this |= 0x02;
1985 /* Delete message only if all bits are set */
1986 if (delete_this == 0x03) {
1987 AdjRefCount(msglist[i], -1);
1993 num_msgs = sort_msglist(msglist, num_msgs);
1994 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1995 msglist, (num_msgs * sizeof(long)));
1997 qrbuf.QRhighest = msglist[num_msgs - 1];
2001 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2002 return (num_deleted);
2008 * Delete message from current room
2010 void cmd_dele(char *delstr)
2015 getuser(&CC->usersupp, CC->curr_user);
2016 if ((CC->usersupp.axlevel < 6)
2017 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2018 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2019 && (!(CC->internal_pgm))) {
2020 cprintf("%d Higher access required.\n",
2021 ERROR + HIGHER_ACCESS_REQUIRED);
2024 delnum = extract_long(delstr, 0);
2026 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2029 cprintf("%d %d message%s deleted.\n", OK,
2030 num_deleted, ((num_deleted != 1) ? "s" : ""));
2032 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2038 * move or copy a message to another room
2040 void cmd_move(char *args)
2044 struct quickroom qtemp;
2048 num = extract_long(args, 0);
2049 extract(targ, args, 1);
2050 is_copy = extract_int(args, 2);
2052 getuser(&CC->usersupp, CC->curr_user);
2053 if ((CC->usersupp.axlevel < 6)
2054 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2055 cprintf("%d Higher access required.\n",
2056 ERROR + HIGHER_ACCESS_REQUIRED);
2060 if (getroom(&qtemp, targ) != 0) {
2061 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2065 err = CtdlSaveMsgPointerInRoom(targ, num, SM_VERIFY_GOODNESS);
2067 cprintf("%d Cannot store message in %s: error %d\n",
2072 /* Now delete the message from the source room,
2073 * if this is a 'move' rather than a 'copy' operation.
2075 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2077 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2083 * GetSuppMsgInfo() - Get the supplementary record for a message
2085 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2088 struct cdbdata *cdbsmi;
2091 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2092 smibuf->smi_msgnum = msgnum;
2093 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2095 /* Use the negative of the message number for its supp record index */
2096 TheIndex = (0L - msgnum);
2098 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2099 if (cdbsmi == NULL) {
2100 return; /* record not found; go with defaults */
2102 memcpy(smibuf, cdbsmi->ptr,
2103 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2104 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2111 * PutSuppMsgInfo() - (re)write supplementary record for a message
2113 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2117 /* Use the negative of the message number for its supp record index */
2118 TheIndex = (0L - smibuf->smi_msgnum);
2120 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2121 smibuf->smi_msgnum, smibuf->smi_refcount);
2123 cdb_store(CDB_MSGMAIN,
2124 &TheIndex, sizeof(long),
2125 smibuf, sizeof(struct SuppMsgInfo));
2130 * AdjRefCount - change the reference count for a message;
2131 * delete the message if it reaches zero
2133 void AdjRefCount(long msgnum, int incr)
2136 struct SuppMsgInfo smi;
2139 /* This is a *tight* critical section; please keep it that way, as
2140 * it may get called while nested in other critical sections.
2141 * Complicating this any further will surely cause deadlock!
2143 begin_critical_section(S_SUPPMSGMAIN);
2144 GetSuppMsgInfo(&smi, msgnum);
2145 smi.smi_refcount += incr;
2146 PutSuppMsgInfo(&smi);
2147 end_critical_section(S_SUPPMSGMAIN);
2149 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2150 msgnum, smi.smi_refcount);
2152 /* If the reference count is now zero, delete the message
2153 * (and its supplementary record as well).
2155 if (smi.smi_refcount == 0) {
2156 lprintf(9, "Deleting message <%ld>\n", msgnum);
2158 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2159 delnum = (0L - msgnum);
2160 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2165 * Write a generic object to this room
2167 * Note: this could be much more efficient. Right now we use two temporary
2168 * files, and still pull the message into memory as with all others.
2170 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2171 char *content_type, /* MIME type of this object */
2172 char *tempfilename, /* Where to fetch it from */
2173 struct usersupp *is_mailbox, /* Mailbox room? */
2174 int is_binary, /* Is encoding necessary? */
2175 int is_unique, /* Del others of this type? */
2176 unsigned int flags /* Internal save flags */
2181 char filename[PATH_MAX];
2184 struct quickroom qrbuf;
2185 char roomname[ROOMNAMELEN];
2186 struct CtdlMessage *msg;
2189 if (is_mailbox != NULL)
2190 MailboxName(roomname, is_mailbox, req_room);
2192 safestrncpy(roomname, req_room, sizeof(roomname));
2193 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2195 strcpy(filename, tmpnam(NULL));
2196 fp = fopen(filename, "w");
2200 tempfp = fopen(tempfilename, "r");
2201 if (tempfp == NULL) {
2207 fprintf(fp, "Content-type: %s\n", content_type);
2208 lprintf(9, "Content-type: %s\n", content_type);
2210 if (is_binary == 0) {
2211 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2212 while (ch = getc(tempfp), ch > 0)
2218 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2221 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2222 tempfilename, filename);
2226 lprintf(9, "Allocating\n");
2227 msg = mallok(sizeof(struct CtdlMessage));
2228 memset(msg, 0, sizeof(struct CtdlMessage));
2229 msg->cm_magic = CTDLMESSAGE_MAGIC;
2230 msg->cm_anon_type = MES_NORMAL;
2231 msg->cm_format_type = 4;
2232 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2233 msg->cm_fields['O'] = strdoop(req_room);
2234 msg->cm_fields['N'] = strdoop(config.c_nodename);
2235 msg->cm_fields['H'] = strdoop(config.c_humannode);
2236 msg->cm_flags = flags;
2238 lprintf(9, "Loading\n");
2239 fp = fopen(filename, "rb");
2240 fseek(fp, 0L, SEEK_END);
2243 msg->cm_fields['M'] = mallok(len);
2244 fread(msg->cm_fields['M'], len, 1, fp);
2248 /* Create the requested room if we have to. */
2249 if (getroom(&qrbuf, roomname) != 0) {
2250 create_room(roomname, 4, "", 0);
2252 /* If the caller specified this object as unique, delete all
2253 * other objects of this type that are currently in the room.
2256 lprintf(9, "Deleted %d other msgs of this type\n",
2257 CtdlDeleteMessages(roomname, 0L, content_type));
2259 /* Now write the data */
2260 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2261 CtdlFreeMessage(msg);