22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
32 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
34 extern struct config config;
38 * This function is self explanatory.
39 * (What can I say, I'm in a weird mood today...)
41 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
45 for (i = 0; i < strlen(name); ++i)
48 if (isspace(name[i - 1])) {
49 strcpy(&name[i - 1], &name[i]);
52 while (isspace(name[i + 1])) {
53 strcpy(&name[i + 1], &name[i + 2]);
60 * Aliasing for network mail.
61 * (Error messages have been commented out, because this is a server.)
64 { /* process alias and routing info for mail */
67 char aaa[300], bbb[300];
69 lprintf(9, "alias() called for <%s>\n", name);
71 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
73 fp = fopen("network/mail.aliases", "r");
75 fp = fopen("/dev/null", "r");
80 while (fgets(aaa, sizeof aaa, fp) != NULL) {
81 while (isspace(name[0]))
82 strcpy(name, &name[1]);
83 aaa[strlen(aaa) - 1] = 0;
85 for (a = 0; a < strlen(aaa); ++a) {
87 strcpy(bbb, &aaa[a + 1]);
91 if (!strcasecmp(name, aaa))
95 lprintf(7, "Mail is being forwarded to %s\n", name);
97 /* determine local or remote type, see citadel.h */
98 for (a = 0; a < strlen(name); ++a)
100 return (MES_INTERNET);
101 for (a = 0; a < strlen(name); ++a)
103 for (b = a; b < strlen(name); ++b)
105 return (MES_INTERNET);
107 for (a = 0; a < strlen(name); ++a)
111 lprintf(7, "Too many @'s in address\n");
115 for (a = 0; a < strlen(name); ++a)
117 strcpy(bbb, &name[a + 1]);
119 strcpy(bbb, &bbb[1]);
120 fp = fopen("network/mail.sysinfo", "r");
124 a = getstring(fp, aaa);
125 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
126 a = getstring(fp, aaa);
127 if (!strncmp(aaa, "use ", 4)) {
128 strcpy(bbb, &aaa[4]);
133 if (!strncmp(aaa, "uum", 3)) {
135 for (a = 0; a < strlen(bbb); ++a) {
141 while (bbb[strlen(bbb) - 1] == '_')
142 bbb[strlen(bbb) - 1] = 0;
143 sprintf(name, &aaa[4], bbb);
144 return (MES_INTERNET);
146 if (!strncmp(aaa, "bin", 3)) {
149 while (aaa[strlen(aaa) - 1] != '@')
150 aaa[strlen(aaa) - 1] = 0;
151 aaa[strlen(aaa) - 1] = 0;
152 while (aaa[strlen(aaa) - 1] == ' ')
153 aaa[strlen(aaa) - 1] = 0;
154 while (bbb[0] != '@')
155 strcpy(bbb, &bbb[1]);
156 strcpy(bbb, &bbb[1]);
157 while (bbb[0] == ' ')
158 strcpy(bbb, &bbb[1]);
159 sprintf(name, "%s @%s", aaa, bbb);
172 fp = fopen("citadel.control", "r");
173 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
179 void simple_listing(long msgnum)
181 cprintf("%ld\n", msgnum);
186 * API function to perform an operation for each qualifying message in the
189 void CtdlForEachMessage(int mode, long ref,
191 void (*CallBack) (long msgnum))
196 struct cdbdata *cdbfr;
197 long *msglist = NULL;
200 struct SuppMsgInfo smi;
202 /* Learn about the user and room in question */
204 getuser(&CC->usersupp, CC->curr_user);
205 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
207 /* Load the message list */
208 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
210 msglist = mallok(cdbfr->len);
211 memcpy(msglist, cdbfr->ptr, cdbfr->len);
212 num_msgs = cdbfr->len / sizeof(long);
215 return; /* No messages at all? No further action. */
219 /* If the caller is looking for a specific MIME type, then filter
220 * out all messages which are not of the type requested.
223 if (content_type != NULL)
224 if (strlen(content_type) > 0)
225 for (a = 0; a < num_msgs; ++a) {
226 GetSuppMsgInfo(&smi, msglist[a]);
227 if (strcasecmp(smi.smi_content_type, content_type)) {
232 num_msgs = sort_msglist(msglist, num_msgs);
235 * Now iterate through the message list, according to the
236 * criteria supplied by the caller.
239 for (a = 0; a < num_msgs; ++a) {
240 thismsg = msglist[a];
241 lprintf(9, "Iterating through <%ld>\n", thismsg);
246 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
247 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
248 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
249 && (CC->usersupp.flags & US_LASTOLD))
250 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
251 || ((mode == MSGS_FIRST) && (a < ref))
252 || ((mode == MSGS_GT) && (thismsg > ref))
255 lprintf(9, "Issuing callback for <%ld>\n", thismsg);
259 phree(msglist); /* Clean up */
265 * cmd_msgs() - get list of message #'s in this room
266 * implements the MSGS server command using CtdlForEachMessage()
268 void cmd_msgs(char *cmdbuf)
274 extract(which, cmdbuf, 0);
275 cm_ref = extract_int(cmdbuf, 1);
279 if (!strncasecmp(which, "OLD", 3))
281 else if (!strncasecmp(which, "NEW", 3))
283 else if (!strncasecmp(which, "FIRST", 5))
285 else if (!strncasecmp(which, "LAST", 4))
287 else if (!strncasecmp(which, "GT", 2))
290 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
291 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
294 cprintf("%d Message list...\n", LISTING_FOLLOWS);
295 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
303 * help_subst() - support routine for help file viewer
305 void help_subst(char *strbuf, char *source, char *dest)
310 while (p = pattern2(strbuf, source), (p >= 0)) {
311 strcpy(workbuf, &strbuf[p + strlen(source)]);
312 strcpy(&strbuf[p], dest);
313 strcat(strbuf, workbuf);
318 void do_help_subst(char *buffer)
322 help_subst(buffer, "^nodename", config.c_nodename);
323 help_subst(buffer, "^humannode", config.c_humannode);
324 help_subst(buffer, "^fqdn", config.c_fqdn);
325 help_subst(buffer, "^username", CC->usersupp.fullname);
326 sprintf(buf2, "%ld", CC->usersupp.usernum);
327 help_subst(buffer, "^usernum", buf2);
328 help_subst(buffer, "^sysadm", config.c_sysadm);
329 help_subst(buffer, "^variantname", CITADEL);
330 sprintf(buf2, "%d", config.c_maxsessions);
331 help_subst(buffer, "^maxsessions", buf2);
337 * memfmout() - Citadel text formatter and paginator.
338 * Although the original purpose of this routine was to format
339 * text to the reader's screen width, all we're really using it
340 * for here is to format text out to 80 columns before sending it
341 * to the client. The client software may reformat it again.
343 void memfmout(int width, char *mptr, char subst)
344 /* screen width to use */
345 /* where are we going to get our text from? */
346 /* nonzero if we should use hypertext mode */
358 c = 1; /* c is the current pos */
361 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
363 buffer[strlen(buffer) + 1] = 0;
364 buffer[strlen(buffer)] = ch;
367 if (buffer[0] == '^')
368 do_help_subst(buffer);
370 buffer[strlen(buffer) + 1] = 0;
372 strcpy(buffer, &buffer[1]);
381 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
383 if (((old == 13) || (old == 10)) && (isspace(real))) {
391 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
392 cprintf("\n%s", aaa);
401 if ((strlen(aaa) + c) > (width - 5)) {
411 if ((ch == 13) || (ch == 10)) {
412 cprintf("%s\n", aaa);
419 FMTEND:cprintf("%s\n", aaa);
425 * Callback function for mime parser that simply lists the part
427 void list_this_part(char *name, char *filename, char *partnum, char *disp,
428 void *content, char *cbtype, size_t length)
431 cprintf("part=%s|%s|%s|%s|%s|%d\n",
432 name, filename, partnum, disp, cbtype, length);
437 * Callback function for mime parser that wants to display text
439 void fixed_output(char *name, char *filename, char *partnum, char *disp,
440 void *content, char *cbtype, size_t length)
443 if (!strcasecmp(cbtype, "text/plain")) {
444 client_write(content, length);
446 cprintf("Part %s: %s (%s) (%d bytes)\n",
447 partnum, filename, cbtype, length);
453 * Callback function for mime parser that opens a section for downloading
455 void mime_download(char *name, char *filename, char *partnum, char *disp,
456 void *content, char *cbtype, size_t length)
459 /* Silently go away if there's already a download open... */
460 if (CC->download_fp != NULL)
463 /* ...or if this is not the desired section */
464 if (strcasecmp(desired_section, partnum))
467 CC->download_fp = tmpfile();
468 if (CC->download_fp == NULL)
471 fwrite(content, length, 1, CC->download_fp);
472 fflush(CC->download_fp);
473 rewind(CC->download_fp);
475 OpenCmdResult(filename, cbtype);
481 * Load a message from disk into memory.
482 * (This will replace a big piece of output_message() eventually)
484 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
485 * using the CtdlMessageFree() function.
487 struct CtdlMessage *CtdlFetchMessage(long msgnum)
489 struct cdbdata *dmsgtext;
490 struct CtdlMessage *ret = NULL;
493 CIT_UBYTE field_header;
497 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
498 if (dmsgtext == NULL) {
499 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
502 mptr = dmsgtext->ptr;
504 /* Parse the three bytes that begin EVERY message on disk.
505 * The first is always 0xFF, the on-disk magic number.
506 * The second is the anonymous/public type byte.
507 * The third is the format type byte (vari, fixed, or MIME).
511 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
515 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
516 memset(ret, 0, sizeof(struct CtdlMessage));
518 ret->cm_magic = CTDLMESSAGE_MAGIC;
519 ret->cm_anon_type = *mptr++; /* Anon type byte */
520 ret->cm_format_type = *mptr++; /* Format type byte */
523 * The rest is zero or more arbitrary fields. Load them in.
524 * We're done when we encounter either a zero-length field or
525 * have just processed the 'M' (message text) field.
528 field_length = strlen(mptr);
529 if (field_length == 0)
531 field_header = *mptr++;
532 ret->cm_fields[field_header] = mallok(field_length);
533 strcpy(ret->cm_fields[field_header], mptr);
535 while (*mptr++ != 0); /* advance to next field */
537 } while ((field_length > 0) && (field_header != 'M'));
544 * 'Destructor' for struct CtdlMessage
546 void CtdlFreeMessage(struct CtdlMessage *msg)
552 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
553 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
556 for (i = 0; i < 256; ++i)
557 if (msg->cm_fields[i] != NULL)
558 phree(msg->cm_fields[i]);
566 * Get a message off disk. (return value is the message's timestamp)
569 void output_message(char *msgid, int mode, int headers_only)
577 struct CtdlMessage *TheMessage = NULL;
581 /* buffers needed for RFC822 translation */
589 msg_num = atol(msgid);
591 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
592 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
595 /* FIX ... small security issue
596 * We need to check to make sure the requested message is actually
597 * in the current room, and set msg_ok to 1 only if it is. This
598 * functionality is currently missing because I'm in a hurry to replace
599 * broken production code with nonbroken pre-beta code. :( -- ajc
602 cprintf("%d Message %ld is not in this room.\n",
609 * Fetch the message from disk
611 TheMessage = CtdlFetchMessage(msg_num);
612 if (TheMessage == NULL) {
613 cprintf("%d Can't locate message %ld on disk\n", ERROR, msg_num);
617 /* Are we downloading a MIME component? */
618 if (mode == MT_DOWNLOAD) {
619 if (TheMessage->cm_format_type != 4) {
620 cprintf("%d This is not a MIME message.\n",
622 } else if (CC->download_fp != NULL) {
623 cprintf("%d You already have a download open.\n",
626 /* Parse the message text component */
627 mptr = TheMessage->cm_fields['M'];
628 mime_parser(mptr, NULL, *mime_download);
629 /* If there's no file open by this time, the requested
630 * section wasn't found, so print an error
632 if (CC->download_fp == NULL) {
633 cprintf("%d Section %s not found.\n",
634 ERROR + FILE_NOT_FOUND,
638 CtdlFreeMessage(TheMessage);
641 /* now for the user-mode message reading loops */
642 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
644 if (mode == MT_CITADEL)
645 cprintf("type=%d\n", TheMessage->cm_format_type);
647 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
648 cprintf("nhdr=yes\n");
650 /* begin header processing loop for Citadel message format */
652 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
654 if (TheMessage->cm_fields['P']) {
655 cprintf("path=%s\n", TheMessage->cm_fields['P']);
657 if (TheMessage->cm_fields['I']) {
658 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
660 if (TheMessage->cm_fields['T']) {
661 cprintf("time=%s\n", TheMessage->cm_fields['T']);
663 if (TheMessage->cm_fields['A']) {
664 strcpy(buf, TheMessage->cm_fields['A']);
665 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
666 if (TheMessage->cm_anon_type == MES_ANON)
667 cprintf("from=****");
668 else if (TheMessage->cm_anon_type == MES_AN2)
669 cprintf("from=anonymous");
671 cprintf("from=%s", buf);
673 && ((TheMessage->cm_anon_type == MES_ANON)
674 || (TheMessage->cm_anon_type == MES_AN2))) {
675 cprintf(" [%s]", buf);
679 if (TheMessage->cm_fields['O']) {
680 cprintf("room=%s\n", TheMessage->cm_fields['O']);
682 if (TheMessage->cm_fields['N']) {
683 cprintf("node=%s\n", TheMessage->cm_fields['N']);
685 if (TheMessage->cm_fields['H']) {
686 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
688 if (TheMessage->cm_fields['R']) {
689 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
691 if (TheMessage->cm_fields['U']) {
692 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
695 /* begin header processing loop for RFC822 transfer format */
699 strcpy(snode, NODENAME);
700 strcpy(lnode, HUMANNODE);
701 if (mode == MT_RFC822) {
702 for (i = 0; i < 256; ++i) {
703 if (TheMessage->cm_fields[i]) {
704 mptr = TheMessage->cm_fields[i];
708 } else if (i == 'P') {
709 cprintf("Path: %s\n", mptr);
710 for (a = 0; a < strlen(mptr); ++a) {
711 if (mptr[a] == '!') {
712 strcpy(mptr, &mptr[a + 1]);
718 cprintf("Subject: %s\n", mptr);
724 cprintf("X-Citadel-Room: %s\n", mptr);
728 cprintf("To: %s\n", mptr);
731 cprintf("Date: %s", asctime(localtime(&xtime)));
736 if (mode == MT_RFC822) {
737 if (!strcasecmp(snode, NODENAME)) {
740 cprintf("Message-ID: <%s@%s>\n", mid, snode);
741 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
742 cprintf("From: %s@%s (%s)\n",
743 suser, snode, luser);
744 cprintf("Organization: %s\n", lnode);
746 /* end header processing loop ... at this point, we're in the text */
748 mptr = TheMessage->cm_fields['M'];
750 /* do some sort of MIME output */
751 if (TheMessage->cm_format_type == 4) {
752 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
753 mime_parser(mptr, NULL, *list_this_part);
755 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
757 CtdlFreeMessage(TheMessage);
763 CtdlFreeMessage(TheMessage);
766 /* signify start of msg text */
767 if (mode == MT_CITADEL)
769 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
772 /* If the format type on disk is 1 (fixed-format), then we want
773 * everything to be output completely literally ... regardless of
774 * what message transfer format is in use.
776 if (TheMessage->cm_format_type == 1) {
778 while (ch = *mptr++, ch > 0) {
781 if ((ch == 10) || (strlen(buf) > 250)) {
782 cprintf("%s\n", buf);
785 buf[strlen(buf) + 1] = 0;
786 buf[strlen(buf)] = ch;
790 cprintf("%s\n", buf);
792 /* If the message on disk is format 0 (Citadel vari-format), we
793 * output using the formatter at 80 columns. This is the final output
794 * form if the transfer format is RFC822, but if the transfer format
795 * is Citadel proprietary, it'll still work, because the indentation
796 * for new paragraphs is correct and the client will reformat the
797 * message to the reader's screen width.
799 if (TheMessage->cm_format_type == 0) {
800 memfmout(80, mptr, 0);
802 /* If the message on disk is format 4 (MIME), we've gotta hand it
803 * off to the MIME parser. The client has already been told that
804 * this message is format 1 (fixed format), so the callback function
805 * we use will display those parts as-is.
807 if (TheMessage->cm_format_type == 4) {
808 mime_parser(mptr, NULL, *fixed_output);
812 CtdlFreeMessage(TheMessage);
819 * display a message (mode 0 - Citadel proprietary)
821 void cmd_msg0(char *cmdbuf)
824 int headers_only = 0;
826 extract(msgid, cmdbuf, 0);
827 headers_only = extract_int(cmdbuf, 1);
829 output_message(msgid, MT_CITADEL, headers_only);
835 * display a message (mode 2 - RFC822)
837 void cmd_msg2(char *cmdbuf)
840 int headers_only = 0;
842 extract(msgid, cmdbuf, 0);
843 headers_only = extract_int(cmdbuf, 1);
845 output_message(msgid, MT_RFC822, headers_only);
851 * display a message (mode 3 - IGnet raw format - internal programs only)
853 void cmd_msg3(char *cmdbuf)
856 struct cdbdata *dmsgtext;
858 if (CC->internal_pgm == 0) {
859 cprintf("%d This command is for internal programs only.\n",
864 msgnum = extract_long(cmdbuf, 0);
866 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
867 if (dmsgtext == NULL) {
868 cprintf("%d Message %ld not found\n", ERROR, msgnum);
871 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
872 client_write(dmsgtext->ptr, dmsgtext->len);
879 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
881 void cmd_msg4(char *cmdbuf)
885 extract(msgid, cmdbuf, 0);
887 output_message(msgid, MT_MIME, 0);
891 * Open a component of a MIME message as a download file
893 void cmd_opna(char *cmdbuf)
897 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
899 extract(msgid, cmdbuf, 0);
900 extract(desired_section, cmdbuf, 1);
902 output_message(msgid, MT_DOWNLOAD, 0);
906 * Message base operation to send a message to the master file
907 * (returns new message number)
909 long send_message(char *message_in_memory,
910 /* pointer to buffer */
911 size_t message_length, /* length of buffer */
913 { /* 1 to generate an I field */
916 char *actual_message;
917 size_t actual_length;
921 /* Get a new message number */
922 newmsgid = get_new_message_number();
925 sprintf(msgidbuf, "I%ld", newmsgid);
926 actual_length = message_length + strlen(msgidbuf) + 1;
927 actual_message = mallok(actual_length);
928 memcpy(actual_message, message_in_memory, 3);
929 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
930 memcpy(&actual_message[strlen(msgidbuf) + 4],
931 &message_in_memory[3], message_length - 3);
933 actual_message = message_in_memory;
934 actual_length = message_length;
937 /* Write our little bundle of joy into the message base */
938 begin_critical_section(S_MSGMAIN);
939 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
940 actual_message, actual_length) < 0) {
941 lprintf(2, "Can't store message\n");
946 end_critical_section(S_MSGMAIN);
949 phree(actual_message);
951 /* Finally, return the pointers */
958 * this is a simple file copy routine.
960 void copy_file(char *from, char *to)
965 ffp = fopen(from, "r");
968 tfp = fopen(to, "w");
973 while (a = getc(ffp), a >= 0) {
984 * message base operation to save a message and install its pointers
986 void save_message(char *mtmp, /* file containing proper message */
987 char *rec, /* Recipient (if mail) */
988 char *force, /* if non-zero length, force a room */
989 int mailtype, /* local or remote type, see citadel.h */
991 { /* set to 1 to generate an 'I' field */
993 char hold_rm[ROOMNAMELEN];
994 char actual_rm[ROOMNAMELEN];
995 char force_room[ROOMNAMELEN];
996 char content_type[256]; /* We have to learn this */
1000 char *message_in_memory;
1002 struct stat statbuf;
1005 struct usersupp userbuf;
1007 static int seqnum = 0;
1008 int successful_local_recipients = 0;
1009 struct quickroom qtemp;
1010 struct SuppMsgInfo smi;
1012 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1013 mtmp, rec, force, mailtype, generate_id);
1015 strcpy(force_room, force);
1017 /* Strip non-printable characters out of the recipient name */
1018 strcpy(recipient, rec);
1019 for (a = 0; a < strlen(recipient); ++a)
1020 if (!isprint(recipient[a]))
1021 strcpy(&recipient[a], &recipient[a + 1]);
1023 /* Measure the message */
1024 stat(mtmp, &statbuf);
1025 templen = statbuf.st_size;
1027 /* Now read it into memory */
1028 message_in_memory = (char *) mallok(templen);
1029 if (message_in_memory == NULL) {
1030 lprintf(2, "Can't allocate memory to save message!\n");
1033 fp = fopen(mtmp, "rb");
1034 fread(message_in_memory, templen, 1, fp);
1037 /* Learn about what's inside, because it's what's inside that counts */
1038 mptr = message_in_memory;
1039 ++mptr; /* advance past 0xFF header */
1040 ++mptr; /* advance past anon flag */
1044 strcpy(content_type, "text/x-citadel-variformat");
1047 strcpy(content_type, "text/plain");
1050 strcpy(content_type, "text/plain");
1051 /* advance past header fields */
1052 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1059 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1060 safestrncpy(content_type, mptr,
1061 sizeof(content_type));
1062 lprintf(9, "%s\n", content_type);
1063 strcpy(content_type, &content_type[14]);
1064 for (a = 0; a < strlen(content_type); ++a)
1065 if ((content_type[a] == ';')
1066 || (content_type[a] == ' ')
1067 || (content_type[a] == 13)
1068 || (content_type[a] == 10))
1069 content_type[a] = 0;
1075 lprintf(9, "Content type is <%s>\n", content_type);
1077 /* Save it to disk */
1078 newmsgid = send_message(message_in_memory, templen, generate_id);
1079 phree(message_in_memory);
1083 strcpy(actual_rm, CC->quickroom.QRname);
1084 strcpy(hold_rm, "");
1086 /* If this is being done by the networker delivering a private
1087 * message, we want to BYPASS saving the sender's copy (because there
1088 * is no local sender; it would otherwise go to the Trashcan).
1090 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1091 /* If the user is a twit, move to the twit room for posting */
1093 if (CC->usersupp.axlevel == 2) {
1094 strcpy(hold_rm, actual_rm);
1095 strcpy(actual_rm, config.c_twitroom);
1097 /* ...or if this message is destined for Aide> then go there. */
1098 lprintf(9, "actual room forcing loop\n");
1099 if (strlen(force_room) > 0) {
1100 strcpy(hold_rm, actual_rm);
1101 strcpy(actual_rm, force_room);
1103 /* This call to usergoto() changes rooms if necessary. It also
1104 * causes the latest message list to be read into memory.
1106 usergoto(actual_rm, 0);
1108 /* read in the quickroom record, obtaining a lock... */
1109 lgetroom(&CC->quickroom, actual_rm);
1111 /* Fix an obscure bug */
1112 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1113 CC->quickroom.QRflags =
1114 CC->quickroom.QRflags & ~QR_MAILBOX;
1116 /* Add the message pointer to the room */
1117 CC->quickroom.QRhighest =
1118 AddMessageToRoom(&CC->quickroom, newmsgid);
1120 /* update quickroom */
1121 lputroom(&CC->quickroom);
1122 ++successful_local_recipients;
1124 /* Network mail - send a copy to the network program. */
1125 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1126 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1127 (long) getpid(), CC->cs_pid, ++seqnum);
1128 copy_file(mtmp, aaa);
1129 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1131 /* Bump this user's messages posted counter. */
1132 lgetuser(&CC->usersupp, CC->curr_user);
1133 CC->usersupp.posted = CC->usersupp.posted + 1;
1134 lputuser(&CC->usersupp);
1136 /* If this is private, local mail, make a copy in the
1137 * recipient's mailbox and bump the reference count.
1139 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1140 if (getuser(&userbuf, recipient) == 0) {
1141 MailboxName(actual_rm, &userbuf, MAILROOM);
1142 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1143 if (lgetroom(&qtemp, actual_rm) == 0) {
1145 AddMessageToRoom(&qtemp, newmsgid);
1147 ++successful_local_recipients;
1151 /* If we've posted in a room other than the current room, then we
1152 * have to now go back to the current room...
1154 if (strlen(hold_rm) > 0) {
1155 usergoto(hold_rm, 0);
1157 unlink(mtmp); /* delete the temporary file */
1159 /* Write a supplemental message info record. This doesn't have to
1160 * be a critical section because nobody else knows about this message
1163 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1164 smi.smi_msgnum = newmsgid;
1165 smi.smi_refcount = successful_local_recipients;
1166 safestrncpy(smi.smi_content_type, content_type, 64);
1167 PutSuppMsgInfo(&smi);
1172 * Generate an administrative message and post it in the Aide> room.
1174 void aide_message(char *text)
1178 fp = fopen(CC->temp, "wb");
1179 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1180 fprintf(fp, "Psysop%c", 0);
1181 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1182 fprintf(fp, "ACitadel%c", 0);
1183 fprintf(fp, "OAide%c", 0);
1184 fprintf(fp, "N%s%c", NODENAME, 0);
1185 fprintf(fp, "M%s\n%c", text, 0);
1187 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1188 syslog(LOG_NOTICE, text);
1191 * Build a binary message to be saved on disk.
1192 */ void make_message(
1193 char *filename, /* temporary file name */
1194 struct usersupp *author, /* author's usersupp structure */
1195 char *recipient, /* NULL if it's not mail */
1196 char *room, /* room where it's going */
1197 int type, /* see MES_ types in header file */
1198 int net_type, /* see MES_ types in header file */
1199 int format_type, /* local or remote (see citadel.h) */
1201 { /* who we're masquerading as */
1209 /* Don't confuse the poor folks if it's not routed mail. */
1210 strcpy(dest_node, "");
1213 /* If net_type is MES_BINARY, split out the destination node. */
1214 if (net_type == MES_BINARY) {
1215 strcpy(dest_node, NODENAME);
1216 for (a = 0; a < strlen(recipient); ++a) {
1217 if (recipient[a] == '@') {
1219 strcpy(dest_node, &recipient[a + 1]);
1223 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1224 strcpy(dest_node, "internet");
1226 while (isspace(recipient[strlen(recipient) - 1]))
1227 recipient[strlen(recipient) - 1] = 0;
1230 fp = fopen(filename, "w");
1232 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1233 putc(format_type, fp); /* Formatted or unformatted */
1234 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1235 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1237 fprintf(fp, "A%s%c", fake_name, 0);
1239 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1241 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1242 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1244 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1247 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1248 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1250 if (recipient[0] != 0)
1251 fprintf(fp, "R%s%c", recipient, 0);
1252 if (dest_node[0] != 0)
1253 fprintf(fp, "D%s%c", dest_node, 0);
1257 while (client_gets(buf), strcmp(buf, "000")) {
1258 fprintf(fp, "%s\n", buf);
1269 * message entry - mode 0 (normal) <bc>
1271 void cmd_ent0(char *entargs)
1274 char recipient[256];
1276 int format_type = 0;
1277 char newusername[256]; /* <bc> */
1282 struct usersupp tempUS;
1285 post = extract_int(entargs, 0);
1286 extract(recipient, entargs, 1);
1287 anon_flag = extract_int(entargs, 2);
1288 format_type = extract_int(entargs, 3);
1290 /* first check to make sure the request is valid. */
1292 if (!(CC->logged_in)) {
1293 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1296 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1297 cprintf("%d Need to be validated to enter ",
1298 ERROR + HIGHER_ACCESS_REQUIRED);
1299 cprintf("(except in %s> to sysop)\n", MAILROOM);
1302 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1303 cprintf("%d Need net privileges to enter here.\n",
1304 ERROR + HIGHER_ACCESS_REQUIRED);
1307 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1308 cprintf("%d Sorry, this is a read-only room.\n",
1309 ERROR + HIGHER_ACCESS_REQUIRED);
1315 if (post == 2) { /* <bc> */
1316 if (CC->usersupp.axlevel < 6) {
1317 cprintf("%d You don't have permission to do an aide post.\n",
1318 ERROR + HIGHER_ACCESS_REQUIRED);
1321 extract(newusername, entargs, 4);
1322 memset(CC->fake_postname, 0, 32);
1323 strcpy(CC->fake_postname, newusername);
1324 cprintf("%d Ok\n", OK);
1327 CC->cs_flags |= CS_POSTING;
1330 if (CC->quickroom.QRflags & QR_MAILBOX) {
1331 if (CC->usersupp.axlevel >= 2) {
1332 strcpy(buf, recipient);
1334 strcpy(buf, "sysop");
1335 lprintf(9, "calling alias()\n");
1336 e = alias(buf); /* alias and mail type */
1337 lprintf(9, "alias() returned %d\n", e);
1338 if ((buf[0] == 0) || (e == MES_ERROR)) {
1339 cprintf("%d Unknown address - cannot send message.\n",
1340 ERROR + NO_SUCH_USER);
1343 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1344 cprintf("%d Net privileges required for network mail.\n",
1345 ERROR + HIGHER_ACCESS_REQUIRED);
1348 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1349 && ((CC->usersupp.flags & US_INTERNET) == 0)
1350 && (!CC->internal_pgm)) {
1351 cprintf("%d You don't have access to Internet mail.\n",
1352 ERROR + HIGHER_ACCESS_REQUIRED);
1355 if (!strcasecmp(buf, "sysop")) {
1360 goto SKFALL; /* don't search local file */
1361 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1362 cprintf("%d Can't send mail to yourself!\n",
1363 ERROR + NO_SUCH_USER);
1366 /* Check to make sure the user exists; also get the correct
1367 * upper/lower casing of the name.
1369 a = getuser(&tempUS, buf);
1371 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1374 strcpy(buf, tempUS.fullname);
1376 SKFALL:b = MES_NORMAL;
1377 if (CC->quickroom.QRflags & QR_ANONONLY)
1379 if (CC->quickroom.QRflags & QR_ANONOPT) {
1383 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1386 /* If we're only checking the validity of the request, return
1387 * success without creating the message.
1390 cprintf("%d %s\n", OK, buf);
1393 cprintf("%d send message\n", SEND_LISTING);
1394 if (CC->fake_postname[0])
1395 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1396 else if (CC->fake_username[0])
1397 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1399 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1400 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1401 CC->fake_postname[0] = '\0';
1408 * message entry - mode 3 (raw)
1410 void cmd_ent3(char *entargs)
1416 struct usersupp tempUS;
1421 if (CC->internal_pgm == 0) {
1422 cprintf("%d This command is for internal programs only.\n",
1426 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1427 for (a = 0; a < strlen(recp); ++a)
1428 if (!isprint(recp[a]))
1429 strcpy(&recp[a], &recp[a + 1]);
1430 while (isspace(recp[0]))
1431 strcpy(recp, &recp[1]);
1432 while (isspace(recp[strlen(recp) - 1]))
1433 recp[strlen(recp) - 1] = 0;
1435 /* If we're in Mail, check the recipient */
1436 if (strlen(recp) > 0) {
1437 e = alias(recp); /* alias and mail type */
1438 if ((recp[0] == 0) || (e == MES_ERROR)) {
1439 cprintf("%d Unknown address - cannot send message.\n",
1440 ERROR + NO_SUCH_USER);
1443 if (e == MES_LOCAL) {
1444 a = getuser(&tempUS, recp);
1446 cprintf("%d No such user.\n",
1447 ERROR + NO_SUCH_USER);
1452 /* At this point, message has been approved. */
1453 if (extract_int(entargs, 0) == 0) {
1454 cprintf("%d OK to send\n", OK);
1457 /* open a temp file to hold the message */
1458 fp = fopen(CC->temp, "wb");
1460 cprintf("%d Cannot open %s: %s\n",
1461 ERROR + INTERNAL_ERROR,
1462 CC->temp, strerror(errno));
1465 msglen = extract_long(entargs, 2);
1466 cprintf("%d %ld\n", SEND_BINARY, msglen);
1467 while (msglen > 0L) {
1468 bloklen = ((msglen >= 255L) ? 255 : msglen);
1469 client_read(buf, (int) bloklen);
1470 fwrite(buf, (int) bloklen, 1, fp);
1471 msglen = msglen - bloklen;
1475 save_message(CC->temp, recp, "", e, 0);
1480 * API function to delete messages which match a set of criteria
1481 * (returns the actual number of messages deleted)
1482 * FIX ... still need to implement delete by content type
1484 int CtdlDeleteMessages(char *room_name, /* which room */
1485 long dmsgnum, /* or "0" for any */
1486 char *content_type /* or NULL for any */
1490 struct quickroom qrbuf;
1491 struct cdbdata *cdbfr;
1492 long *msglist = NULL;
1495 int num_deleted = 0;
1497 struct SuppMsgInfo smi;
1499 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1500 room_name, dmsgnum, content_type);
1502 /* get room record, obtaining a lock... */
1503 if (lgetroom(&qrbuf, room_name) != 0) {
1504 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1506 return (0); /* room not found */
1508 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1510 lprintf(9, "doing mallok/memcpy loop\n");
1511 if (cdbfr != NULL) {
1512 msglist = mallok(cdbfr->len);
1513 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1514 num_msgs = cdbfr->len / sizeof(long);
1518 for (i = 0; i < num_msgs; ++i) {
1521 /* Set/clear a bit for each criterion */
1523 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1524 delete_this |= 0x01;
1526 if (content_type == NULL) {
1527 delete_this |= 0x02;
1529 GetSuppMsgInfo(&smi, msglist[i]);
1530 if (!strcasecmp(smi.smi_content_type,
1532 delete_this |= 0x02;
1536 /* Delete message only if all bits are set */
1537 if (delete_this == 0x03) {
1538 AdjRefCount(msglist[i], -1);
1544 num_msgs = sort_msglist(msglist, num_msgs);
1545 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1546 msglist, (num_msgs * sizeof(long)));
1548 qrbuf.QRhighest = msglist[num_msgs - 1];
1552 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1553 return (num_deleted);
1559 * Delete message from current room
1561 void cmd_dele(char *delstr)
1566 getuser(&CC->usersupp, CC->curr_user);
1567 if ((CC->usersupp.axlevel < 6)
1568 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1569 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1570 cprintf("%d Higher access required.\n",
1571 ERROR + HIGHER_ACCESS_REQUIRED);
1574 delnum = extract_long(delstr, 0);
1576 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1579 cprintf("%d %d message%s deleted.\n", OK,
1580 num_deleted, ((num_deleted != 1) ? "s" : ""));
1582 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1588 * move a message to another room
1590 void cmd_move(char *args)
1594 struct quickroom qtemp;
1597 num = extract_long(args, 0);
1598 extract(targ, args, 1);
1600 getuser(&CC->usersupp, CC->curr_user);
1601 if ((CC->usersupp.axlevel < 6)
1602 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1603 cprintf("%d Higher access required.\n",
1604 ERROR + HIGHER_ACCESS_REQUIRED);
1607 if (getroom(&qtemp, targ) != 0) {
1608 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1611 /* Bump the reference count, otherwise the message will be deleted
1612 * from disk when we remove it from the source room.
1614 AdjRefCount(num, 1);
1616 /* yank the message out of the current room... */
1617 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1620 /* put the message into the target room */
1621 lgetroom(&qtemp, targ);
1622 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1624 cprintf("%d Message moved.\n", OK);
1626 AdjRefCount(num, (-1)); /* oops */
1627 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1634 * GetSuppMsgInfo() - Get the supplementary record for a message
1636 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1639 struct cdbdata *cdbsmi;
1642 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1643 smibuf->smi_msgnum = msgnum;
1644 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1646 /* Use the negative of the message number for its supp record index */
1647 TheIndex = (0L - msgnum);
1649 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1650 if (cdbsmi == NULL) {
1651 return; /* record not found; go with defaults */
1653 memcpy(smibuf, cdbsmi->ptr,
1654 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1655 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1662 * PutSuppMsgInfo() - (re)write supplementary record for a message
1664 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1668 /* Use the negative of the message number for its supp record index */
1669 TheIndex = (0L - smibuf->smi_msgnum);
1671 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1672 smibuf->smi_msgnum, smibuf->smi_refcount);
1674 cdb_store(CDB_MSGMAIN,
1675 &TheIndex, sizeof(long),
1676 smibuf, sizeof(struct SuppMsgInfo));
1680 * AdjRefCount - change the reference count for a message;
1681 * delete the message if it reaches zero
1682 */ void AdjRefCount(long msgnum, int incr)
1685 struct SuppMsgInfo smi;
1688 /* This is a *tight* critical section; please keep it that way, as
1689 * it may get called while nested in other critical sections.
1690 * Complicating this any further will surely cause deadlock!
1692 begin_critical_section(S_SUPPMSGMAIN);
1693 GetSuppMsgInfo(&smi, msgnum);
1694 smi.smi_refcount += incr;
1695 PutSuppMsgInfo(&smi);
1696 end_critical_section(S_SUPPMSGMAIN);
1698 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1699 msgnum, smi.smi_refcount);
1701 /* If the reference count is now zero, delete the message
1702 * (and its supplementary record as well).
1704 if (smi.smi_refcount == 0) {
1705 lprintf(9, "Deleting message <%ld>\n", msgnum);
1707 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1708 delnum = (0L - msgnum);
1709 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1714 * Write a generic object to this room
1716 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1717 char *content_type, /* MIME type of this object */
1718 char *tempfilename, /* Where to fetch it from */
1719 int is_mailbox, /* Private mailbox room? */
1720 int is_binary, /* Is encoding necessary? */
1721 int is_unique /* Del others of this type? */
1726 char filename[PATH_MAX];
1729 struct quickroom qrbuf;
1730 char roomname[ROOMNAMELEN];
1733 MailboxName(roomname, &CC->usersupp, req_room);
1735 safestrncpy(roomname, req_room, sizeof(roomname));
1737 strcpy(filename, tmpnam(NULL));
1738 fp = fopen(filename, "w");
1742 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1743 fprintf(fp, "T%ld%c", time(NULL), 0);
1744 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1745 fprintf(fp, "O%s%c", roomname, 0);
1746 fprintf(fp, "N%s%c", config.c_nodename, 0);
1747 fprintf(fp, "MContent-type: %s\n", content_type);
1749 tempfp = fopen(tempfilename, "r");
1750 if (tempfp == NULL) {
1755 if (is_binary == 0) {
1756 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1757 while (ch = getc(tempfp), ch > 0)
1763 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1766 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1767 tempfilename, filename);
1771 /* Create the requested room if we have to. */
1772 if (getroom(&qrbuf, roomname) != 0) {
1773 create_room(roomname, 4, "", 0);
1775 /* If the caller specified this object as unique, delete all
1776 * other objects of this type that are currently in the room.
1779 lprintf(9, "Deleted %d other msgs of this type\n",
1780 CtdlDeleteMessages(roomname, 0L, content_type));
1782 /* Now write the data */
1783 save_message(filename, "", roomname, MES_LOCAL, 1);