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))
35 extern struct config config;
39 * This function is self explanatory.
40 * (What can I say, I'm in a weird mood today...)
42 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
46 for (i = 0; i < strlen(name); ++i)
49 if (isspace(name[i - 1])) {
50 strcpy(&name[i - 1], &name[i]);
53 while (isspace(name[i + 1])) {
54 strcpy(&name[i + 1], &name[i + 2]);
61 * Aliasing for network mail.
62 * (Error messages have been commented out, because this is a server.)
65 { /* process alias and routing info for mail */
68 char aaa[300], bbb[300];
70 lprintf(9, "alias() called for <%s>\n", name);
72 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
74 fp = fopen("network/mail.aliases", "r");
76 fp = fopen("/dev/null", "r");
81 while (fgets(aaa, sizeof aaa, fp) != NULL) {
82 while (isspace(name[0]))
83 strcpy(name, &name[1]);
84 aaa[strlen(aaa) - 1] = 0;
86 for (a = 0; a < strlen(aaa); ++a) {
88 strcpy(bbb, &aaa[a + 1]);
92 if (!strcasecmp(name, aaa))
96 lprintf(7, "Mail is being forwarded to %s\n", name);
98 /* determine local or remote type, see citadel.h */
99 for (a = 0; a < strlen(name); ++a)
101 return (MES_INTERNET);
102 for (a = 0; a < strlen(name); ++a)
104 for (b = a; b < strlen(name); ++b)
106 return (MES_INTERNET);
108 for (a = 0; a < strlen(name); ++a)
112 lprintf(7, "Too many @'s in address\n");
116 for (a = 0; a < strlen(name); ++a)
118 strcpy(bbb, &name[a + 1]);
120 strcpy(bbb, &bbb[1]);
121 fp = fopen("network/mail.sysinfo", "r");
125 a = getstring(fp, aaa);
126 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
127 a = getstring(fp, aaa);
128 if (!strncmp(aaa, "use ", 4)) {
129 strcpy(bbb, &aaa[4]);
134 if (!strncmp(aaa, "uum", 3)) {
136 for (a = 0; a < strlen(bbb); ++a) {
142 while (bbb[strlen(bbb) - 1] == '_')
143 bbb[strlen(bbb) - 1] = 0;
144 sprintf(name, &aaa[4], bbb);
145 return (MES_INTERNET);
147 if (!strncmp(aaa, "bin", 3)) {
150 while (aaa[strlen(aaa) - 1] != '@')
151 aaa[strlen(aaa) - 1] = 0;
152 aaa[strlen(aaa) - 1] = 0;
153 while (aaa[strlen(aaa) - 1] == ' ')
154 aaa[strlen(aaa) - 1] = 0;
155 while (bbb[0] != '@')
156 strcpy(bbb, &bbb[1]);
157 strcpy(bbb, &bbb[1]);
158 while (bbb[0] == ' ')
159 strcpy(bbb, &bbb[1]);
160 sprintf(name, "%s @%s", aaa, bbb);
173 fp = fopen("citadel.control", "r");
174 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
180 void simple_listing(long msgnum)
182 cprintf("%ld\n", msgnum);
187 * API function to perform an operation for each qualifying message in the
190 void CtdlForEachMessage(int mode, long ref,
192 void (*CallBack) (long msgnum))
197 struct cdbdata *cdbfr;
198 long *msglist = NULL;
201 struct SuppMsgInfo smi;
203 /* Learn about the user and room in question */
205 getuser(&CC->usersupp, CC->curr_user);
206 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
208 /* Load the message list */
209 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
211 msglist = mallok(cdbfr->len);
212 memcpy(msglist, cdbfr->ptr, cdbfr->len);
213 num_msgs = cdbfr->len / sizeof(long);
216 return; /* No messages at all? No further action. */
220 /* If the caller is looking for a specific MIME type, then filter
221 * out all messages which are not of the type requested.
224 if (content_type != NULL)
225 if (strlen(content_type) > 0)
226 for (a = 0; a < num_msgs; ++a) {
227 GetSuppMsgInfo(&smi, msglist[a]);
228 if (strcasecmp(smi.smi_content_type, content_type)) {
233 num_msgs = sort_msglist(msglist, num_msgs);
236 * Now iterate through the message list, according to the
237 * criteria supplied by the caller.
240 for (a = 0; a < num_msgs; ++a) {
241 thismsg = msglist[a];
242 lprintf(9, "Iterating through <%ld>\n", thismsg);
247 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
248 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
249 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
250 && (CC->usersupp.flags & US_LASTOLD))
251 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
252 || ((mode == MSGS_FIRST) && (a < ref))
253 || ((mode == MSGS_GT) && (thismsg > ref))
256 lprintf(9, "Issuing callback for <%ld>\n", thismsg);
260 phree(msglist); /* Clean up */
266 * cmd_msgs() - get list of message #'s in this room
267 * implements the MSGS server command using CtdlForEachMessage()
269 void cmd_msgs(char *cmdbuf)
275 extract(which, cmdbuf, 0);
276 cm_ref = extract_int(cmdbuf, 1);
280 if (!strncasecmp(which, "OLD", 3))
282 else if (!strncasecmp(which, "NEW", 3))
284 else if (!strncasecmp(which, "FIRST", 5))
286 else if (!strncasecmp(which, "LAST", 4))
288 else if (!strncasecmp(which, "GT", 2))
291 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
292 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
295 cprintf("%d Message list...\n", LISTING_FOLLOWS);
296 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
304 * help_subst() - support routine for help file viewer
306 void help_subst(char *strbuf, char *source, char *dest)
311 while (p = pattern2(strbuf, source), (p >= 0)) {
312 strcpy(workbuf, &strbuf[p + strlen(source)]);
313 strcpy(&strbuf[p], dest);
314 strcat(strbuf, workbuf);
319 void do_help_subst(char *buffer)
323 help_subst(buffer, "^nodename", config.c_nodename);
324 help_subst(buffer, "^humannode", config.c_humannode);
325 help_subst(buffer, "^fqdn", config.c_fqdn);
326 help_subst(buffer, "^username", CC->usersupp.fullname);
327 sprintf(buf2, "%ld", CC->usersupp.usernum);
328 help_subst(buffer, "^usernum", buf2);
329 help_subst(buffer, "^sysadm", config.c_sysadm);
330 help_subst(buffer, "^variantname", CITADEL);
331 sprintf(buf2, "%d", config.c_maxsessions);
332 help_subst(buffer, "^maxsessions", buf2);
338 * memfmout() - Citadel text formatter and paginator.
339 * Although the original purpose of this routine was to format
340 * text to the reader's screen width, all we're really using it
341 * for here is to format text out to 80 columns before sending it
342 * to the client. The client software may reformat it again.
344 void memfmout(int width, char *mptr, char subst)
345 /* screen width to use */
346 /* where are we going to get our text from? */
347 /* nonzero if we should use hypertext mode */
359 c = 1; /* c is the current pos */
362 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
364 buffer[strlen(buffer) + 1] = 0;
365 buffer[strlen(buffer)] = ch;
368 if (buffer[0] == '^')
369 do_help_subst(buffer);
371 buffer[strlen(buffer) + 1] = 0;
373 strcpy(buffer, &buffer[1]);
382 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
384 if (((old == 13) || (old == 10)) && (isspace(real))) {
392 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
393 cprintf("\n%s", aaa);
402 if ((strlen(aaa) + c) > (width - 5)) {
412 if ((ch == 13) || (ch == 10)) {
413 cprintf("%s\n", aaa);
420 FMTEND:cprintf("%s\n", aaa);
426 * Callback function for mime parser that simply lists the part
428 void list_this_part(char *name, char *filename, char *partnum, char *disp,
429 void *content, char *cbtype, size_t length)
432 cprintf("part=%s|%s|%s|%s|%s|%d\n",
433 name, filename, partnum, disp, cbtype, length);
438 * Callback function for mime parser that wants to display text
440 void fixed_output(char *name, char *filename, char *partnum, char *disp,
441 void *content, char *cbtype, size_t length)
445 if (!strcasecmp(cbtype, "text/plain")) {
446 client_write(content, length);
448 else if (!strcasecmp(cbtype, "text/html")) {
449 ptr = html_to_ascii(content, 80, 1);
450 client_write(ptr, strlen(ptr));
454 cprintf("Part %s: %s (%s) (%d bytes)\n",
455 partnum, filename, cbtype, length);
461 * Callback function for mime parser that opens a section for downloading
463 void mime_download(char *name, char *filename, char *partnum, char *disp,
464 void *content, char *cbtype, size_t length)
467 /* Silently go away if there's already a download open... */
468 if (CC->download_fp != NULL)
471 /* ...or if this is not the desired section */
472 if (strcasecmp(desired_section, partnum))
475 CC->download_fp = tmpfile();
476 if (CC->download_fp == NULL)
479 fwrite(content, length, 1, CC->download_fp);
480 fflush(CC->download_fp);
481 rewind(CC->download_fp);
483 OpenCmdResult(filename, cbtype);
489 * Load a message from disk into memory.
490 * This is used by output_message() and other fetch functions.
492 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
493 * using the CtdlMessageFree() function.
495 struct CtdlMessage *CtdlFetchMessage(long msgnum)
497 struct cdbdata *dmsgtext;
498 struct CtdlMessage *ret = NULL;
501 CIT_UBYTE field_header;
505 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
506 if (dmsgtext == NULL) {
507 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
510 mptr = dmsgtext->ptr;
512 /* Parse the three bytes that begin EVERY message on disk.
513 * The first is always 0xFF, the on-disk magic number.
514 * The second is the anonymous/public type byte.
515 * The third is the format type byte (vari, fixed, or MIME).
519 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
523 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
524 memset(ret, 0, sizeof(struct CtdlMessage));
526 ret->cm_magic = CTDLMESSAGE_MAGIC;
527 ret->cm_anon_type = *mptr++; /* Anon type byte */
528 ret->cm_format_type = *mptr++; /* Format type byte */
531 * The rest is zero or more arbitrary fields. Load them in.
532 * We're done when we encounter either a zero-length field or
533 * have just processed the 'M' (message text) field.
536 field_length = strlen(mptr);
537 if (field_length == 0)
539 field_header = *mptr++;
540 ret->cm_fields[field_header] = mallok(field_length);
541 strcpy(ret->cm_fields[field_header], mptr);
543 while (*mptr++ != 0); /* advance to next field */
545 } while ((field_length > 0) && (field_header != 'M'));
552 * 'Destructor' for struct CtdlMessage
554 void CtdlFreeMessage(struct CtdlMessage *msg)
560 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
561 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
564 for (i = 0; i < 256; ++i)
565 if (msg->cm_fields[i] != NULL)
566 phree(msg->cm_fields[i]);
574 * Get a message off disk. (return value is the message's timestamp)
577 void output_message(char *msgid, int mode, int headers_only)
585 struct CtdlMessage *TheMessage = NULL;
589 /* buffers needed for RFC822 translation */
597 msg_num = atol(msgid);
599 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
600 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
603 /* FIX ... small security issue
604 * We need to check to make sure the requested message is actually
605 * in the current room, and set msg_ok to 1 only if it is. This
606 * functionality is currently missing because I'm in a hurry to replace
607 * broken production code with nonbroken pre-beta code. :( -- ajc
610 cprintf("%d Message %ld is not in this room.\n",
617 * Fetch the message from disk
619 TheMessage = CtdlFetchMessage(msg_num);
620 if (TheMessage == NULL) {
621 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
625 /* Are we downloading a MIME component? */
626 if (mode == MT_DOWNLOAD) {
627 if (TheMessage->cm_format_type != 4) {
628 cprintf("%d This is not a MIME message.\n",
630 } else if (CC->download_fp != NULL) {
631 cprintf("%d You already have a download open.\n",
634 /* Parse the message text component */
635 mptr = TheMessage->cm_fields['M'];
636 mime_parser(mptr, NULL, *mime_download);
637 /* If there's no file open by this time, the requested
638 * section wasn't found, so print an error
640 if (CC->download_fp == NULL) {
641 cprintf("%d Section %s not found.\n",
642 ERROR + FILE_NOT_FOUND,
646 CtdlFreeMessage(TheMessage);
650 /* now for the user-mode message reading loops */
651 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
653 if (mode == MT_CITADEL)
654 cprintf("type=%d\n", TheMessage->cm_format_type);
656 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
657 cprintf("nhdr=yes\n");
660 /* begin header processing loop for Citadel message format */
662 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
664 if (TheMessage->cm_fields['P']) {
665 cprintf("path=%s\n", TheMessage->cm_fields['P']);
667 if (TheMessage->cm_fields['I']) {
668 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
670 if (TheMessage->cm_fields['T']) {
671 cprintf("time=%s\n", TheMessage->cm_fields['T']);
673 if (TheMessage->cm_fields['A']) {
674 strcpy(buf, TheMessage->cm_fields['A']);
675 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
676 if (TheMessage->cm_anon_type == MES_ANON)
677 cprintf("from=****");
678 else if (TheMessage->cm_anon_type == MES_AN2)
679 cprintf("from=anonymous");
681 cprintf("from=%s", buf);
683 && ((TheMessage->cm_anon_type == MES_ANON)
684 || (TheMessage->cm_anon_type == MES_AN2))) {
685 cprintf(" [%s]", buf);
689 if (TheMessage->cm_fields['O']) {
690 cprintf("room=%s\n", TheMessage->cm_fields['O']);
692 if (TheMessage->cm_fields['N']) {
693 cprintf("node=%s\n", TheMessage->cm_fields['N']);
695 if (TheMessage->cm_fields['H']) {
696 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
698 if (TheMessage->cm_fields['R']) {
699 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
701 if (TheMessage->cm_fields['U']) {
702 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
706 /* begin header processing loop for RFC822 transfer format */
710 strcpy(snode, NODENAME);
711 strcpy(lnode, HUMANNODE);
712 if (mode == MT_RFC822) {
713 for (i = 0; i < 256; ++i) {
714 if (TheMessage->cm_fields[i]) {
715 mptr = TheMessage->cm_fields[i];
719 } else if (i == 'P') {
720 cprintf("Path: %s\n", mptr);
721 for (a = 0; a < strlen(mptr); ++a) {
722 if (mptr[a] == '!') {
723 strcpy(mptr, &mptr[a + 1]);
729 cprintf("Subject: %s\n", mptr);
735 cprintf("X-Citadel-Room: %s\n", mptr);
739 cprintf("To: %s\n", mptr);
742 cprintf("Date: %s", asctime(localtime(&xtime)));
748 if (mode == MT_RFC822) {
749 if (!strcasecmp(snode, NODENAME)) {
752 cprintf("Message-ID: <%s@%s>\n", mid, snode);
753 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
754 cprintf("From: %s@%s (%s)\n",
755 suser, snode, luser);
756 cprintf("Organization: %s\n", lnode);
759 /* end header processing loop ... at this point, we're in the text */
761 mptr = TheMessage->cm_fields['M'];
763 /* do some sort of MIME output */
764 if (TheMessage->cm_format_type == 4) {
765 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
766 mime_parser(mptr, NULL, *list_this_part);
768 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
770 CtdlFreeMessage(TheMessage);
777 CtdlFreeMessage(TheMessage);
781 /* signify start of msg text */
782 if (mode == MT_CITADEL)
784 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
787 /* If the format type on disk is 1 (fixed-format), then we want
788 * everything to be output completely literally ... regardless of
789 * what message transfer format is in use.
791 if (TheMessage->cm_format_type == 1) {
793 while (ch = *mptr++, ch > 0) {
796 if ((ch == 10) || (strlen(buf) > 250)) {
797 cprintf("%s\n", buf);
800 buf[strlen(buf) + 1] = 0;
801 buf[strlen(buf)] = ch;
805 cprintf("%s\n", buf);
808 /* If the message on disk is format 0 (Citadel vari-format), we
809 * output using the formatter at 80 columns. This is the final output
810 * form if the transfer format is RFC822, but if the transfer format
811 * is Citadel proprietary, it'll still work, because the indentation
812 * for new paragraphs is correct and the client will reformat the
813 * message to the reader's screen width.
815 if (TheMessage->cm_format_type == 0) {
816 memfmout(80, mptr, 0);
819 /* If the message on disk is format 4 (MIME), we've gotta hand it
820 * off to the MIME parser. The client has already been told that
821 * this message is format 1 (fixed format), so the callback function
822 * we use will display those parts as-is.
824 if (TheMessage->cm_format_type == 4) {
825 mime_parser(mptr, NULL, *fixed_output);
830 CtdlFreeMessage(TheMessage);
837 * display a message (mode 0 - Citadel proprietary)
839 void cmd_msg0(char *cmdbuf)
842 int headers_only = 0;
844 extract(msgid, cmdbuf, 0);
845 headers_only = extract_int(cmdbuf, 1);
847 output_message(msgid, MT_CITADEL, headers_only);
853 * display a message (mode 2 - RFC822)
855 void cmd_msg2(char *cmdbuf)
858 int headers_only = 0;
860 extract(msgid, cmdbuf, 0);
861 headers_only = extract_int(cmdbuf, 1);
863 output_message(msgid, MT_RFC822, headers_only);
869 * display a message (mode 3 - IGnet raw format - internal programs only)
871 void cmd_msg3(char *cmdbuf)
874 struct cdbdata *dmsgtext;
876 if (CC->internal_pgm == 0) {
877 cprintf("%d This command is for internal programs only.\n",
882 msgnum = extract_long(cmdbuf, 0);
884 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
885 if (dmsgtext == NULL) {
886 cprintf("%d Message %ld not found\n", ERROR, msgnum);
889 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
890 client_write(dmsgtext->ptr, dmsgtext->len);
897 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
899 void cmd_msg4(char *cmdbuf)
903 extract(msgid, cmdbuf, 0);
905 output_message(msgid, MT_MIME, 0);
909 * Open a component of a MIME message as a download file
911 void cmd_opna(char *cmdbuf)
915 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
917 extract(msgid, cmdbuf, 0);
918 extract(desired_section, cmdbuf, 1);
920 output_message(msgid, MT_DOWNLOAD, 0);
924 * Message base operation to send a message to the master file
925 * (returns new message number)
927 long send_message(char *message_in_memory,
928 /* pointer to buffer */
929 size_t message_length, /* length of buffer */
931 { /* 1 to generate an I field */
934 char *actual_message;
935 size_t actual_length;
939 /* Get a new message number */
940 newmsgid = get_new_message_number();
943 sprintf(msgidbuf, "I%ld", newmsgid);
944 actual_length = message_length + strlen(msgidbuf) + 1;
945 actual_message = mallok(actual_length);
946 memcpy(actual_message, message_in_memory, 3);
947 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
948 memcpy(&actual_message[strlen(msgidbuf) + 4],
949 &message_in_memory[3], message_length - 3);
951 actual_message = message_in_memory;
952 actual_length = message_length;
955 /* Write our little bundle of joy into the message base */
956 begin_critical_section(S_MSGMAIN);
957 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
958 actual_message, actual_length) < 0) {
959 lprintf(2, "Can't store message\n");
964 end_critical_section(S_MSGMAIN);
967 phree(actual_message);
969 /* Finally, return the pointers */
976 * this is a simple file copy routine.
978 void copy_file(char *from, char *to)
983 ffp = fopen(from, "r");
986 tfp = fopen(to, "w");
991 while (a = getc(ffp), a >= 0) {
1002 * message base operation to save a message and install its pointers
1004 void save_message(char *mtmp, /* file containing proper message */
1005 char *rec, /* Recipient (if mail) */
1006 char *force, /* if non-zero length, force a room */
1007 int mailtype, /* local or remote type, see citadel.h */
1009 { /* set to 1 to generate an 'I' field */
1011 char hold_rm[ROOMNAMELEN];
1012 char actual_rm[ROOMNAMELEN];
1013 char force_room[ROOMNAMELEN];
1014 char content_type[256]; /* We have to learn this */
1016 char recipient[256];
1018 char *message_in_memory;
1020 struct stat statbuf;
1023 struct usersupp userbuf;
1025 static int seqnum = 0;
1026 int successful_local_recipients = 0;
1027 struct quickroom qtemp;
1028 struct SuppMsgInfo smi;
1030 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1031 mtmp, rec, force, mailtype, generate_id);
1033 strcpy(force_room, force);
1035 /* Strip non-printable characters out of the recipient name */
1036 strcpy(recipient, rec);
1037 for (a = 0; a < strlen(recipient); ++a)
1038 if (!isprint(recipient[a]))
1039 strcpy(&recipient[a], &recipient[a + 1]);
1041 /* Measure the message */
1042 stat(mtmp, &statbuf);
1043 templen = statbuf.st_size;
1045 /* Now read it into memory */
1046 message_in_memory = (char *) mallok(templen);
1047 if (message_in_memory == NULL) {
1048 lprintf(2, "Can't allocate memory to save message!\n");
1051 fp = fopen(mtmp, "rb");
1052 fread(message_in_memory, templen, 1, fp);
1055 /* Learn about what's inside, because it's what's inside that counts */
1056 mptr = message_in_memory;
1057 ++mptr; /* advance past 0xFF header */
1058 ++mptr; /* advance past anon flag */
1062 strcpy(content_type, "text/x-citadel-variformat");
1065 strcpy(content_type, "text/plain");
1068 strcpy(content_type, "text/plain");
1069 /* advance past header fields */
1070 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1077 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1078 safestrncpy(content_type, mptr,
1079 sizeof(content_type));
1080 lprintf(9, "%s\n", content_type);
1081 strcpy(content_type, &content_type[14]);
1082 for (a = 0; a < strlen(content_type); ++a)
1083 if ((content_type[a] == ';')
1084 || (content_type[a] == ' ')
1085 || (content_type[a] == 13)
1086 || (content_type[a] == 10))
1087 content_type[a] = 0;
1093 lprintf(9, "Content type is <%s>\n", content_type);
1095 /* Save it to disk */
1096 newmsgid = send_message(message_in_memory, templen, generate_id);
1097 phree(message_in_memory);
1101 strcpy(actual_rm, CC->quickroom.QRname);
1102 strcpy(hold_rm, "");
1104 /* If this is being done by the networker delivering a private
1105 * message, we want to BYPASS saving the sender's copy (because there
1106 * is no local sender; it would otherwise go to the Trashcan).
1108 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1109 /* If the user is a twit, move to the twit room for posting */
1111 if (CC->usersupp.axlevel == 2) {
1112 strcpy(hold_rm, actual_rm);
1113 strcpy(actual_rm, config.c_twitroom);
1115 /* ...or if this message is destined for Aide> then go there. */
1116 lprintf(9, "actual room forcing loop\n");
1117 if (strlen(force_room) > 0) {
1118 strcpy(hold_rm, actual_rm);
1119 strcpy(actual_rm, force_room);
1121 /* This call to usergoto() changes rooms if necessary. It also
1122 * causes the latest message list to be read into memory.
1124 usergoto(actual_rm, 0);
1126 /* read in the quickroom record, obtaining a lock... */
1127 lgetroom(&CC->quickroom, actual_rm);
1129 /* Fix an obscure bug */
1130 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1131 CC->quickroom.QRflags =
1132 CC->quickroom.QRflags & ~QR_MAILBOX;
1134 /* Add the message pointer to the room */
1135 CC->quickroom.QRhighest =
1136 AddMessageToRoom(&CC->quickroom, newmsgid);
1138 /* update quickroom */
1139 lputroom(&CC->quickroom);
1140 ++successful_local_recipients;
1142 /* Network mail - send a copy to the network program. */
1143 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1144 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1145 (long) getpid(), CC->cs_pid, ++seqnum);
1146 copy_file(mtmp, aaa);
1147 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1149 /* Bump this user's messages posted counter. */
1150 lgetuser(&CC->usersupp, CC->curr_user);
1151 CC->usersupp.posted = CC->usersupp.posted + 1;
1152 lputuser(&CC->usersupp);
1154 /* If this is private, local mail, make a copy in the
1155 * recipient's mailbox and bump the reference count.
1157 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1158 if (getuser(&userbuf, recipient) == 0) {
1159 MailboxName(actual_rm, &userbuf, MAILROOM);
1160 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1161 if (lgetroom(&qtemp, actual_rm) == 0) {
1163 AddMessageToRoom(&qtemp, newmsgid);
1165 ++successful_local_recipients;
1169 /* If we've posted in a room other than the current room, then we
1170 * have to now go back to the current room...
1172 if (strlen(hold_rm) > 0) {
1173 usergoto(hold_rm, 0);
1175 unlink(mtmp); /* delete the temporary file */
1177 /* Write a supplemental message info record. This doesn't have to
1178 * be a critical section because nobody else knows about this message
1181 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1182 smi.smi_msgnum = newmsgid;
1183 smi.smi_refcount = successful_local_recipients;
1184 safestrncpy(smi.smi_content_type, content_type, 64);
1185 PutSuppMsgInfo(&smi);
1190 * Generate an administrative message and post it in the Aide> room.
1192 void aide_message(char *text)
1196 fp = fopen(CC->temp, "wb");
1197 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1198 fprintf(fp, "Psysop%c", 0);
1199 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1200 fprintf(fp, "ACitadel%c", 0);
1201 fprintf(fp, "OAide%c", 0);
1202 fprintf(fp, "N%s%c", NODENAME, 0);
1203 fprintf(fp, "M%s\n%c", text, 0);
1205 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1206 syslog(LOG_NOTICE, text);
1209 * Build a binary message to be saved on disk.
1210 */ void make_message(
1211 char *filename, /* temporary file name */
1212 struct usersupp *author, /* author's usersupp structure */
1213 char *recipient, /* NULL if it's not mail */
1214 char *room, /* room where it's going */
1215 int type, /* see MES_ types in header file */
1216 int net_type, /* see MES_ types in header file */
1217 int format_type, /* local or remote (see citadel.h) */
1219 { /* who we're masquerading as */
1227 /* Don't confuse the poor folks if it's not routed mail. */
1228 strcpy(dest_node, "");
1231 /* If net_type is MES_BINARY, split out the destination node. */
1232 if (net_type == MES_BINARY) {
1233 strcpy(dest_node, NODENAME);
1234 for (a = 0; a < strlen(recipient); ++a) {
1235 if (recipient[a] == '@') {
1237 strcpy(dest_node, &recipient[a + 1]);
1241 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1242 strcpy(dest_node, "internet");
1244 while (isspace(recipient[strlen(recipient) - 1]))
1245 recipient[strlen(recipient) - 1] = 0;
1248 fp = fopen(filename, "w");
1250 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1251 putc(format_type, fp); /* Formatted or unformatted */
1252 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1253 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1255 fprintf(fp, "A%s%c", fake_name, 0);
1257 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1259 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1260 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1262 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1265 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1266 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1268 if (recipient[0] != 0)
1269 fprintf(fp, "R%s%c", recipient, 0);
1270 if (dest_node[0] != 0)
1271 fprintf(fp, "D%s%c", dest_node, 0);
1275 while (client_gets(buf), strcmp(buf, "000")) {
1276 fprintf(fp, "%s\n", buf);
1287 * message entry - mode 0 (normal) <bc>
1289 void cmd_ent0(char *entargs)
1292 char recipient[256];
1294 int format_type = 0;
1295 char newusername[256]; /* <bc> */
1300 struct usersupp tempUS;
1303 post = extract_int(entargs, 0);
1304 extract(recipient, entargs, 1);
1305 anon_flag = extract_int(entargs, 2);
1306 format_type = extract_int(entargs, 3);
1308 /* first check to make sure the request is valid. */
1310 if (!(CC->logged_in)) {
1311 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1314 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1315 cprintf("%d Need to be validated to enter ",
1316 ERROR + HIGHER_ACCESS_REQUIRED);
1317 cprintf("(except in %s> to sysop)\n", MAILROOM);
1320 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1321 cprintf("%d Need net privileges to enter here.\n",
1322 ERROR + HIGHER_ACCESS_REQUIRED);
1325 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1326 cprintf("%d Sorry, this is a read-only room.\n",
1327 ERROR + HIGHER_ACCESS_REQUIRED);
1333 if (post == 2) { /* <bc> */
1334 if (CC->usersupp.axlevel < 6) {
1335 cprintf("%d You don't have permission to do an aide post.\n",
1336 ERROR + HIGHER_ACCESS_REQUIRED);
1339 extract(newusername, entargs, 4);
1340 memset(CC->fake_postname, 0, 32);
1341 strcpy(CC->fake_postname, newusername);
1342 cprintf("%d Ok\n", OK);
1345 CC->cs_flags |= CS_POSTING;
1348 if (CC->quickroom.QRflags & QR_MAILBOX) {
1349 if (CC->usersupp.axlevel >= 2) {
1350 strcpy(buf, recipient);
1352 strcpy(buf, "sysop");
1353 lprintf(9, "calling alias()\n");
1354 e = alias(buf); /* alias and mail type */
1355 lprintf(9, "alias() returned %d\n", e);
1356 if ((buf[0] == 0) || (e == MES_ERROR)) {
1357 cprintf("%d Unknown address - cannot send message.\n",
1358 ERROR + NO_SUCH_USER);
1361 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1362 cprintf("%d Net privileges required for network mail.\n",
1363 ERROR + HIGHER_ACCESS_REQUIRED);
1366 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1367 && ((CC->usersupp.flags & US_INTERNET) == 0)
1368 && (!CC->internal_pgm)) {
1369 cprintf("%d You don't have access to Internet mail.\n",
1370 ERROR + HIGHER_ACCESS_REQUIRED);
1373 if (!strcasecmp(buf, "sysop")) {
1378 goto SKFALL; /* don't search local file */
1379 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1380 cprintf("%d Can't send mail to yourself!\n",
1381 ERROR + NO_SUCH_USER);
1384 /* Check to make sure the user exists; also get the correct
1385 * upper/lower casing of the name.
1387 a = getuser(&tempUS, buf);
1389 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1392 strcpy(buf, tempUS.fullname);
1394 SKFALL:b = MES_NORMAL;
1395 if (CC->quickroom.QRflags & QR_ANONONLY)
1397 if (CC->quickroom.QRflags & QR_ANONOPT) {
1401 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1404 /* If we're only checking the validity of the request, return
1405 * success without creating the message.
1408 cprintf("%d %s\n", OK, buf);
1411 cprintf("%d send message\n", SEND_LISTING);
1412 if (CC->fake_postname[0])
1413 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1414 else if (CC->fake_username[0])
1415 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1417 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1418 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1419 CC->fake_postname[0] = '\0';
1426 * message entry - mode 3 (raw)
1428 void cmd_ent3(char *entargs)
1434 struct usersupp tempUS;
1439 if (CC->internal_pgm == 0) {
1440 cprintf("%d This command is for internal programs only.\n",
1444 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1445 for (a = 0; a < strlen(recp); ++a)
1446 if (!isprint(recp[a]))
1447 strcpy(&recp[a], &recp[a + 1]);
1448 while (isspace(recp[0]))
1449 strcpy(recp, &recp[1]);
1450 while (isspace(recp[strlen(recp) - 1]))
1451 recp[strlen(recp) - 1] = 0;
1453 /* If we're in Mail, check the recipient */
1454 if (strlen(recp) > 0) {
1455 e = alias(recp); /* alias and mail type */
1456 if ((recp[0] == 0) || (e == MES_ERROR)) {
1457 cprintf("%d Unknown address - cannot send message.\n",
1458 ERROR + NO_SUCH_USER);
1461 if (e == MES_LOCAL) {
1462 a = getuser(&tempUS, recp);
1464 cprintf("%d No such user.\n",
1465 ERROR + NO_SUCH_USER);
1470 /* At this point, message has been approved. */
1471 if (extract_int(entargs, 0) == 0) {
1472 cprintf("%d OK to send\n", OK);
1475 /* open a temp file to hold the message */
1476 fp = fopen(CC->temp, "wb");
1478 cprintf("%d Cannot open %s: %s\n",
1479 ERROR + INTERNAL_ERROR,
1480 CC->temp, strerror(errno));
1483 msglen = extract_long(entargs, 2);
1484 cprintf("%d %ld\n", SEND_BINARY, msglen);
1485 while (msglen > 0L) {
1486 bloklen = ((msglen >= 255L) ? 255 : msglen);
1487 client_read(buf, (int) bloklen);
1488 fwrite(buf, (int) bloklen, 1, fp);
1489 msglen = msglen - bloklen;
1493 save_message(CC->temp, recp, "", e, 0);
1498 * API function to delete messages which match a set of criteria
1499 * (returns the actual number of messages deleted)
1500 * FIX ... still need to implement delete by content type
1502 int CtdlDeleteMessages(char *room_name, /* which room */
1503 long dmsgnum, /* or "0" for any */
1504 char *content_type /* or NULL for any */
1508 struct quickroom qrbuf;
1509 struct cdbdata *cdbfr;
1510 long *msglist = NULL;
1513 int num_deleted = 0;
1515 struct SuppMsgInfo smi;
1517 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1518 room_name, dmsgnum, content_type);
1520 /* get room record, obtaining a lock... */
1521 if (lgetroom(&qrbuf, room_name) != 0) {
1522 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1524 return (0); /* room not found */
1526 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1528 lprintf(9, "doing mallok/memcpy loop\n");
1529 if (cdbfr != NULL) {
1530 msglist = mallok(cdbfr->len);
1531 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1532 num_msgs = cdbfr->len / sizeof(long);
1536 for (i = 0; i < num_msgs; ++i) {
1539 /* Set/clear a bit for each criterion */
1541 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1542 delete_this |= 0x01;
1544 if (content_type == NULL) {
1545 delete_this |= 0x02;
1547 GetSuppMsgInfo(&smi, msglist[i]);
1548 if (!strcasecmp(smi.smi_content_type,
1550 delete_this |= 0x02;
1554 /* Delete message only if all bits are set */
1555 if (delete_this == 0x03) {
1556 AdjRefCount(msglist[i], -1);
1562 num_msgs = sort_msglist(msglist, num_msgs);
1563 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1564 msglist, (num_msgs * sizeof(long)));
1566 qrbuf.QRhighest = msglist[num_msgs - 1];
1570 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1571 return (num_deleted);
1577 * Delete message from current room
1579 void cmd_dele(char *delstr)
1584 getuser(&CC->usersupp, CC->curr_user);
1585 if ((CC->usersupp.axlevel < 6)
1586 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1587 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1588 cprintf("%d Higher access required.\n",
1589 ERROR + HIGHER_ACCESS_REQUIRED);
1592 delnum = extract_long(delstr, 0);
1594 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1597 cprintf("%d %d message%s deleted.\n", OK,
1598 num_deleted, ((num_deleted != 1) ? "s" : ""));
1600 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1606 * move a message to another room
1608 void cmd_move(char *args)
1612 struct quickroom qtemp;
1615 num = extract_long(args, 0);
1616 extract(targ, args, 1);
1618 getuser(&CC->usersupp, CC->curr_user);
1619 if ((CC->usersupp.axlevel < 6)
1620 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1621 cprintf("%d Higher access required.\n",
1622 ERROR + HIGHER_ACCESS_REQUIRED);
1625 if (getroom(&qtemp, targ) != 0) {
1626 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1629 /* Bump the reference count, otherwise the message will be deleted
1630 * from disk when we remove it from the source room.
1632 AdjRefCount(num, 1);
1634 /* yank the message out of the current room... */
1635 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1638 /* put the message into the target room */
1639 lgetroom(&qtemp, targ);
1640 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1642 cprintf("%d Message moved.\n", OK);
1644 AdjRefCount(num, (-1)); /* oops */
1645 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1652 * GetSuppMsgInfo() - Get the supplementary record for a message
1654 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1657 struct cdbdata *cdbsmi;
1660 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1661 smibuf->smi_msgnum = msgnum;
1662 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1664 /* Use the negative of the message number for its supp record index */
1665 TheIndex = (0L - msgnum);
1667 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1668 if (cdbsmi == NULL) {
1669 return; /* record not found; go with defaults */
1671 memcpy(smibuf, cdbsmi->ptr,
1672 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1673 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1680 * PutSuppMsgInfo() - (re)write supplementary record for a message
1682 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1686 /* Use the negative of the message number for its supp record index */
1687 TheIndex = (0L - smibuf->smi_msgnum);
1689 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1690 smibuf->smi_msgnum, smibuf->smi_refcount);
1692 cdb_store(CDB_MSGMAIN,
1693 &TheIndex, sizeof(long),
1694 smibuf, sizeof(struct SuppMsgInfo));
1698 * AdjRefCount - change the reference count for a message;
1699 * delete the message if it reaches zero
1700 */ void AdjRefCount(long msgnum, int incr)
1703 struct SuppMsgInfo smi;
1706 /* This is a *tight* critical section; please keep it that way, as
1707 * it may get called while nested in other critical sections.
1708 * Complicating this any further will surely cause deadlock!
1710 begin_critical_section(S_SUPPMSGMAIN);
1711 GetSuppMsgInfo(&smi, msgnum);
1712 smi.smi_refcount += incr;
1713 PutSuppMsgInfo(&smi);
1714 end_critical_section(S_SUPPMSGMAIN);
1716 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1717 msgnum, smi.smi_refcount);
1719 /* If the reference count is now zero, delete the message
1720 * (and its supplementary record as well).
1722 if (smi.smi_refcount == 0) {
1723 lprintf(9, "Deleting message <%ld>\n", msgnum);
1725 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1726 delnum = (0L - msgnum);
1727 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1732 * Write a generic object to this room
1734 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1735 char *content_type, /* MIME type of this object */
1736 char *tempfilename, /* Where to fetch it from */
1737 int is_mailbox, /* Private mailbox room? */
1738 int is_binary, /* Is encoding necessary? */
1739 int is_unique /* Del others of this type? */
1744 char filename[PATH_MAX];
1747 struct quickroom qrbuf;
1748 char roomname[ROOMNAMELEN];
1751 MailboxName(roomname, &CC->usersupp, req_room);
1753 safestrncpy(roomname, req_room, sizeof(roomname));
1755 strcpy(filename, tmpnam(NULL));
1756 fp = fopen(filename, "w");
1760 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1761 fprintf(fp, "T%ld%c", time(NULL), 0);
1762 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1763 fprintf(fp, "O%s%c", roomname, 0);
1764 fprintf(fp, "N%s%c", config.c_nodename, 0);
1765 fprintf(fp, "MContent-type: %s\n", content_type);
1767 tempfp = fopen(tempfilename, "r");
1768 if (tempfp == NULL) {
1773 if (is_binary == 0) {
1774 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1775 while (ch = getc(tempfp), ch > 0)
1781 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1784 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1785 tempfilename, filename);
1789 /* Create the requested room if we have to. */
1790 if (getroom(&qrbuf, roomname) != 0) {
1791 create_room(roomname, 4, "", 0);
1793 /* If the caller specified this object as unique, delete all
1794 * other objects of this type that are currently in the room.
1797 lprintf(9, "Deleted %d other msgs of this type\n",
1798 CtdlDeleteMessages(roomname, 0L, content_type));
1800 /* Now write the data */
1801 save_message(filename, "", roomname, MES_LOCAL, 1);