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 * Now iterate through the message list, according to the
233 * criteria supplied by the caller.
236 for (a = 0; a < num_msgs; ++a) {
237 thismsg = msglist[a];
242 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
243 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
244 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
245 && (CC->usersupp.flags & US_LASTOLD))
246 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
247 || ((mode == MSGS_FIRST) && (a < ref))
248 || ((mode == MSGS_GT) && (thismsg > ref))
254 phree(msglist); /* Clean up */
260 * cmd_msgs() - get list of message #'s in this room
261 * implements the MSGS server command using CtdlForEachMessage()
263 void cmd_msgs(char *cmdbuf)
269 extract(which, cmdbuf, 0);
270 cm_ref = extract_int(cmdbuf, 1);
274 if (!strncasecmp(which, "OLD", 3))
276 else if (!strncasecmp(which, "NEW", 3))
278 else if (!strncasecmp(which, "FIRST", 5))
280 else if (!strncasecmp(which, "LAST", 4))
282 else if (!strncasecmp(which, "GT", 2))
285 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
286 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
289 cprintf("%d Message list...\n", LISTING_FOLLOWS);
290 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
298 * help_subst() - support routine for help file viewer
300 void help_subst(char *strbuf, char *source, char *dest)
305 while (p = pattern2(strbuf, source), (p >= 0)) {
306 strcpy(workbuf, &strbuf[p + strlen(source)]);
307 strcpy(&strbuf[p], dest);
308 strcat(strbuf, workbuf);
313 void do_help_subst(char *buffer)
317 help_subst(buffer, "^nodename", config.c_nodename);
318 help_subst(buffer, "^humannode", config.c_humannode);
319 help_subst(buffer, "^fqdn", config.c_fqdn);
320 help_subst(buffer, "^username", CC->usersupp.fullname);
321 sprintf(buf2, "%ld", CC->usersupp.usernum);
322 help_subst(buffer, "^usernum", buf2);
323 help_subst(buffer, "^sysadm", config.c_sysadm);
324 help_subst(buffer, "^variantname", CITADEL);
325 sprintf(buf2, "%d", config.c_maxsessions);
326 help_subst(buffer, "^maxsessions", buf2);
332 * memfmout() - Citadel text formatter and paginator.
333 * Although the original purpose of this routine was to format
334 * text to the reader's screen width, all we're really using it
335 * for here is to format text out to 80 columns before sending it
336 * to the client. The client software may reformat it again.
338 void memfmout(int width, char *mptr, char subst)
339 /* screen width to use */
340 /* where are we going to get our text from? */
341 /* nonzero if we should use hypertext mode */
353 c = 1; /* c is the current pos */
356 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
358 buffer[strlen(buffer) + 1] = 0;
359 buffer[strlen(buffer)] = ch;
362 if (buffer[0] == '^')
363 do_help_subst(buffer);
365 buffer[strlen(buffer) + 1] = 0;
367 strcpy(buffer, &buffer[1]);
376 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
378 if (((old == 13) || (old == 10)) && (isspace(real))) {
386 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
387 cprintf("\n%s", aaa);
396 if ((strlen(aaa) + c) > (width - 5)) {
406 if ((ch == 13) || (ch == 10)) {
407 cprintf("%s\n", aaa);
414 FMTEND:cprintf("%s\n", aaa);
420 * Callback function for mime parser that simply lists the part
422 void list_this_part(char *name, char *filename, char *partnum, char *disp,
423 void *content, char *cbtype, size_t length)
426 cprintf("part=%s|%s|%s|%s|%s|%d\n",
427 name, filename, partnum, disp, cbtype, length);
432 * Callback function for mime parser that wants to display text
434 void fixed_output(char *name, char *filename, char *partnum, char *disp,
435 void *content, char *cbtype, size_t length)
438 if (!strcasecmp(cbtype, "text/plain")) {
439 client_write(content, length);
441 cprintf("Part %s: %s (%s) (%d bytes)\n",
442 partnum, filename, cbtype, length);
448 * Callback function for mime parser that opens a section for downloading
450 void mime_download(char *name, char *filename, char *partnum, char *disp,
451 void *content, char *cbtype, size_t length)
454 /* Silently go away if there's already a download open... */
455 if (CC->download_fp != NULL)
458 /* ...or if this is not the desired section */
459 if (strcasecmp(desired_section, partnum))
462 CC->download_fp = tmpfile();
463 if (CC->download_fp == NULL)
466 fwrite(content, length, 1, CC->download_fp);
467 fflush(CC->download_fp);
468 rewind(CC->download_fp);
470 OpenCmdResult(filename, cbtype);
476 * Load a message from disk into memory.
477 * (This will replace a big piece of output_message() eventually)
479 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
480 * using the CtdlMessageFree() function.
482 struct CtdlMessage *CtdlFetchMessage(long msgnum)
484 struct cdbdata *dmsgtext;
485 struct CtdlMessage *ret = NULL;
488 CIT_UBYTE field_header;
492 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
493 if (dmsgtext == NULL) {
494 lprintf(9, "CtdlMessage(%ld) failed.\n");
497 mptr = dmsgtext->ptr;
499 /* Parse the three bytes that begin EVERY message on disk.
500 * The first is always 0xFF, the on-disk magic number.
501 * The second is the anonymous/public type byte.
502 * The third is the format type byte (vari, fixed, or MIME).
506 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
510 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
511 memset(ret, 0, sizeof(struct CtdlMessage));
513 ret->cm_magic = CTDLMESSAGE_MAGIC;
514 ret->cm_anon_type = *mptr++; /* Anon type byte */
515 ret->cm_format_type = *mptr++; /* Format type byte */
518 * The rest is zero or more arbitrary fields. Load them in.
519 * We're done when we encounter either a zero-length field or
520 * have just processed the 'M' (message text) field.
523 field_length = strlen(mptr);
524 if (field_length == 0)
526 field_header = *mptr++;
527 ret->cm_fields[field_header] = mallok(field_length);
528 strcpy(ret->cm_fields[field_header], mptr);
530 while (*mptr++ != 0); /* advance to next field */
532 } while ((field_length > 0) && (field_header != 'M'));
539 * 'Destructor' for struct CtdlMessage
541 void CtdlFreeMessage(struct CtdlMessage *msg)
547 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
548 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
551 for (i = 0; i < 256; ++i)
552 if (msg->cm_fields[i] != NULL)
553 phree(msg->cm_fields[i]);
561 * Get a message off disk. (return value is the message's timestamp)
564 void output_message(char *msgid, int mode, int headers_only)
572 struct CtdlMessage *TheMessage = NULL;
576 /* buffers needed for RFC822 translation */
584 msg_num = atol(msgid);
586 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
587 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
590 /* FIX ... small security issue
591 * We need to check to make sure the requested message is actually
592 * in the current room, and set msg_ok to 1 only if it is. This
593 * functionality is currently missing because I'm in a hurry to replace
594 * broken production code with nonbroken pre-beta code. :( -- ajc
597 cprintf("%d Message %ld is not in this room.\n",
604 * Fetch the message from disk
606 TheMessage = CtdlFetchMessage(msg_num);
607 if (TheMessage == NULL) {
608 cprintf("%d Can't locate message %ld on disk\n", ERROR, msg_num);
612 /* Are we downloading a MIME component? */
613 if (mode == MT_DOWNLOAD) {
614 if (TheMessage->cm_format_type != 4) {
615 cprintf("%d This is not a MIME message.\n",
617 } else if (CC->download_fp != NULL) {
618 cprintf("%d You already have a download open.\n",
621 /* Parse the message text component */
622 mptr = TheMessage->cm_fields['M'];
623 mime_parser(mptr, NULL, *mime_download);
624 /* If there's no file open by this time, the requested
625 * section wasn't found, so print an error
627 if (CC->download_fp == NULL) {
628 cprintf("%d Section %s not found.\n",
629 ERROR + FILE_NOT_FOUND,
633 CtdlFreeMessage(TheMessage);
636 /* now for the user-mode message reading loops */
637 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
639 if (mode == MT_CITADEL)
640 cprintf("type=%d\n", TheMessage->cm_format_type);
642 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
643 cprintf("nhdr=yes\n");
645 /* begin header processing loop for Citadel message format */
647 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
649 if (TheMessage->cm_fields['P']) {
650 cprintf("path=%s\n", TheMessage->cm_fields['P']);
652 if (TheMessage->cm_fields['I']) {
653 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
655 if (TheMessage->cm_fields['T']) {
656 cprintf("time=%s\n", TheMessage->cm_fields['T']);
658 if (TheMessage->cm_fields['A']) {
659 strcpy(buf, TheMessage->cm_fields['A']);
660 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
661 if (TheMessage->cm_anon_type == MES_ANON)
662 cprintf("from=****");
663 else if (TheMessage->cm_anon_type == MES_AN2)
664 cprintf("from=anonymous");
666 cprintf("from=%s", buf);
668 && ((TheMessage->cm_anon_type == MES_ANON)
669 || (TheMessage->cm_anon_type == MES_AN2))) {
670 cprintf(" [%s]", buf);
674 if (TheMessage->cm_fields['O']) {
675 cprintf("room=%s\n", TheMessage->cm_fields['O']);
677 if (TheMessage->cm_fields['N']) {
678 cprintf("node=%s\n", TheMessage->cm_fields['N']);
680 if (TheMessage->cm_fields['H']) {
681 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
683 if (TheMessage->cm_fields['R']) {
684 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
686 if (TheMessage->cm_fields['U']) {
687 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
690 /* begin header processing loop for RFC822 transfer format */
694 strcpy(snode, NODENAME);
695 strcpy(lnode, HUMANNODE);
696 if (mode == MT_RFC822) {
697 for (i = 0; i < 256; ++i) {
698 if (TheMessage->cm_fields[i]) {
699 mptr = TheMessage->cm_fields[i];
703 } else if (i == 'P') {
704 cprintf("Path: %s\n", mptr);
705 for (a = 0; a < strlen(mptr); ++a) {
706 if (mptr[a] == '!') {
707 strcpy(mptr, &mptr[a + 1]);
713 cprintf("Subject: %s\n", mptr);
719 cprintf("X-Citadel-Room: %s\n", mptr);
723 cprintf("To: %s\n", mptr);
726 cprintf("Date: %s", asctime(localtime(&xtime)));
731 if (mode == MT_RFC822) {
732 if (!strcasecmp(snode, NODENAME)) {
735 cprintf("Message-ID: <%s@%s>\n", mid, snode);
736 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
737 cprintf("From: %s@%s (%s)\n",
738 suser, snode, luser);
739 cprintf("Organization: %s\n", lnode);
741 /* end header processing loop ... at this point, we're in the text */
743 mptr = TheMessage->cm_fields['M'];
745 /* do some sort of MIME output */
746 if (TheMessage->cm_format_type == 4) {
747 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
748 mime_parser(mptr, NULL, *list_this_part);
750 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
752 CtdlFreeMessage(TheMessage);
758 CtdlFreeMessage(TheMessage);
761 /* signify start of msg text */
762 if (mode == MT_CITADEL)
764 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
767 /* If the format type on disk is 1 (fixed-format), then we want
768 * everything to be output completely literally ... regardless of
769 * what message transfer format is in use.
771 if (TheMessage->cm_format_type == 1) {
773 while (ch = *mptr++, ch > 0) {
776 if ((ch == 10) || (strlen(buf) > 250)) {
777 cprintf("%s\n", buf);
780 buf[strlen(buf) + 1] = 0;
781 buf[strlen(buf)] = ch;
785 cprintf("%s\n", buf);
787 /* If the message on disk is format 0 (Citadel vari-format), we
788 * output using the formatter at 80 columns. This is the final output
789 * form if the transfer format is RFC822, but if the transfer format
790 * is Citadel proprietary, it'll still work, because the indentation
791 * for new paragraphs is correct and the client will reformat the
792 * message to the reader's screen width.
794 if (TheMessage->cm_format_type == 0) {
795 memfmout(80, mptr, 0);
797 /* If the message on disk is format 4 (MIME), we've gotta hand it
798 * off to the MIME parser. The client has already been told that
799 * this message is format 1 (fixed format), so the callback function
800 * we use will display those parts as-is.
802 if (TheMessage->cm_format_type == 4) {
803 mime_parser(mptr, NULL, *fixed_output);
807 CtdlFreeMessage(TheMessage);
814 * display a message (mode 0 - Citadel proprietary)
816 void cmd_msg0(char *cmdbuf)
819 int headers_only = 0;
821 extract(msgid, cmdbuf, 0);
822 headers_only = extract_int(cmdbuf, 1);
824 output_message(msgid, MT_CITADEL, headers_only);
830 * display a message (mode 2 - RFC822)
832 void cmd_msg2(char *cmdbuf)
835 int headers_only = 0;
837 extract(msgid, cmdbuf, 0);
838 headers_only = extract_int(cmdbuf, 1);
840 output_message(msgid, MT_RFC822, headers_only);
846 * display a message (mode 3 - IGnet raw format - internal programs only)
848 void cmd_msg3(char *cmdbuf)
851 struct cdbdata *dmsgtext;
853 if (CC->internal_pgm == 0) {
854 cprintf("%d This command is for internal programs only.\n",
859 msgnum = extract_long(cmdbuf, 0);
861 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
862 if (dmsgtext == NULL) {
863 cprintf("%d Message %ld not found\n", ERROR, msgnum);
866 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
867 client_write(dmsgtext->ptr, dmsgtext->len);
874 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
876 void cmd_msg4(char *cmdbuf)
880 extract(msgid, cmdbuf, 0);
882 output_message(msgid, MT_MIME, 0);
886 * Open a component of a MIME message as a download file
888 void cmd_opna(char *cmdbuf)
892 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
894 extract(msgid, cmdbuf, 0);
895 extract(desired_section, cmdbuf, 1);
897 output_message(msgid, MT_DOWNLOAD, 0);
901 * Message base operation to send a message to the master file
902 * (returns new message number)
904 long send_message(char *message_in_memory,
905 /* pointer to buffer */
906 size_t message_length, /* length of buffer */
908 { /* 1 to generate an I field */
911 char *actual_message;
912 size_t actual_length;
916 /* Get a new message number */
917 newmsgid = get_new_message_number();
920 sprintf(msgidbuf, "I%ld", newmsgid);
921 actual_length = message_length + strlen(msgidbuf) + 1;
922 actual_message = mallok(actual_length);
923 memcpy(actual_message, message_in_memory, 3);
924 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
925 memcpy(&actual_message[strlen(msgidbuf) + 4],
926 &message_in_memory[3], message_length - 3);
928 actual_message = message_in_memory;
929 actual_length = message_length;
932 /* Write our little bundle of joy into the message base */
933 begin_critical_section(S_MSGMAIN);
934 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
935 actual_message, actual_length) < 0) {
936 lprintf(2, "Can't store message\n");
941 end_critical_section(S_MSGMAIN);
944 phree(actual_message);
946 /* Finally, return the pointers */
953 * this is a simple file copy routine.
955 void copy_file(char *from, char *to)
960 ffp = fopen(from, "r");
963 tfp = fopen(to, "w");
968 while (a = getc(ffp), a >= 0) {
979 * message base operation to save a message and install its pointers
981 void save_message(char *mtmp, /* file containing proper message */
982 char *rec, /* Recipient (if mail) */
983 char *force, /* if non-zero length, force a room */
984 int mailtype, /* local or remote type, see citadel.h */
986 { /* set to 1 to generate an 'I' field */
988 char hold_rm[ROOMNAMELEN];
989 char actual_rm[ROOMNAMELEN];
990 char force_room[ROOMNAMELEN];
991 char content_type[256]; /* We have to learn this */
995 char *message_in_memory;
1000 struct usersupp userbuf;
1002 static int seqnum = 0;
1003 int successful_local_recipients = 0;
1004 struct quickroom qtemp;
1005 struct SuppMsgInfo smi;
1007 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1008 mtmp, rec, force, mailtype, generate_id);
1010 strcpy(force_room, force);
1012 /* Strip non-printable characters out of the recipient name */
1013 strcpy(recipient, rec);
1014 for (a = 0; a < strlen(recipient); ++a)
1015 if (!isprint(recipient[a]))
1016 strcpy(&recipient[a], &recipient[a + 1]);
1018 /* Measure the message */
1019 stat(mtmp, &statbuf);
1020 templen = statbuf.st_size;
1022 /* Now read it into memory */
1023 message_in_memory = (char *) mallok(templen);
1024 if (message_in_memory == NULL) {
1025 lprintf(2, "Can't allocate memory to save message!\n");
1028 fp = fopen(mtmp, "rb");
1029 fread(message_in_memory, templen, 1, fp);
1032 /* Learn about what's inside, because it's what's inside that counts */
1033 mptr = message_in_memory;
1034 ++mptr; /* advance past 0xFF header */
1035 ++mptr; /* advance past anon flag */
1039 strcpy(content_type, "text/x-citadel-variformat");
1042 strcpy(content_type, "text/plain");
1045 strcpy(content_type, "text/plain");
1046 /* advance past header fields */
1047 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1054 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1055 safestrncpy(content_type, mptr,
1056 sizeof(content_type));
1057 lprintf(9, "%s\n", content_type);
1058 strcpy(content_type, &content_type[14]);
1059 for (a = 0; a < strlen(content_type); ++a)
1060 if ((content_type[a] == ';')
1061 || (content_type[a] == ' ')
1062 || (content_type[a] == 13)
1063 || (content_type[a] == 10))
1064 content_type[a] = 0;
1070 lprintf(9, "Content type is <%s>\n", content_type);
1072 /* Save it to disk */
1073 newmsgid = send_message(message_in_memory, templen, generate_id);
1074 phree(message_in_memory);
1078 strcpy(actual_rm, CC->quickroom.QRname);
1079 strcpy(hold_rm, "");
1081 /* If this is being done by the networker delivering a private
1082 * message, we want to BYPASS saving the sender's copy (because there
1083 * is no local sender; it would otherwise go to the Trashcan).
1085 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1086 /* If the user is a twit, move to the twit room for posting */
1088 if (CC->usersupp.axlevel == 2) {
1089 strcpy(hold_rm, actual_rm);
1090 strcpy(actual_rm, config.c_twitroom);
1092 /* ...or if this message is destined for Aide> then go there. */
1093 lprintf(9, "actual room forcing loop\n");
1094 if (strlen(force_room) > 0) {
1095 strcpy(hold_rm, actual_rm);
1096 strcpy(actual_rm, force_room);
1098 /* This call to usergoto() changes rooms if necessary. It also
1099 * causes the latest message list to be read into memory.
1101 usergoto(actual_rm, 0);
1103 /* read in the quickroom record, obtaining a lock... */
1104 lgetroom(&CC->quickroom, actual_rm);
1106 /* Fix an obscure bug */
1107 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1108 CC->quickroom.QRflags =
1109 CC->quickroom.QRflags & ~QR_MAILBOX;
1111 /* Add the message pointer to the room */
1112 CC->quickroom.QRhighest =
1113 AddMessageToRoom(&CC->quickroom, newmsgid);
1115 /* update quickroom */
1116 lputroom(&CC->quickroom);
1117 ++successful_local_recipients;
1119 /* Network mail - send a copy to the network program. */
1120 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1121 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1122 (long) getpid(), CC->cs_pid, ++seqnum);
1123 copy_file(mtmp, aaa);
1124 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1126 /* Bump this user's messages posted counter. */
1127 lgetuser(&CC->usersupp, CC->curr_user);
1128 CC->usersupp.posted = CC->usersupp.posted + 1;
1129 lputuser(&CC->usersupp);
1131 /* If this is private, local mail, make a copy in the
1132 * recipient's mailbox and bump the reference count.
1134 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1135 if (getuser(&userbuf, recipient) == 0) {
1136 MailboxName(actual_rm, &userbuf, MAILROOM);
1137 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1138 if (lgetroom(&qtemp, actual_rm) == 0) {
1140 AddMessageToRoom(&qtemp, newmsgid);
1142 ++successful_local_recipients;
1146 /* If we've posted in a room other than the current room, then we
1147 * have to now go back to the current room...
1149 if (strlen(hold_rm) > 0) {
1150 usergoto(hold_rm, 0);
1152 unlink(mtmp); /* delete the temporary file */
1154 /* Write a supplemental message info record. This doesn't have to
1155 * be a critical section because nobody else knows about this message
1158 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1159 smi.smi_msgnum = newmsgid;
1160 smi.smi_refcount = successful_local_recipients;
1161 safestrncpy(smi.smi_content_type, content_type, 64);
1162 PutSuppMsgInfo(&smi);
1167 * Generate an administrative message and post it in the Aide> room.
1169 void aide_message(char *text)
1173 fp = fopen(CC->temp, "wb");
1174 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1175 fprintf(fp, "Psysop%c", 0);
1176 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1177 fprintf(fp, "ACitadel%c", 0);
1178 fprintf(fp, "OAide%c", 0);
1179 fprintf(fp, "N%s%c", NODENAME, 0);
1180 fprintf(fp, "M%s\n%c", text, 0);
1182 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1183 syslog(LOG_NOTICE, text);
1186 * Build a binary message to be saved on disk.
1187 */ void make_message(
1188 char *filename, /* temporary file name */
1189 struct usersupp *author, /* author's usersupp structure */
1190 char *recipient, /* NULL if it's not mail */
1191 char *room, /* room where it's going */
1192 int type, /* see MES_ types in header file */
1193 int net_type, /* see MES_ types in header file */
1194 int format_type, /* local or remote (see citadel.h) */
1196 { /* who we're masquerading as */
1204 /* Don't confuse the poor folks if it's not routed mail. */
1205 strcpy(dest_node, "");
1208 /* If net_type is MES_BINARY, split out the destination node. */
1209 if (net_type == MES_BINARY) {
1210 strcpy(dest_node, NODENAME);
1211 for (a = 0; a < strlen(recipient); ++a) {
1212 if (recipient[a] == '@') {
1214 strcpy(dest_node, &recipient[a + 1]);
1218 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1219 strcpy(dest_node, "internet");
1221 while (isspace(recipient[strlen(recipient) - 1]))
1222 recipient[strlen(recipient) - 1] = 0;
1225 fp = fopen(filename, "w");
1227 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1228 putc(format_type, fp); /* Formatted or unformatted */
1229 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1230 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1232 fprintf(fp, "A%s%c", fake_name, 0);
1234 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1236 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1237 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1239 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1242 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1243 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1245 if (recipient[0] != 0)
1246 fprintf(fp, "R%s%c", recipient, 0);
1247 if (dest_node[0] != 0)
1248 fprintf(fp, "D%s%c", dest_node, 0);
1252 while (client_gets(buf), strcmp(buf, "000")) {
1253 fprintf(fp, "%s\n", buf);
1264 * message entry - mode 0 (normal) <bc>
1266 void cmd_ent0(char *entargs)
1269 char recipient[256];
1271 int format_type = 0;
1272 char newusername[256]; /* <bc> */
1277 struct usersupp tempUS;
1280 post = extract_int(entargs, 0);
1281 extract(recipient, entargs, 1);
1282 anon_flag = extract_int(entargs, 2);
1283 format_type = extract_int(entargs, 3);
1285 /* first check to make sure the request is valid. */
1287 if (!(CC->logged_in)) {
1288 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1291 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1292 cprintf("%d Need to be validated to enter ",
1293 ERROR + HIGHER_ACCESS_REQUIRED);
1294 cprintf("(except in %s> to sysop)\n", MAILROOM);
1297 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1298 cprintf("%d Need net privileges to enter here.\n",
1299 ERROR + HIGHER_ACCESS_REQUIRED);
1302 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1303 cprintf("%d Sorry, this is a read-only room.\n",
1304 ERROR + HIGHER_ACCESS_REQUIRED);
1310 if (post == 2) { /* <bc> */
1311 if (CC->usersupp.axlevel < 6) {
1312 cprintf("%d You don't have permission to do an aide post.\n",
1313 ERROR + HIGHER_ACCESS_REQUIRED);
1316 extract(newusername, entargs, 4);
1317 memset(CC->fake_postname, 0, 32);
1318 strcpy(CC->fake_postname, newusername);
1319 cprintf("%d Ok\n", OK);
1322 CC->cs_flags |= CS_POSTING;
1325 if (CC->quickroom.QRflags & QR_MAILBOX) {
1326 if (CC->usersupp.axlevel >= 2) {
1327 strcpy(buf, recipient);
1329 strcpy(buf, "sysop");
1330 lprintf(9, "calling alias()\n");
1331 e = alias(buf); /* alias and mail type */
1332 lprintf(9, "alias() returned %d\n", e);
1333 if ((buf[0] == 0) || (e == MES_ERROR)) {
1334 cprintf("%d Unknown address - cannot send message.\n",
1335 ERROR + NO_SUCH_USER);
1338 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1339 cprintf("%d Net privileges required for network mail.\n",
1340 ERROR + HIGHER_ACCESS_REQUIRED);
1343 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1344 && ((CC->usersupp.flags & US_INTERNET) == 0)
1345 && (!CC->internal_pgm)) {
1346 cprintf("%d You don't have access to Internet mail.\n",
1347 ERROR + HIGHER_ACCESS_REQUIRED);
1350 if (!strcasecmp(buf, "sysop")) {
1355 goto SKFALL; /* don't search local file */
1356 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1357 cprintf("%d Can't send mail to yourself!\n",
1358 ERROR + NO_SUCH_USER);
1361 /* Check to make sure the user exists; also get the correct
1362 * upper/lower casing of the name.
1364 a = getuser(&tempUS, buf);
1366 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1369 strcpy(buf, tempUS.fullname);
1371 SKFALL:b = MES_NORMAL;
1372 if (CC->quickroom.QRflags & QR_ANONONLY)
1374 if (CC->quickroom.QRflags & QR_ANONOPT) {
1378 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1381 /* If we're only checking the validity of the request, return
1382 * success without creating the message.
1385 cprintf("%d %s\n", OK, buf);
1388 cprintf("%d send message\n", SEND_LISTING);
1389 if (CC->fake_postname[0])
1390 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1391 else if (CC->fake_username[0])
1392 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1394 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1395 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1396 CC->fake_postname[0] = '\0';
1403 * message entry - mode 3 (raw)
1405 void cmd_ent3(char *entargs)
1411 struct usersupp tempUS;
1416 if (CC->internal_pgm == 0) {
1417 cprintf("%d This command is for internal programs only.\n",
1421 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1422 for (a = 0; a < strlen(recp); ++a)
1423 if (!isprint(recp[a]))
1424 strcpy(&recp[a], &recp[a + 1]);
1425 while (isspace(recp[0]))
1426 strcpy(recp, &recp[1]);
1427 while (isspace(recp[strlen(recp) - 1]))
1428 recp[strlen(recp) - 1] = 0;
1430 /* If we're in Mail, check the recipient */
1431 if (strlen(recp) > 0) {
1432 e = alias(recp); /* alias and mail type */
1433 if ((recp[0] == 0) || (e == MES_ERROR)) {
1434 cprintf("%d Unknown address - cannot send message.\n",
1435 ERROR + NO_SUCH_USER);
1438 if (e == MES_LOCAL) {
1439 a = getuser(&tempUS, recp);
1441 cprintf("%d No such user.\n",
1442 ERROR + NO_SUCH_USER);
1447 /* At this point, message has been approved. */
1448 if (extract_int(entargs, 0) == 0) {
1449 cprintf("%d OK to send\n", OK);
1452 /* open a temp file to hold the message */
1453 fp = fopen(CC->temp, "wb");
1455 cprintf("%d Cannot open %s: %s\n",
1456 ERROR + INTERNAL_ERROR,
1457 CC->temp, strerror(errno));
1460 msglen = extract_long(entargs, 2);
1461 cprintf("%d %ld\n", SEND_BINARY, msglen);
1462 while (msglen > 0L) {
1463 bloklen = ((msglen >= 255L) ? 255 : msglen);
1464 client_read(buf, (int) bloklen);
1465 fwrite(buf, (int) bloklen, 1, fp);
1466 msglen = msglen - bloklen;
1470 save_message(CC->temp, recp, "", e, 0);
1475 * API function to delete messages which match a set of criteria
1476 * (returns the actual number of messages deleted)
1477 * FIX ... still need to implement delete by content type
1479 int CtdlDeleteMessages(char *room_name, /* which room */
1480 long dmsgnum, /* or "0" for any */
1481 char *content_type /* or NULL for any */
1485 struct quickroom qrbuf;
1486 struct cdbdata *cdbfr;
1487 long *msglist = NULL;
1490 int num_deleted = 0;
1492 struct SuppMsgInfo smi;
1494 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1495 room_name, dmsgnum, content_type);
1497 /* get room record, obtaining a lock... */
1498 if (lgetroom(&qrbuf, room_name) != 0) {
1499 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1501 return (0); /* room not found */
1503 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1505 lprintf(9, "doing mallok/memcpy loop\n");
1506 if (cdbfr != NULL) {
1507 msglist = mallok(cdbfr->len);
1508 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1509 num_msgs = cdbfr->len / sizeof(long);
1513 for (i = 0; i < num_msgs; ++i) {
1516 /* Set/clear a bit for each criterion */
1518 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1519 delete_this |= 0x01;
1521 if (content_type == NULL) {
1522 delete_this |= 0x02;
1524 GetSuppMsgInfo(&smi, msglist[i]);
1525 if (!strcasecmp(smi.smi_content_type,
1527 delete_this |= 0x02;
1531 /* Delete message only if all bits are set */
1532 if (delete_this == 0x03) {
1533 AdjRefCount(msglist[i], -1);
1539 num_msgs = sort_msglist(msglist, num_msgs);
1540 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1541 msglist, (num_msgs * sizeof(long)));
1543 qrbuf.QRhighest = msglist[num_msgs - 1];
1547 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1548 return (num_deleted);
1554 * Delete message from current room
1556 void cmd_dele(char *delstr)
1561 getuser(&CC->usersupp, CC->curr_user);
1562 if ((CC->usersupp.axlevel < 6)
1563 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1564 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1565 cprintf("%d Higher access required.\n",
1566 ERROR + HIGHER_ACCESS_REQUIRED);
1569 delnum = extract_long(delstr, 0);
1571 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1574 cprintf("%d %d message%s deleted.\n", OK,
1575 num_deleted, ((num_deleted != 1) ? "s" : ""));
1577 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1583 * move a message to another room
1585 void cmd_move(char *args)
1589 struct quickroom qtemp;
1592 num = extract_long(args, 0);
1593 extract(targ, args, 1);
1595 getuser(&CC->usersupp, CC->curr_user);
1596 if ((CC->usersupp.axlevel < 6)
1597 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1598 cprintf("%d Higher access required.\n",
1599 ERROR + HIGHER_ACCESS_REQUIRED);
1602 if (getroom(&qtemp, targ) != 0) {
1603 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1606 /* Bump the reference count, otherwise the message will be deleted
1607 * from disk when we remove it from the source room.
1609 AdjRefCount(num, 1);
1611 /* yank the message out of the current room... */
1612 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1615 /* put the message into the target room */
1616 lgetroom(&qtemp, targ);
1617 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1619 cprintf("%d Message moved.\n", OK);
1621 AdjRefCount(num, (-1)); /* oops */
1622 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1629 * GetSuppMsgInfo() - Get the supplementary record for a message
1631 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1634 struct cdbdata *cdbsmi;
1637 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1638 smibuf->smi_msgnum = msgnum;
1639 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1641 /* Use the negative of the message number for its supp record index */
1642 TheIndex = (0L - msgnum);
1644 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1645 if (cdbsmi == NULL) {
1646 return; /* record not found; go with defaults */
1648 memcpy(smibuf, cdbsmi->ptr,
1649 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1650 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1657 * PutSuppMsgInfo() - (re)write supplementary record for a message
1659 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1663 /* Use the negative of the message number for its supp record index */
1664 TheIndex = (0L - smibuf->smi_msgnum);
1666 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1667 smibuf->smi_msgnum, smibuf->smi_refcount);
1669 cdb_store(CDB_MSGMAIN,
1670 &TheIndex, sizeof(long),
1671 smibuf, sizeof(struct SuppMsgInfo));
1675 * AdjRefCount - change the reference count for a message;
1676 * delete the message if it reaches zero
1677 */ void AdjRefCount(long msgnum, int incr)
1680 struct SuppMsgInfo smi;
1683 /* This is a *tight* critical section; please keep it that way, as
1684 * it may get called while nested in other critical sections.
1685 * Complicating this any further will surely cause deadlock!
1687 begin_critical_section(S_SUPPMSGMAIN);
1688 GetSuppMsgInfo(&smi, msgnum);
1689 smi.smi_refcount += incr;
1690 PutSuppMsgInfo(&smi);
1691 end_critical_section(S_SUPPMSGMAIN);
1693 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1694 msgnum, smi.smi_refcount);
1696 /* If the reference count is now zero, delete the message
1697 * (and its supplementary record as well).
1699 if (smi.smi_refcount == 0) {
1700 lprintf(9, "Deleting message <%ld>\n", msgnum);
1702 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1703 delnum = (0L - msgnum);
1704 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1709 * Write a generic object to this room
1711 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1712 char *content_type, /* MIME type of this object */
1713 char *tempfilename, /* Where to fetch it from */
1714 int is_mailbox, /* Private mailbox room? */
1715 int is_binary, /* Is encoding necessary? */
1716 int is_unique /* Del others of this type? */
1721 char filename[PATH_MAX];
1724 struct quickroom qrbuf;
1725 char roomname[ROOMNAMELEN];
1728 MailboxName(roomname, &CC->usersupp, req_room);
1730 safestrncpy(roomname, req_room, sizeof(roomname));
1732 strcpy(filename, tmpnam(NULL));
1733 fp = fopen(filename, "w");
1737 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1738 fprintf(fp, "T%ld%c", time(NULL), 0);
1739 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1740 fprintf(fp, "O%s%c", roomname, 0);
1741 fprintf(fp, "N%s%c", config.c_nodename, 0);
1742 fprintf(fp, "MContent-type: %s\n", content_type);
1744 tempfp = fopen(tempfilename, "r");
1745 if (tempfp == NULL) {
1750 if (is_binary == 0) {
1751 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1752 while (ch = getc(tempfp), ch > 0)
1758 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1761 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1762 tempfilename, filename);
1766 /* Create the requested room if we have to. */
1767 if (getroom(&qrbuf, roomname) != 0) {
1768 create_room(roomname, 4, "", 0);
1770 /* If the caller specified this object as unique, delete all
1771 * other objects of this type that are currently in the room.
1774 lprintf(9, "Deleted %d other msgs of this type\n",
1775 CtdlDeleteMessages(roomname, 0L, content_type));
1777 /* Now write the data */
1778 save_message(filename, "", roomname, MES_LOCAL, 1);