22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
39 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
41 extern struct config config;
45 * This function is self explanatory.
46 * (What can I say, I'm in a weird mood today...)
48 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
52 for (i = 0; i < strlen(name); ++i)
55 if (isspace(name[i - 1])) {
56 strcpy(&name[i - 1], &name[i]);
59 while (isspace(name[i + 1])) {
60 strcpy(&name[i + 1], &name[i + 2]);
67 * Aliasing for network mail.
68 * (Error messages have been commented out, because this is a server.)
71 { /* process alias and routing info for mail */
74 char aaa[300], bbb[300];
76 lprintf(9, "alias() called for <%s>\n", name);
78 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
80 fp = fopen("network/mail.aliases", "r");
82 fp = fopen("/dev/null", "r");
87 while (fgets(aaa, sizeof aaa, fp) != NULL) {
88 while (isspace(name[0]))
89 strcpy(name, &name[1]);
90 aaa[strlen(aaa) - 1] = 0;
92 for (a = 0; a < strlen(aaa); ++a) {
94 strcpy(bbb, &aaa[a + 1]);
98 if (!strcasecmp(name, aaa))
102 lprintf(7, "Mail is being forwarded to %s\n", name);
104 /* determine local or remote type, see citadel.h */
105 for (a = 0; a < strlen(name); ++a)
107 return (MES_INTERNET);
108 for (a = 0; a < strlen(name); ++a)
110 for (b = a; b < strlen(name); ++b)
112 return (MES_INTERNET);
114 for (a = 0; a < strlen(name); ++a)
118 lprintf(7, "Too many @'s in address\n");
122 for (a = 0; a < strlen(name); ++a)
124 strcpy(bbb, &name[a + 1]);
126 strcpy(bbb, &bbb[1]);
127 fp = fopen("network/mail.sysinfo", "r");
131 a = getstring(fp, aaa);
132 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
133 a = getstring(fp, aaa);
134 if (!strncmp(aaa, "use ", 4)) {
135 strcpy(bbb, &aaa[4]);
140 if (!strncmp(aaa, "uum", 3)) {
142 for (a = 0; a < strlen(bbb); ++a) {
148 while (bbb[strlen(bbb) - 1] == '_')
149 bbb[strlen(bbb) - 1] = 0;
150 sprintf(name, &aaa[4], bbb);
151 return (MES_INTERNET);
153 if (!strncmp(aaa, "bin", 3)) {
156 while (aaa[strlen(aaa) - 1] != '@')
157 aaa[strlen(aaa) - 1] = 0;
158 aaa[strlen(aaa) - 1] = 0;
159 while (aaa[strlen(aaa) - 1] == ' ')
160 aaa[strlen(aaa) - 1] = 0;
161 while (bbb[0] != '@')
162 strcpy(bbb, &bbb[1]);
163 strcpy(bbb, &bbb[1]);
164 while (bbb[0] == ' ')
165 strcpy(bbb, &bbb[1]);
166 sprintf(name, "%s @%s", aaa, bbb);
179 fp = fopen("citadel.control", "r");
180 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
186 void simple_listing(long msgnum)
188 cprintf("%ld\n", msgnum);
193 * API function to perform an operation for each qualifying message in the
196 void CtdlForEachMessage(int mode, long ref,
198 void (*CallBack) (long msgnum))
203 struct cdbdata *cdbfr;
204 long *msglist = NULL;
207 struct SuppMsgInfo smi;
209 /* Learn about the user and room in question */
211 getuser(&CC->usersupp, CC->curr_user);
212 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
214 /* Load the message list */
215 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
217 msglist = mallok(cdbfr->len);
218 memcpy(msglist, cdbfr->ptr, cdbfr->len);
219 num_msgs = cdbfr->len / sizeof(long);
222 return; /* No messages at all? No further action. */
226 /* If the caller is looking for a specific MIME type, then filter
227 * out all messages which are not of the type requested.
230 if (content_type != NULL)
231 if (strlen(content_type) > 0)
232 for (a = 0; a < num_msgs; ++a) {
233 GetSuppMsgInfo(&smi, msglist[a]);
234 if (strcasecmp(smi.smi_content_type, content_type)) {
239 * Now iterate through the message list, according to the
240 * criteria supplied by the caller.
243 for (a = 0; a < num_msgs; ++a) {
244 thismsg = msglist[a];
249 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
250 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
251 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
252 && (CC->usersupp.flags & US_LASTOLD))
253 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
254 || ((mode == MSGS_FIRST) && (a < ref))
255 || ((mode == MSGS_GT) && (thismsg > ref))
261 phree(msglist); /* Clean up */
267 * cmd_msgs() - get list of message #'s in this room
268 * implements the MSGS server command using CtdlForEachMessage()
270 void cmd_msgs(char *cmdbuf)
276 extract(which, cmdbuf, 0);
277 cm_ref = extract_int(cmdbuf, 1);
281 if (!strncasecmp(which, "OLD", 3))
283 else if (!strncasecmp(which, "NEW", 3))
285 else if (!strncasecmp(which, "FIRST", 5))
287 else if (!strncasecmp(which, "LAST", 4))
289 else if (!strncasecmp(which, "GT", 2))
292 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
293 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
296 cprintf("%d Message list...\n", LISTING_FOLLOWS);
297 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
305 * help_subst() - support routine for help file viewer
307 void help_subst(char *strbuf, char *source, char *dest)
312 while (p = pattern2(strbuf, source), (p >= 0)) {
313 strcpy(workbuf, &strbuf[p + strlen(source)]);
314 strcpy(&strbuf[p], dest);
315 strcat(strbuf, workbuf);
320 void do_help_subst(char *buffer)
324 help_subst(buffer, "^nodename", config.c_nodename);
325 help_subst(buffer, "^humannode", config.c_humannode);
326 help_subst(buffer, "^fqdn", config.c_fqdn);
327 help_subst(buffer, "^username", CC->usersupp.fullname);
328 sprintf(buf2, "%ld", CC->usersupp.usernum);
329 help_subst(buffer, "^usernum", buf2);
330 help_subst(buffer, "^sysadm", config.c_sysadm);
331 help_subst(buffer, "^variantname", CITADEL);
332 sprintf(buf2, "%d", config.c_maxsessions);
333 help_subst(buffer, "^maxsessions", buf2);
339 * memfmout() - Citadel text formatter and paginator.
340 * Although the original purpose of this routine was to format
341 * text to the reader's screen width, all we're really using it
342 * for here is to format text out to 80 columns before sending it
343 * to the client. The client software may reformat it again.
345 void memfmout(int width, char *mptr, char subst)
346 /* screen width to use */
347 /* where are we going to get our text from? */
348 /* nonzero if we should use hypertext mode */
360 c = 1; /* c is the current pos */
363 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
365 buffer[strlen(buffer) + 1] = 0;
366 buffer[strlen(buffer)] = ch;
369 if (buffer[0] == '^')
370 do_help_subst(buffer);
372 buffer[strlen(buffer) + 1] = 0;
374 strcpy(buffer, &buffer[1]);
383 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
385 if (((old == 13) || (old == 10)) && (isspace(real))) {
393 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
394 cprintf("\n%s", aaa);
403 if ((strlen(aaa) + c) > (width - 5)) {
413 if ((ch == 13) || (ch == 10)) {
414 cprintf("%s\n", aaa);
421 FMTEND:cprintf("%s\n", aaa);
427 * Callback function for mime parser that simply lists the part
429 void list_this_part(char *name, char *filename, char *partnum, char *disp,
430 void *content, char *cbtype, size_t length)
433 cprintf("part=%s|%s|%s|%s|%s|%d\n",
434 name, filename, partnum, disp, cbtype, length);
439 * Callback function for mime parser that wants to display text
441 void fixed_output(char *name, char *filename, char *partnum, char *disp,
442 void *content, char *cbtype, size_t length)
445 if (!strcasecmp(cbtype, "text/plain")) {
446 client_write(content, length);
448 cprintf("Part %s: %s (%s) (%d bytes)\n",
449 partnum, filename, cbtype, length);
455 * Callback function for mime parser that opens a section for downloading
457 void mime_download(char *name, char *filename, char *partnum, char *disp,
458 void *content, char *cbtype, size_t length)
461 /* Silently go away if there's already a download open... */
462 if (CC->download_fp != NULL)
465 /* ...or if this is not the desired section */
466 if (strcasecmp(desired_section, partnum))
469 CC->download_fp = tmpfile();
470 if (CC->download_fp == NULL)
473 fwrite(content, length, 1, CC->download_fp);
474 fflush(CC->download_fp);
475 rewind(CC->download_fp);
477 OpenCmdResult(filename, cbtype);
483 * Load a message from disk into memory.
484 * (This will replace a big piece of output_message() eventually)
486 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
487 * using the CtdlMessageFree() function.
489 struct CtdlMessage *CtdlFetchMessage(long msgnum)
491 struct cdbdata *dmsgtext;
492 struct CtdlMessage *ret = NULL;
495 CIT_UBYTE field_header;
499 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
500 if (dmsgtext == NULL) {
501 lprintf(9, "CtdlMessage(%ld) failed.\n");
504 mptr = dmsgtext->ptr;
506 /* Parse the three bytes that begin EVERY message on disk.
507 * The first is always 0xFF, the on-disk magic number.
508 * The second is the anonymous/public type byte.
509 * The third is the format type byte (vari, fixed, or MIME).
513 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
517 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
518 memset(ret, 0, sizeof(struct CtdlMessage));
520 ret->cm_magic = CTDLMESSAGE_MAGIC;
521 ret->cm_anon_type = *mptr++; /* Anon type byte */
522 ret->cm_format_type = *mptr++; /* Format type byte */
525 * The rest is zero or more arbitrary fields. Load them in.
526 * We're done when we encounter either a zero-length field or
527 * have just processed the 'M' (message text) field.
530 field_length = strlen(mptr);
531 if (field_length == 0)
533 field_header = *mptr++;
534 ret->cm_fields[field_header] = mallok(field_length);
535 strcpy(ret->cm_fields[field_header], mptr);
537 while (*mptr++ != 0); /* advance to next field */
539 } while ((field_length > 0) && (field_header != 'M'));
546 * 'Destructor' for struct CtdlMessage
548 void CtdlFreeMessage(struct CtdlMessage *msg)
554 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
555 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
558 for (i = 0; i < 256; ++i)
559 if (msg->cm_fields[i] != NULL)
560 phree(msg->cm_fields[i]);
568 * Get a message off disk. (return value is the message's timestamp)
571 void output_message(char *msgid, int mode, int headers_only)
579 struct CtdlMessage *TheMessage = NULL;
583 /* buffers needed for RFC822 translation */
591 msg_num = atol(msgid);
593 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
594 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
597 /* FIX ... small security issue
598 * We need to check to make sure the requested message is actually
599 * in the current room, and set msg_ok to 1 only if it is. This
600 * functionality is currently missing because I'm in a hurry to replace
601 * broken production code with nonbroken pre-beta code. :( -- ajc
604 cprintf("%d Message %ld is not in this room.\n",
611 * Fetch the message from disk
613 TheMessage = CtdlFetchMessage(msg_num);
614 if (TheMessage == NULL) {
615 cprintf("%d Can't locate message %ld on disk\n", ERROR, msg_num);
619 /* Are we downloading a MIME component? */
620 if (mode == MT_DOWNLOAD) {
621 if (TheMessage->cm_format_type != 4) {
622 cprintf("%d This is not a MIME message.\n",
624 } else if (CC->download_fp != NULL) {
625 cprintf("%d You already have a download open.\n",
628 /* Parse the message text component */
629 mptr = TheMessage->cm_fields['M'];
630 mime_parser(mptr, NULL, *mime_download);
631 /* If there's no file open by this time, the requested
632 * section wasn't found, so print an error
634 if (CC->download_fp == NULL) {
635 cprintf("%d Section %s not found.\n",
636 ERROR + FILE_NOT_FOUND,
640 CtdlFreeMessage(TheMessage);
643 /* now for the user-mode message reading loops */
644 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
646 if (mode == MT_CITADEL)
647 cprintf("type=%d\n", TheMessage->cm_format_type);
649 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
650 cprintf("nhdr=yes\n");
652 /* begin header processing loop for Citadel message format */
654 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
656 if (TheMessage->cm_fields['P']) {
657 cprintf("path=%s\n", TheMessage->cm_fields['P']);
659 if (TheMessage->cm_fields['I']) {
660 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
662 if (TheMessage->cm_fields['T']) {
663 cprintf("time=%s\n", TheMessage->cm_fields['T']);
665 if (TheMessage->cm_fields['A']) {
666 strcpy(buf, TheMessage->cm_fields['A']);
667 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
668 if (TheMessage->cm_anon_type == MES_ANON)
669 cprintf("from=****");
670 else if (TheMessage->cm_anon_type == MES_AN2)
671 cprintf("from=anonymous");
673 cprintf("from=%s", buf);
675 && ((TheMessage->cm_anon_type == MES_ANON)
676 || (TheMessage->cm_anon_type == MES_AN2))) {
677 cprintf(" [%s]", buf);
681 if (TheMessage->cm_fields['O']) {
682 cprintf("room=%s\n", TheMessage->cm_fields['O']);
684 if (TheMessage->cm_fields['N']) {
685 cprintf("node=%s\n", TheMessage->cm_fields['N']);
687 if (TheMessage->cm_fields['H']) {
688 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
690 if (TheMessage->cm_fields['R']) {
691 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
693 if (TheMessage->cm_fields['U']) {
694 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
697 /* begin header processing loop for RFC822 transfer format */
701 strcpy(snode, NODENAME);
702 strcpy(lnode, HUMANNODE);
703 if (mode == MT_RFC822) {
704 for (i = 0; i < 256; ++i) {
705 if (TheMessage->cm_fields[i]) {
706 mptr = TheMessage->cm_fields[i];
710 } else if (i == 'P') {
711 cprintf("Path: %s\n", mptr);
712 for (a = 0; a < strlen(mptr); ++a) {
713 if (mptr[a] == '!') {
714 strcpy(mptr, &mptr[a + 1]);
720 cprintf("Subject: %s\n", mptr);
726 cprintf("X-Citadel-Room: %s\n", mptr);
730 cprintf("To: %s\n", mptr);
733 cprintf("Date: %s", asctime(localtime(&xtime)));
738 if (mode == MT_RFC822) {
739 if (!strcasecmp(snode, NODENAME)) {
742 cprintf("Message-ID: <%s@%s>\n", mid, snode);
743 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
744 cprintf("From: %s@%s (%s)\n",
745 suser, snode, luser);
746 cprintf("Organization: %s\n", lnode);
748 /* end header processing loop ... at this point, we're in the text */
750 mptr = TheMessage->cm_fields['M'];
752 /* do some sort of MIME output */
753 if (TheMessage->cm_format_type == 4) {
754 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
755 mime_parser(mptr, NULL, *list_this_part);
757 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
759 CtdlFreeMessage(TheMessage);
765 CtdlFreeMessage(TheMessage);
768 /* signify start of msg text */
769 if (mode == MT_CITADEL)
771 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
774 /* If the format type on disk is 1 (fixed-format), then we want
775 * everything to be output completely literally ... regardless of
776 * what message transfer format is in use.
778 if (TheMessage->cm_format_type == 1) {
780 while (ch = *mptr++, ch > 0) {
783 if ((ch == 10) || (strlen(buf) > 250)) {
784 cprintf("%s\n", buf);
787 buf[strlen(buf) + 1] = 0;
788 buf[strlen(buf)] = ch;
792 cprintf("%s\n", buf);
794 /* If the message on disk is format 0 (Citadel vari-format), we
795 * output using the formatter at 80 columns. This is the final output
796 * form if the transfer format is RFC822, but if the transfer format
797 * is Citadel proprietary, it'll still work, because the indentation
798 * for new paragraphs is correct and the client will reformat the
799 * message to the reader's screen width.
801 if (TheMessage->cm_format_type == 0) {
802 memfmout(80, mptr, 0);
804 /* If the message on disk is format 4 (MIME), we've gotta hand it
805 * off to the MIME parser. The client has already been told that
806 * this message is format 1 (fixed format), so the callback function
807 * we use will display those parts as-is.
809 if (TheMessage->cm_format_type == 4) {
810 mime_parser(mptr, NULL, *fixed_output);
814 CtdlFreeMessage(TheMessage);
821 * display a message (mode 0 - Citadel proprietary)
823 void cmd_msg0(char *cmdbuf)
826 int headers_only = 0;
828 extract(msgid, cmdbuf, 0);
829 headers_only = extract_int(cmdbuf, 1);
831 output_message(msgid, MT_CITADEL, headers_only);
837 * display a message (mode 2 - RFC822)
839 void cmd_msg2(char *cmdbuf)
842 int headers_only = 0;
844 extract(msgid, cmdbuf, 0);
845 headers_only = extract_int(cmdbuf, 1);
847 output_message(msgid, MT_RFC822, headers_only);
853 * display a message (mode 3 - IGnet raw format - internal programs only)
855 void cmd_msg3(char *cmdbuf)
858 struct cdbdata *dmsgtext;
860 if (CC->internal_pgm == 0) {
861 cprintf("%d This command is for internal programs only.\n",
866 msgnum = extract_long(cmdbuf, 0);
868 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
869 if (dmsgtext == NULL) {
870 cprintf("%d Message %ld not found\n", ERROR, msgnum);
873 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
874 client_write(dmsgtext->ptr, dmsgtext->len);
881 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
883 void cmd_msg4(char *cmdbuf)
887 extract(msgid, cmdbuf, 0);
889 output_message(msgid, MT_MIME, 0);
893 * Open a component of a MIME message as a download file
895 void cmd_opna(char *cmdbuf)
899 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
901 extract(msgid, cmdbuf, 0);
902 extract(desired_section, cmdbuf, 1);
904 output_message(msgid, MT_DOWNLOAD, 0);
908 * Message base operation to send a message to the master file
909 * (returns new message number)
911 long send_message(char *message_in_memory,
912 /* pointer to buffer */
913 size_t message_length, /* length of buffer */
915 { /* 1 to generate an I field */
918 char *actual_message;
919 size_t actual_length;
923 /* Get a new message number */
924 newmsgid = get_new_message_number();
927 sprintf(msgidbuf, "I%ld", newmsgid);
928 actual_length = message_length + strlen(msgidbuf) + 1;
929 actual_message = mallok(actual_length);
930 memcpy(actual_message, message_in_memory, 3);
931 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
932 memcpy(&actual_message[strlen(msgidbuf) + 4],
933 &message_in_memory[3], message_length - 3);
935 actual_message = message_in_memory;
936 actual_length = message_length;
939 /* Write our little bundle of joy into the message base */
940 begin_critical_section(S_MSGMAIN);
941 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
942 actual_message, actual_length) < 0) {
943 lprintf(2, "Can't store message\n");
948 end_critical_section(S_MSGMAIN);
951 phree(actual_message);
953 /* Finally, return the pointers */
960 * this is a simple file copy routine.
962 void copy_file(char *from, char *to)
967 ffp = fopen(from, "r");
970 tfp = fopen(to, "w");
975 while (a = getc(ffp), a >= 0) {
986 * message base operation to save a message and install its pointers
988 void save_message(char *mtmp, /* file containing proper message */
989 char *rec, /* Recipient (if mail) */
990 char *force, /* if non-zero length, force a room */
991 int mailtype, /* local or remote type, see citadel.h */
993 { /* set to 1 to generate an 'I' field */
995 char hold_rm[ROOMNAMELEN];
996 char actual_rm[ROOMNAMELEN];
997 char force_room[ROOMNAMELEN];
998 char content_type[256]; /* We have to learn this */
1000 char recipient[256];
1002 char *message_in_memory;
1004 struct stat statbuf;
1007 struct usersupp userbuf;
1009 static int seqnum = 0;
1010 int successful_local_recipients = 0;
1011 struct quickroom qtemp;
1012 struct SuppMsgInfo smi;
1014 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1015 mtmp, rec, force, mailtype, generate_id);
1017 strcpy(force_room, force);
1019 /* Strip non-printable characters out of the recipient name */
1020 strcpy(recipient, rec);
1021 for (a = 0; a < strlen(recipient); ++a)
1022 if (!isprint(recipient[a]))
1023 strcpy(&recipient[a], &recipient[a + 1]);
1025 /* Measure the message */
1026 stat(mtmp, &statbuf);
1027 templen = statbuf.st_size;
1029 /* Now read it into memory */
1030 message_in_memory = (char *) mallok(templen);
1031 if (message_in_memory == NULL) {
1032 lprintf(2, "Can't allocate memory to save message!\n");
1035 fp = fopen(mtmp, "rb");
1036 fread(message_in_memory, templen, 1, fp);
1039 /* Learn about what's inside, because it's what's inside that counts */
1040 mptr = message_in_memory;
1041 ++mptr; /* advance past 0xFF header */
1042 ++mptr; /* advance past anon flag */
1046 strcpy(content_type, "text/x-citadel-variformat");
1049 strcpy(content_type, "text/plain");
1052 strcpy(content_type, "text/plain");
1053 /* advance past header fields */
1054 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1061 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1062 safestrncpy(content_type, mptr,
1063 sizeof(content_type));
1064 lprintf(9, "%s\n", content_type);
1065 strcpy(content_type, &content_type[14]);
1066 for (a = 0; a < strlen(content_type); ++a)
1067 if ((content_type[a] == ';')
1068 || (content_type[a] == ' ')
1069 || (content_type[a] == 13)
1070 || (content_type[a] == 10))
1071 content_type[a] = 0;
1077 lprintf(9, "Content type is <%s>\n", content_type);
1079 /* Save it to disk */
1080 newmsgid = send_message(message_in_memory, templen, generate_id);
1081 phree(message_in_memory);
1085 strcpy(actual_rm, CC->quickroom.QRname);
1086 strcpy(hold_rm, "");
1088 /* If this is being done by the networker delivering a private
1089 * message, we want to BYPASS saving the sender's copy (because there
1090 * is no local sender; it would otherwise go to the Trashcan).
1092 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1093 /* If the user is a twit, move to the twit room for posting */
1095 if (CC->usersupp.axlevel == 2) {
1096 strcpy(hold_rm, actual_rm);
1097 strcpy(actual_rm, config.c_twitroom);
1099 /* ...or if this message is destined for Aide> then go there. */
1100 lprintf(9, "actual room forcing loop\n");
1101 if (strlen(force_room) > 0) {
1102 strcpy(hold_rm, actual_rm);
1103 strcpy(actual_rm, force_room);
1105 /* This call to usergoto() changes rooms if necessary. It also
1106 * causes the latest message list to be read into memory.
1108 usergoto(actual_rm, 0);
1110 /* read in the quickroom record, obtaining a lock... */
1111 lgetroom(&CC->quickroom, actual_rm);
1113 /* Fix an obscure bug */
1114 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1115 CC->quickroom.QRflags =
1116 CC->quickroom.QRflags & ~QR_MAILBOX;
1118 /* Add the message pointer to the room */
1119 CC->quickroom.QRhighest =
1120 AddMessageToRoom(&CC->quickroom, newmsgid);
1122 /* update quickroom */
1123 lputroom(&CC->quickroom);
1124 ++successful_local_recipients;
1126 /* Network mail - send a copy to the network program. */
1127 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1128 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1129 (long) getpid(), CC->cs_pid, ++seqnum);
1130 copy_file(mtmp, aaa);
1131 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1133 /* Bump this user's messages posted counter. */
1134 lgetuser(&CC->usersupp, CC->curr_user);
1135 CC->usersupp.posted = CC->usersupp.posted + 1;
1136 lputuser(&CC->usersupp);
1138 /* If this is private, local mail, make a copy in the
1139 * recipient's mailbox and bump the reference count.
1141 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1142 if (getuser(&userbuf, recipient) == 0) {
1143 MailboxName(actual_rm, &userbuf, MAILROOM);
1144 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1145 if (lgetroom(&qtemp, actual_rm) == 0) {
1147 AddMessageToRoom(&qtemp, newmsgid);
1149 ++successful_local_recipients;
1153 /* If we've posted in a room other than the current room, then we
1154 * have to now go back to the current room...
1156 if (strlen(hold_rm) > 0) {
1157 usergoto(hold_rm, 0);
1159 unlink(mtmp); /* delete the temporary file */
1161 /* Write a supplemental message info record. This doesn't have to
1162 * be a critical section because nobody else knows about this message
1165 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1166 smi.smi_msgnum = newmsgid;
1167 smi.smi_refcount = successful_local_recipients;
1168 safestrncpy(smi.smi_content_type, content_type, 64);
1169 PutSuppMsgInfo(&smi);
1174 * Generate an administrative message and post it in the Aide> room.
1176 void aide_message(char *text)
1180 fp = fopen(CC->temp, "wb");
1181 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1182 fprintf(fp, "Psysop%c", 0);
1183 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1184 fprintf(fp, "ACitadel%c", 0);
1185 fprintf(fp, "OAide%c", 0);
1186 fprintf(fp, "N%s%c", NODENAME, 0);
1187 fprintf(fp, "M%s\n%c", text, 0);
1189 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1190 syslog(LOG_NOTICE, text);
1193 * Build a binary message to be saved on disk.
1194 */ void make_message(
1195 char *filename, /* temporary file name */
1196 struct usersupp *author, /* author's usersupp structure */
1197 char *recipient, /* NULL if it's not mail */
1198 char *room, /* room where it's going */
1199 int type, /* see MES_ types in header file */
1200 int net_type, /* see MES_ types in header file */
1201 int format_type, /* local or remote (see citadel.h) */
1203 { /* who we're masquerading as */
1211 /* Don't confuse the poor folks if it's not routed mail. */
1212 strcpy(dest_node, "");
1215 /* If net_type is MES_BINARY, split out the destination node. */
1216 if (net_type == MES_BINARY) {
1217 strcpy(dest_node, NODENAME);
1218 for (a = 0; a < strlen(recipient); ++a) {
1219 if (recipient[a] == '@') {
1221 strcpy(dest_node, &recipient[a + 1]);
1225 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1226 strcpy(dest_node, "internet");
1228 while (isspace(recipient[strlen(recipient) - 1]))
1229 recipient[strlen(recipient) - 1] = 0;
1232 fp = fopen(filename, "w");
1234 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1235 putc(format_type, fp); /* Formatted or unformatted */
1236 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1237 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1239 fprintf(fp, "A%s%c", fake_name, 0);
1241 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1243 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1244 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1246 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1249 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1250 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1252 if (recipient[0] != 0)
1253 fprintf(fp, "R%s%c", recipient, 0);
1254 if (dest_node[0] != 0)
1255 fprintf(fp, "D%s%c", dest_node, 0);
1259 while (client_gets(buf), strcmp(buf, "000")) {
1260 fprintf(fp, "%s\n", buf);
1271 * message entry - mode 0 (normal) <bc>
1273 void cmd_ent0(char *entargs)
1276 char recipient[256];
1278 int format_type = 0;
1279 char newusername[256]; /* <bc> */
1284 struct usersupp tempUS;
1287 post = extract_int(entargs, 0);
1288 extract(recipient, entargs, 1);
1289 anon_flag = extract_int(entargs, 2);
1290 format_type = extract_int(entargs, 3);
1292 /* first check to make sure the request is valid. */
1294 if (!(CC->logged_in)) {
1295 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1298 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1299 cprintf("%d Need to be validated to enter ",
1300 ERROR + HIGHER_ACCESS_REQUIRED);
1301 cprintf("(except in %s> to sysop)\n", MAILROOM);
1304 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1305 cprintf("%d Need net privileges to enter here.\n",
1306 ERROR + HIGHER_ACCESS_REQUIRED);
1309 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1310 cprintf("%d Sorry, this is a read-only room.\n",
1311 ERROR + HIGHER_ACCESS_REQUIRED);
1317 if (post == 2) { /* <bc> */
1318 if (CC->usersupp.axlevel < 6) {
1319 cprintf("%d You don't have permission to do an aide post.\n",
1320 ERROR + HIGHER_ACCESS_REQUIRED);
1323 extract(newusername, entargs, 4);
1324 memset(CC->fake_postname, 0, 32);
1325 strcpy(CC->fake_postname, newusername);
1326 cprintf("%d Ok\n", OK);
1329 CC->cs_flags |= CS_POSTING;
1332 if (CC->quickroom.QRflags & QR_MAILBOX) {
1333 if (CC->usersupp.axlevel >= 2) {
1334 strcpy(buf, recipient);
1336 strcpy(buf, "sysop");
1337 lprintf(9, "calling alias()\n");
1338 e = alias(buf); /* alias and mail type */
1339 lprintf(9, "alias() returned %d\n", e);
1340 if ((buf[0] == 0) || (e == MES_ERROR)) {
1341 cprintf("%d Unknown address - cannot send message.\n",
1342 ERROR + NO_SUCH_USER);
1345 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1346 cprintf("%d Net privileges required for network mail.\n",
1347 ERROR + HIGHER_ACCESS_REQUIRED);
1350 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1351 && ((CC->usersupp.flags & US_INTERNET) == 0)
1352 && (!CC->internal_pgm)) {
1353 cprintf("%d You don't have access to Internet mail.\n",
1354 ERROR + HIGHER_ACCESS_REQUIRED);
1357 if (!strcasecmp(buf, "sysop")) {
1362 goto SKFALL; /* don't search local file */
1363 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1364 cprintf("%d Can't send mail to yourself!\n",
1365 ERROR + NO_SUCH_USER);
1368 /* Check to make sure the user exists; also get the correct
1369 * upper/lower casing of the name.
1371 a = getuser(&tempUS, buf);
1373 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1376 strcpy(buf, tempUS.fullname);
1378 SKFALL:b = MES_NORMAL;
1379 if (CC->quickroom.QRflags & QR_ANONONLY)
1381 if (CC->quickroom.QRflags & QR_ANONOPT) {
1385 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1388 /* If we're only checking the validity of the request, return
1389 * success without creating the message.
1392 cprintf("%d %s\n", OK, buf);
1395 cprintf("%d send message\n", SEND_LISTING);
1396 if (CC->fake_postname[0])
1397 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1398 else if (CC->fake_username[0])
1399 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1401 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1402 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1403 CC->fake_postname[0] = '\0';
1410 * message entry - mode 3 (raw)
1412 void cmd_ent3(char *entargs)
1418 struct usersupp tempUS;
1423 if (CC->internal_pgm == 0) {
1424 cprintf("%d This command is for internal programs only.\n",
1428 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1429 for (a = 0; a < strlen(recp); ++a)
1430 if (!isprint(recp[a]))
1431 strcpy(&recp[a], &recp[a + 1]);
1432 while (isspace(recp[0]))
1433 strcpy(recp, &recp[1]);
1434 while (isspace(recp[strlen(recp) - 1]))
1435 recp[strlen(recp) - 1] = 0;
1437 /* If we're in Mail, check the recipient */
1438 if (strlen(recp) > 0) {
1439 e = alias(recp); /* alias and mail type */
1440 if ((recp[0] == 0) || (e == MES_ERROR)) {
1441 cprintf("%d Unknown address - cannot send message.\n",
1442 ERROR + NO_SUCH_USER);
1445 if (e == MES_LOCAL) {
1446 a = getuser(&tempUS, recp);
1448 cprintf("%d No such user.\n",
1449 ERROR + NO_SUCH_USER);
1454 /* At this point, message has been approved. */
1455 if (extract_int(entargs, 0) == 0) {
1456 cprintf("%d OK to send\n", OK);
1459 /* open a temp file to hold the message */
1460 fp = fopen(CC->temp, "wb");
1462 cprintf("%d Cannot open %s: %s\n",
1463 ERROR + INTERNAL_ERROR,
1464 CC->temp, strerror(errno));
1467 msglen = extract_long(entargs, 2);
1468 cprintf("%d %ld\n", SEND_BINARY, msglen);
1469 while (msglen > 0L) {
1470 bloklen = ((msglen >= 255L) ? 255 : msglen);
1471 client_read(buf, (int) bloklen);
1472 fwrite(buf, (int) bloklen, 1, fp);
1473 msglen = msglen - bloklen;
1477 save_message(CC->temp, recp, "", e, 0);
1482 * API function to delete messages which match a set of criteria
1483 * (returns the actual number of messages deleted)
1484 * FIX ... still need to implement delete by content type
1486 int CtdlDeleteMessages(char *room_name, /* which room */
1487 long dmsgnum, /* or "0" for any */
1488 char *content_type /* or NULL for any */
1492 struct quickroom qrbuf;
1493 struct cdbdata *cdbfr;
1494 long *msglist = NULL;
1497 int num_deleted = 0;
1499 struct SuppMsgInfo smi;
1501 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1502 room_name, dmsgnum, content_type);
1504 /* get room record, obtaining a lock... */
1505 if (lgetroom(&qrbuf, room_name) != 0) {
1506 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1508 return (0); /* room not found */
1510 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1512 lprintf(9, "doing mallok/memcpy loop\n");
1513 if (cdbfr != NULL) {
1514 msglist = mallok(cdbfr->len);
1515 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1516 num_msgs = cdbfr->len / sizeof(long);
1520 for (i = 0; i < num_msgs; ++i) {
1523 /* Set/clear a bit for each criterion */
1525 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1526 delete_this |= 0x01;
1528 if (content_type == NULL) {
1529 delete_this |= 0x02;
1531 GetSuppMsgInfo(&smi, msglist[i]);
1532 if (!strcasecmp(smi.smi_content_type,
1534 delete_this |= 0x02;
1538 /* Delete message only if all bits are set */
1539 if (delete_this == 0x03) {
1540 AdjRefCount(msglist[i], -1);
1546 num_msgs = sort_msglist(msglist, num_msgs);
1547 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1548 msglist, (num_msgs * sizeof(long)));
1550 qrbuf.QRhighest = msglist[num_msgs - 1];
1553 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1554 return (num_deleted);
1560 * Delete message from current room
1562 void cmd_dele(char *delstr)
1567 getuser(&CC->usersupp, CC->curr_user);
1568 if ((CC->usersupp.axlevel < 6)
1569 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1570 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1571 cprintf("%d Higher access required.\n",
1572 ERROR + HIGHER_ACCESS_REQUIRED);
1575 delnum = extract_long(delstr, 0);
1577 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1580 cprintf("%d %d message%s deleted.\n", OK,
1581 num_deleted, ((num_deleted != 1) ? "s" : ""));
1583 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1589 * move a message to another room
1591 void cmd_move(char *args)
1595 struct quickroom qtemp;
1598 num = extract_long(args, 0);
1599 extract(targ, args, 1);
1601 getuser(&CC->usersupp, CC->curr_user);
1602 if ((CC->usersupp.axlevel < 6)
1603 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1604 cprintf("%d Higher access required.\n",
1605 ERROR + HIGHER_ACCESS_REQUIRED);
1608 if (getroom(&qtemp, targ) != 0) {
1609 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1612 /* Bump the reference count, otherwise the message will be deleted
1613 * from disk when we remove it from the source room.
1615 AdjRefCount(num, 1);
1617 /* yank the message out of the current room... */
1618 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1621 /* put the message into the target room */
1622 lgetroom(&qtemp, targ);
1623 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1625 cprintf("%d Message moved.\n", OK);
1627 AdjRefCount(num, (-1)); /* oops */
1628 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1635 * GetSuppMsgInfo() - Get the supplementary record for a message
1637 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1640 struct cdbdata *cdbsmi;
1643 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1644 smibuf->smi_msgnum = msgnum;
1645 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1647 /* Use the negative of the message number for its supp record index */
1648 TheIndex = (0L - msgnum);
1650 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1651 if (cdbsmi == NULL) {
1652 return; /* record not found; go with defaults */
1654 memcpy(smibuf, cdbsmi->ptr,
1655 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1656 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1663 * PutSuppMsgInfo() - (re)write supplementary record for a message
1665 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1669 /* Use the negative of the message number for its supp record index */
1670 TheIndex = (0L - smibuf->smi_msgnum);
1672 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1673 smibuf->smi_msgnum, smibuf->smi_refcount);
1675 cdb_store(CDB_MSGMAIN,
1676 &TheIndex, sizeof(long),
1677 smibuf, sizeof(struct SuppMsgInfo));
1681 * AdjRefCount - change the reference count for a message;
1682 * delete the message if it reaches zero
1683 */ void AdjRefCount(long msgnum, int incr)
1686 struct SuppMsgInfo smi;
1689 /* This is a *tight* critical section; please keep it that way, as
1690 * it may get called while nested in other critical sections.
1691 * Complicating this any further will surely cause deadlock!
1693 begin_critical_section(S_SUPPMSGMAIN);
1694 GetSuppMsgInfo(&smi, msgnum);
1695 smi.smi_refcount += incr;
1696 PutSuppMsgInfo(&smi);
1697 end_critical_section(S_SUPPMSGMAIN);
1699 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1700 msgnum, smi.smi_refcount);
1702 /* If the reference count is now zero, delete the message
1703 * (and its supplementary record as well).
1705 if (smi.smi_refcount == 0) {
1706 lprintf(9, "Deleting message <%ld>\n", msgnum);
1708 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1709 delnum = (0L - msgnum);
1710 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1714 * Write a generic object to this room
1715 */ void CtdlWriteObject(char *req_room,
1716 /* 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);