22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
33 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
34 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
36 extern struct config config;
40 * This function is self explanatory.
41 * (What can I say, I'm in a weird mood today...)
43 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
47 for (i = 0; i < strlen(name); ++i)
50 if (isspace(name[i - 1])) {
51 strcpy(&name[i - 1], &name[i]);
54 while (isspace(name[i + 1])) {
55 strcpy(&name[i + 1], &name[i + 2]);
62 * Aliasing for network mail.
63 * (Error messages have been commented out, because this is a server.)
66 { /* process alias and routing info for mail */
69 char aaa[300], bbb[300];
71 lprintf(9, "alias() called for <%s>\n", name);
73 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
75 fp = fopen("network/mail.aliases", "r");
77 fp = fopen("/dev/null", "r");
82 while (fgets(aaa, sizeof aaa, fp) != NULL) {
83 while (isspace(name[0]))
84 strcpy(name, &name[1]);
85 aaa[strlen(aaa) - 1] = 0;
87 for (a = 0; a < strlen(aaa); ++a) {
89 strcpy(bbb, &aaa[a + 1]);
93 if (!strcasecmp(name, aaa))
97 lprintf(7, "Mail is being forwarded to %s\n", name);
99 /* determine local or remote type, see citadel.h */
100 for (a = 0; a < strlen(name); ++a)
102 return (MES_INTERNET);
103 for (a = 0; a < strlen(name); ++a)
105 for (b = a; b < strlen(name); ++b)
107 return (MES_INTERNET);
109 for (a = 0; a < strlen(name); ++a)
113 lprintf(7, "Too many @'s in address\n");
117 for (a = 0; a < strlen(name); ++a)
119 strcpy(bbb, &name[a + 1]);
121 strcpy(bbb, &bbb[1]);
122 fp = fopen("network/mail.sysinfo", "r");
126 a = getstring(fp, aaa);
127 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
128 a = getstring(fp, aaa);
129 if (!strncmp(aaa, "use ", 4)) {
130 strcpy(bbb, &aaa[4]);
135 if (!strncmp(aaa, "uum", 3)) {
137 for (a = 0; a < strlen(bbb); ++a) {
143 while (bbb[strlen(bbb) - 1] == '_')
144 bbb[strlen(bbb) - 1] = 0;
145 sprintf(name, &aaa[4], bbb);
146 return (MES_INTERNET);
148 if (!strncmp(aaa, "bin", 3)) {
151 while (aaa[strlen(aaa) - 1] != '@')
152 aaa[strlen(aaa) - 1] = 0;
153 aaa[strlen(aaa) - 1] = 0;
154 while (aaa[strlen(aaa) - 1] == ' ')
155 aaa[strlen(aaa) - 1] = 0;
156 while (bbb[0] != '@')
157 strcpy(bbb, &bbb[1]);
158 strcpy(bbb, &bbb[1]);
159 while (bbb[0] == ' ')
160 strcpy(bbb, &bbb[1]);
161 sprintf(name, "%s @%s", aaa, bbb);
174 fp = fopen("citadel.control", "r");
175 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
181 void simple_listing(long msgnum)
183 cprintf("%ld\n", msgnum);
188 * API function to perform an operation for each qualifying message in the
191 void CtdlForEachMessage(int mode, long ref,
193 void (*CallBack) (long msgnum))
198 struct cdbdata *cdbfr;
199 long *msglist = NULL;
202 struct SuppMsgInfo smi;
204 /* Learn about the user and room in question */
206 getuser(&CC->usersupp, CC->curr_user);
207 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
209 /* Load the message list */
210 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
212 msglist = mallok(cdbfr->len);
213 memcpy(msglist, cdbfr->ptr, cdbfr->len);
214 num_msgs = cdbfr->len / sizeof(long);
217 return; /* No messages at all? No further action. */
221 /* If the caller is looking for a specific MIME type, then filter
222 * out all messages which are not of the type requested.
225 if (content_type != NULL)
226 if (strlen(content_type) > 0)
227 for (a = 0; a < num_msgs; ++a) {
228 GetSuppMsgInfo(&smi, msglist[a]);
229 if (strcasecmp(smi.smi_content_type, content_type)) {
234 num_msgs = sort_msglist(msglist, num_msgs);
237 * Now iterate through the message list, according to the
238 * criteria supplied by the caller.
241 for (a = 0; a < num_msgs; ++a) {
242 thismsg = msglist[a];
243 lprintf(9, "Iterating through <%ld>\n", thismsg);
248 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
249 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
250 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
251 && (CC->usersupp.flags & US_LASTOLD))
252 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
253 || ((mode == MSGS_FIRST) && (a < ref))
254 || ((mode == MSGS_GT) && (thismsg > ref))
257 lprintf(9, "Issuing callback for <%ld>\n", thismsg);
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)
446 if (!strcasecmp(cbtype, "multipart/alternative")) {
447 strcpy(ma->prefix, partnum);
448 strcat(ma->prefix, ".");
454 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
456 && (ma->did_print == 1) ) {
457 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
463 if (!strcasecmp(cbtype, "text/plain")) {
464 client_write(content, length);
466 else if (!strcasecmp(cbtype, "text/html")) {
467 ptr = html_to_ascii(content, 80, 0);
468 client_write(ptr, strlen(ptr));
471 else if (strncasecmp(cbtype, "multipart/", 10)) {
472 cprintf("Part %s: %s (%s) (%d bytes)\n",
473 partnum, filename, cbtype, length);
479 * Callback function for mime parser that opens a section for downloading
481 void mime_download(char *name, char *filename, char *partnum, char *disp,
482 void *content, char *cbtype, size_t length)
485 /* Silently go away if there's already a download open... */
486 if (CC->download_fp != NULL)
489 /* ...or if this is not the desired section */
490 if (strcasecmp(desired_section, partnum))
493 CC->download_fp = tmpfile();
494 if (CC->download_fp == NULL)
497 fwrite(content, length, 1, CC->download_fp);
498 fflush(CC->download_fp);
499 rewind(CC->download_fp);
501 OpenCmdResult(filename, cbtype);
507 * Load a message from disk into memory.
508 * This is used by output_message() and other fetch functions.
510 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
511 * using the CtdlMessageFree() function.
513 struct CtdlMessage *CtdlFetchMessage(long msgnum)
515 struct cdbdata *dmsgtext;
516 struct CtdlMessage *ret = NULL;
519 CIT_UBYTE field_header;
523 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
524 if (dmsgtext == NULL) {
525 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
528 mptr = dmsgtext->ptr;
530 /* Parse the three bytes that begin EVERY message on disk.
531 * The first is always 0xFF, the on-disk magic number.
532 * The second is the anonymous/public type byte.
533 * The third is the format type byte (vari, fixed, or MIME).
537 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
541 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
542 memset(ret, 0, sizeof(struct CtdlMessage));
544 ret->cm_magic = CTDLMESSAGE_MAGIC;
545 ret->cm_anon_type = *mptr++; /* Anon type byte */
546 ret->cm_format_type = *mptr++; /* Format type byte */
549 * The rest is zero or more arbitrary fields. Load them in.
550 * We're done when we encounter either a zero-length field or
551 * have just processed the 'M' (message text) field.
554 field_length = strlen(mptr);
555 if (field_length == 0)
557 field_header = *mptr++;
558 ret->cm_fields[field_header] = mallok(field_length);
559 strcpy(ret->cm_fields[field_header], mptr);
561 while (*mptr++ != 0); /* advance to next field */
563 } while ((field_length > 0) && (field_header != 'M'));
570 * 'Destructor' for struct CtdlMessage
572 void CtdlFreeMessage(struct CtdlMessage *msg)
578 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
579 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
582 for (i = 0; i < 256; ++i)
583 if (msg->cm_fields[i] != NULL)
584 phree(msg->cm_fields[i]);
592 * Get a message off disk. (return value is the message's timestamp)
595 void output_message(char *msgid, int mode, int headers_only)
603 struct CtdlMessage *TheMessage = NULL;
607 /* buffers needed for RFC822 translation */
615 msg_num = atol(msgid);
617 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
618 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
622 /* FIX ... small security issue
623 * We need to check to make sure the requested message is actually
624 * in the current room, and set msg_ok to 1 only if it is. This
625 * functionality is currently missing because I'm in a hurry to replace
626 * broken production code with nonbroken pre-beta code. :( -- ajc
629 cprintf("%d Message %ld is not in this room.\n",
636 * Fetch the message from disk
638 TheMessage = CtdlFetchMessage(msg_num);
639 if (TheMessage == NULL) {
640 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
644 /* Are we downloading a MIME component? */
645 if (mode == MT_DOWNLOAD) {
646 if (TheMessage->cm_format_type != 4) {
647 cprintf("%d This is not a MIME message.\n",
649 } else if (CC->download_fp != NULL) {
650 cprintf("%d You already have a download open.\n",
653 /* Parse the message text component */
654 mptr = TheMessage->cm_fields['M'];
655 mime_parser(mptr, NULL, *mime_download);
656 /* If there's no file open by this time, the requested
657 * section wasn't found, so print an error
659 if (CC->download_fp == NULL) {
660 cprintf("%d Section %s not found.\n",
661 ERROR + FILE_NOT_FOUND,
665 CtdlFreeMessage(TheMessage);
669 /* now for the user-mode message reading loops */
670 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
672 /* Tell the client which format type we're using. If this is a
673 * MIME message, *lie* about it and tell the user it's fixed-format.
675 if (mode == MT_CITADEL) {
676 if (TheMessage->cm_format_type == 4)
679 cprintf("type=%d\n", TheMessage->cm_format_type);
682 /* nhdr=yes means that we're only displaying headers, no body */
683 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
684 cprintf("nhdr=yes\n");
687 /* begin header processing loop for Citadel message format */
689 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
691 if (TheMessage->cm_fields['P']) {
692 cprintf("path=%s\n", TheMessage->cm_fields['P']);
694 if (TheMessage->cm_fields['I']) {
695 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
697 if (TheMessage->cm_fields['T']) {
698 cprintf("time=%s\n", TheMessage->cm_fields['T']);
700 if (TheMessage->cm_fields['A']) {
701 strcpy(buf, TheMessage->cm_fields['A']);
702 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
703 if (TheMessage->cm_anon_type == MES_ANON)
704 cprintf("from=****");
705 else if (TheMessage->cm_anon_type == MES_AN2)
706 cprintf("from=anonymous");
708 cprintf("from=%s", buf);
710 && ((TheMessage->cm_anon_type == MES_ANON)
711 || (TheMessage->cm_anon_type == MES_AN2))) {
712 cprintf(" [%s]", buf);
716 if (TheMessage->cm_fields['O']) {
717 cprintf("room=%s\n", TheMessage->cm_fields['O']);
719 if (TheMessage->cm_fields['N']) {
720 cprintf("node=%s\n", TheMessage->cm_fields['N']);
722 if (TheMessage->cm_fields['H']) {
723 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
725 if (TheMessage->cm_fields['R']) {
726 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
728 if (TheMessage->cm_fields['U']) {
729 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
733 /* begin header processing loop for RFC822 transfer format */
737 strcpy(snode, NODENAME);
738 strcpy(lnode, HUMANNODE);
739 if (mode == MT_RFC822) {
740 for (i = 0; i < 256; ++i) {
741 if (TheMessage->cm_fields[i]) {
742 mptr = TheMessage->cm_fields[i];
746 } else if (i == 'P') {
747 cprintf("Path: %s\n", mptr);
748 for (a = 0; a < strlen(mptr); ++a) {
749 if (mptr[a] == '!') {
750 strcpy(mptr, &mptr[a + 1]);
756 cprintf("Subject: %s\n", mptr);
762 cprintf("X-Citadel-Room: %s\n", mptr);
766 cprintf("To: %s\n", mptr);
769 cprintf("Date: %s", asctime(localtime(&xtime)));
775 if (mode == MT_RFC822) {
776 if (!strcasecmp(snode, NODENAME)) {
779 cprintf("Message-ID: <%s@%s>\n", mid, snode);
780 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
781 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
782 cprintf("Organization: %s\n", lnode);
785 /* end header processing loop ... at this point, we're in the text */
787 mptr = TheMessage->cm_fields['M'];
789 /* Tell the client about the MIME parts in this message */
790 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
791 if (mode == MT_CITADEL) {
792 mime_parser(mptr, NULL, *list_this_part);
794 else if (mode == MT_MIME) { /* list parts only */
795 mime_parser(mptr, NULL, *list_this_part);
797 CtdlFreeMessage(TheMessage);
804 CtdlFreeMessage(TheMessage);
808 /* signify start of msg text */
809 if (mode == MT_CITADEL)
811 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
814 /* If the format type on disk is 1 (fixed-format), then we want
815 * everything to be output completely literally ... regardless of
816 * what message transfer format is in use.
818 if (TheMessage->cm_format_type == 1) {
820 while (ch = *mptr++, ch > 0) {
823 if ((ch == 10) || (strlen(buf) > 250)) {
824 cprintf("%s\n", buf);
827 buf[strlen(buf) + 1] = 0;
828 buf[strlen(buf)] = ch;
832 cprintf("%s\n", buf);
835 /* If the message on disk is format 0 (Citadel vari-format), we
836 * output using the formatter at 80 columns. This is the final output
837 * form if the transfer format is RFC822, but if the transfer format
838 * is Citadel proprietary, it'll still work, because the indentation
839 * for new paragraphs is correct and the client will reformat the
840 * message to the reader's screen width.
842 if (TheMessage->cm_format_type == 0) {
843 memfmout(80, mptr, 0);
846 /* If the message on disk is format 4 (MIME), we've gotta hand it
847 * off to the MIME parser. The client has already been told that
848 * this message is format 1 (fixed format), so the callback function
849 * we use will display those parts as-is.
851 if (TheMessage->cm_format_type == 4) {
852 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
853 memset(ma, 0, sizeof(struct ma_info));
854 mime_parser(mptr, NULL, *fixed_output);
859 CtdlFreeMessage(TheMessage);
866 * display a message (mode 0 - Citadel proprietary)
868 void cmd_msg0(char *cmdbuf)
871 int headers_only = 0;
873 extract(msgid, cmdbuf, 0);
874 headers_only = extract_int(cmdbuf, 1);
876 output_message(msgid, MT_CITADEL, headers_only);
882 * display a message (mode 2 - RFC822)
884 void cmd_msg2(char *cmdbuf)
887 int headers_only = 0;
889 extract(msgid, cmdbuf, 0);
890 headers_only = extract_int(cmdbuf, 1);
892 output_message(msgid, MT_RFC822, headers_only);
898 * display a message (mode 3 - IGnet raw format - internal programs only)
900 void cmd_msg3(char *cmdbuf)
903 struct cdbdata *dmsgtext;
905 if (CC->internal_pgm == 0) {
906 cprintf("%d This command is for internal programs only.\n",
911 msgnum = extract_long(cmdbuf, 0);
913 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
914 if (dmsgtext == NULL) {
915 cprintf("%d Message %ld not found\n", ERROR, msgnum);
918 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
919 client_write(dmsgtext->ptr, dmsgtext->len);
926 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
928 void cmd_msg4(char *cmdbuf)
932 extract(msgid, cmdbuf, 0);
934 output_message(msgid, MT_MIME, 0);
938 * Open a component of a MIME message as a download file
940 void cmd_opna(char *cmdbuf)
944 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
946 extract(msgid, cmdbuf, 0);
947 extract(desired_section, cmdbuf, 1);
949 output_message(msgid, MT_DOWNLOAD, 0);
953 * Message base operation to send a message to the master file
954 * (returns new message number)
956 long send_message(char *message_in_memory,
957 /* pointer to buffer */
958 size_t message_length, /* length of buffer */
960 { /* 1 to generate an I field */
963 char *actual_message;
964 size_t actual_length;
968 /* Get a new message number */
969 newmsgid = get_new_message_number();
972 sprintf(msgidbuf, "I%ld", newmsgid);
973 actual_length = message_length + strlen(msgidbuf) + 1;
974 actual_message = mallok(actual_length);
975 memcpy(actual_message, message_in_memory, 3);
976 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
977 memcpy(&actual_message[strlen(msgidbuf) + 4],
978 &message_in_memory[3], message_length - 3);
980 actual_message = message_in_memory;
981 actual_length = message_length;
984 /* Write our little bundle of joy into the message base */
985 begin_critical_section(S_MSGMAIN);
986 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
987 actual_message, actual_length) < 0) {
988 lprintf(2, "Can't store message\n");
993 end_critical_section(S_MSGMAIN);
996 phree(actual_message);
998 /* Finally, return the pointers */
1005 * this is a simple file copy routine.
1007 void copy_file(char *from, char *to)
1012 ffp = fopen(from, "r");
1015 tfp = fopen(to, "w");
1020 while (a = getc(ffp), a >= 0) {
1031 * message base operation to save a message and install its pointers
1033 void save_message(char *mtmp, /* file containing proper message */
1034 char *rec, /* Recipient (if mail) */
1035 char *force, /* if non-zero length, force a room */
1036 int mailtype, /* local or remote type, see citadel.h */
1038 { /* set to 1 to generate an 'I' field */
1040 char hold_rm[ROOMNAMELEN];
1041 char actual_rm[ROOMNAMELEN];
1042 char force_room[ROOMNAMELEN];
1043 char content_type[256]; /* We have to learn this */
1045 char recipient[256];
1047 char *message_in_memory;
1049 struct stat statbuf;
1052 struct usersupp userbuf;
1054 static int seqnum = 0;
1055 int successful_local_recipients = 0;
1056 struct quickroom qtemp;
1057 struct SuppMsgInfo smi;
1059 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1060 mtmp, rec, force, mailtype, generate_id);
1062 strcpy(force_room, force);
1064 /* Strip non-printable characters out of the recipient name */
1065 strcpy(recipient, rec);
1066 for (a = 0; a < strlen(recipient); ++a)
1067 if (!isprint(recipient[a]))
1068 strcpy(&recipient[a], &recipient[a + 1]);
1070 /* Measure the message */
1071 stat(mtmp, &statbuf);
1072 templen = statbuf.st_size;
1074 /* Now read it into memory */
1075 message_in_memory = (char *) mallok(templen);
1076 if (message_in_memory == NULL) {
1077 lprintf(2, "Can't allocate memory to save message!\n");
1080 fp = fopen(mtmp, "rb");
1081 fread(message_in_memory, templen, 1, fp);
1084 /* Learn about what's inside, because it's what's inside that counts */
1085 mptr = message_in_memory;
1086 ++mptr; /* advance past 0xFF header */
1087 ++mptr; /* advance past anon flag */
1091 strcpy(content_type, "text/x-citadel-variformat");
1094 strcpy(content_type, "text/plain");
1097 strcpy(content_type, "text/plain");
1098 /* advance past header fields */
1099 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1106 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1107 safestrncpy(content_type, mptr,
1108 sizeof(content_type));
1109 lprintf(9, "%s\n", content_type);
1110 strcpy(content_type, &content_type[14]);
1111 for (a = 0; a < strlen(content_type); ++a)
1112 if ((content_type[a] == ';')
1113 || (content_type[a] == ' ')
1114 || (content_type[a] == 13)
1115 || (content_type[a] == 10))
1116 content_type[a] = 0;
1122 lprintf(9, "Content type is <%s>\n", content_type);
1124 /* Save it to disk */
1125 newmsgid = send_message(message_in_memory, templen, generate_id);
1126 phree(message_in_memory);
1130 strcpy(actual_rm, CC->quickroom.QRname);
1131 strcpy(hold_rm, "");
1133 /* If this is being done by the networker delivering a private
1134 * message, we want to BYPASS saving the sender's copy (because there
1135 * is no local sender; it would otherwise go to the Trashcan).
1137 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1138 /* If the user is a twit, move to the twit room for posting */
1140 if (CC->usersupp.axlevel == 2) {
1141 strcpy(hold_rm, actual_rm);
1142 strcpy(actual_rm, config.c_twitroom);
1144 /* ...or if this message is destined for Aide> then go there. */
1145 lprintf(9, "actual room forcing loop\n");
1146 if (strlen(force_room) > 0) {
1147 strcpy(hold_rm, actual_rm);
1148 strcpy(actual_rm, force_room);
1150 /* This call to usergoto() changes rooms if necessary. It also
1151 * causes the latest message list to be read into memory.
1153 usergoto(actual_rm, 0);
1155 /* read in the quickroom record, obtaining a lock... */
1156 lgetroom(&CC->quickroom, actual_rm);
1158 /* Fix an obscure bug */
1159 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1160 CC->quickroom.QRflags =
1161 CC->quickroom.QRflags & ~QR_MAILBOX;
1163 /* Add the message pointer to the room */
1164 CC->quickroom.QRhighest =
1165 AddMessageToRoom(&CC->quickroom, newmsgid);
1167 /* update quickroom */
1168 lputroom(&CC->quickroom);
1169 ++successful_local_recipients;
1171 /* Network mail - send a copy to the network program. */
1172 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1173 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1174 (long) getpid(), CC->cs_pid, ++seqnum);
1175 copy_file(mtmp, aaa);
1176 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1178 /* Bump this user's messages posted counter. */
1179 lgetuser(&CC->usersupp, CC->curr_user);
1180 CC->usersupp.posted = CC->usersupp.posted + 1;
1181 lputuser(&CC->usersupp);
1183 /* If this is private, local mail, make a copy in the
1184 * recipient's mailbox and bump the reference count.
1186 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1187 if (getuser(&userbuf, recipient) == 0) {
1188 MailboxName(actual_rm, &userbuf, MAILROOM);
1189 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1190 if (lgetroom(&qtemp, actual_rm) == 0) {
1192 AddMessageToRoom(&qtemp, newmsgid);
1194 ++successful_local_recipients;
1198 /* If we've posted in a room other than the current room, then we
1199 * have to now go back to the current room...
1201 if (strlen(hold_rm) > 0) {
1202 usergoto(hold_rm, 0);
1204 unlink(mtmp); /* delete the temporary file */
1206 /* Write a supplemental message info record. This doesn't have to
1207 * be a critical section because nobody else knows about this message
1210 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1211 smi.smi_msgnum = newmsgid;
1212 smi.smi_refcount = successful_local_recipients;
1213 safestrncpy(smi.smi_content_type, content_type, 64);
1214 PutSuppMsgInfo(&smi);
1219 * Generate an administrative message and post it in the Aide> room.
1221 void aide_message(char *text)
1225 fp = fopen(CC->temp, "wb");
1226 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1227 fprintf(fp, "Psysop%c", 0);
1228 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1229 fprintf(fp, "ACitadel%c", 0);
1230 fprintf(fp, "OAide%c", 0);
1231 fprintf(fp, "N%s%c", NODENAME, 0);
1232 fprintf(fp, "M%s\n%c", text, 0);
1234 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1235 syslog(LOG_NOTICE, text);
1240 * Build a binary message to be saved on disk.
1244 char *filename, /* temporary file name */
1245 struct usersupp *author, /* author's usersupp structure */
1246 char *recipient, /* NULL if it's not mail */
1247 char *room, /* room where it's going */
1248 int type, /* see MES_ types in header file */
1249 int net_type, /* see MES_ types in header file */
1250 int format_type, /* local or remote (see citadel.h) */
1251 char *fake_name) /* who we're masquerading as */
1261 /* Don't confuse the poor folks if it's not routed mail. */
1262 strcpy(dest_node, "");
1264 /* If net_type is MES_BINARY, split out the destination node. */
1265 if (net_type == MES_BINARY) {
1266 strcpy(dest_node, NODENAME);
1267 for (a = 0; a < strlen(recipient); ++a) {
1268 if (recipient[a] == '@') {
1270 strcpy(dest_node, &recipient[a + 1]);
1275 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1276 strcpy(dest_node, "internet");
1279 while (isspace(recipient[strlen(recipient) - 1]))
1280 recipient[strlen(recipient) - 1] = 0;
1283 fp = fopen(filename, "w");
1285 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1286 putc(format_type, fp); /* Formatted or unformatted */
1287 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1288 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1291 fprintf(fp, "A%s%c", fake_name, 0);
1293 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1295 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1296 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1298 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1301 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1302 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1304 if (recipient[0] != 0)
1305 fprintf(fp, "R%s%c", recipient, 0);
1306 if (dest_node[0] != 0)
1307 fprintf(fp, "D%s%c", dest_node, 0);
1311 while (client_gets(buf), strcmp(buf, "000")) {
1312 if (msglen < config.c_maxmsglen)
1313 fprintf(fp, "%s\n", buf);
1315 lprintf(7, "Message exceeded %d byte limit\n",
1316 config.c_maxmsglen);
1317 msglen = msglen + strlen(buf) + 1;
1329 * message entry - mode 0 (normal)
1331 void cmd_ent0(char *entargs)
1334 char recipient[256];
1336 int format_type = 0;
1337 char newusername[256];
1342 struct usersupp tempUS;
1345 post = extract_int(entargs, 0);
1346 extract(recipient, entargs, 1);
1347 anon_flag = extract_int(entargs, 2);
1348 format_type = extract_int(entargs, 3);
1350 /* first check to make sure the request is valid. */
1352 if (!(CC->logged_in)) {
1353 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1356 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1357 cprintf("%d Need to be validated to enter ",
1358 ERROR + HIGHER_ACCESS_REQUIRED);
1359 cprintf("(except in %s> to sysop)\n", MAILROOM);
1362 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1363 cprintf("%d Need net privileges to enter here.\n",
1364 ERROR + HIGHER_ACCESS_REQUIRED);
1367 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1368 cprintf("%d Sorry, this is a read-only room.\n",
1369 ERROR + HIGHER_ACCESS_REQUIRED);
1376 if (CC->usersupp.axlevel < 6) {
1377 cprintf("%d You don't have permission to masquerade.\n",
1378 ERROR + HIGHER_ACCESS_REQUIRED);
1381 extract(newusername, entargs, 4);
1382 memset(CC->fake_postname, 0, 32);
1383 strcpy(CC->fake_postname, newusername);
1384 cprintf("%d Ok\n", OK);
1387 CC->cs_flags |= CS_POSTING;
1390 if (CC->quickroom.QRflags & QR_MAILBOX) {
1391 if (CC->usersupp.axlevel >= 2) {
1392 strcpy(buf, recipient);
1394 strcpy(buf, "sysop");
1395 lprintf(9, "calling alias()\n");
1396 e = alias(buf); /* alias and mail type */
1397 lprintf(9, "alias() returned %d\n", e);
1398 if ((buf[0] == 0) || (e == MES_ERROR)) {
1399 cprintf("%d Unknown address - cannot send message.\n",
1400 ERROR + NO_SUCH_USER);
1403 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1404 cprintf("%d Net privileges required for network mail.\n",
1405 ERROR + HIGHER_ACCESS_REQUIRED);
1408 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1409 && ((CC->usersupp.flags & US_INTERNET) == 0)
1410 && (!CC->internal_pgm)) {
1411 cprintf("%d You don't have access to Internet mail.\n",
1412 ERROR + HIGHER_ACCESS_REQUIRED);
1415 if (!strcasecmp(buf, "sysop")) {
1420 goto SKFALL; /* don't search local file */
1421 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1422 cprintf("%d Can't send mail to yourself!\n",
1423 ERROR + NO_SUCH_USER);
1426 /* Check to make sure the user exists; also get the correct
1427 * upper/lower casing of the name.
1429 a = getuser(&tempUS, buf);
1431 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1434 strcpy(buf, tempUS.fullname);
1437 SKFALL: b = MES_NORMAL;
1438 if (CC->quickroom.QRflags & QR_ANONONLY)
1440 if (CC->quickroom.QRflags & QR_ANONOPT) {
1444 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1447 /* If we're only checking the validity of the request, return
1448 * success without creating the message.
1451 cprintf("%d %s\n", OK, buf);
1455 cprintf("%d send message\n", SEND_LISTING);
1457 if (CC->fake_postname[0])
1458 make_message(CC->temp, &CC->usersupp, buf,
1459 CC->quickroom.QRname, b, e, format_type,
1461 else if (CC->fake_username[0])
1462 make_message(CC->temp, &CC->usersupp, buf,
1463 CC->quickroom.QRname, b, e, format_type,
1466 make_message(CC->temp, &CC->usersupp, buf,
1467 CC->quickroom.QRname, b, e, format_type, "");
1469 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1470 CC->fake_postname[0] = '\0';
1477 * message entry - mode 3 (raw)
1479 void cmd_ent3(char *entargs)
1485 struct usersupp tempUS;
1490 if (CC->internal_pgm == 0) {
1491 cprintf("%d This command is for internal programs only.\n",
1495 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1496 for (a = 0; a < strlen(recp); ++a)
1497 if (!isprint(recp[a]))
1498 strcpy(&recp[a], &recp[a + 1]);
1499 while (isspace(recp[0]))
1500 strcpy(recp, &recp[1]);
1501 while (isspace(recp[strlen(recp) - 1]))
1502 recp[strlen(recp) - 1] = 0;
1504 /* If we're in Mail, check the recipient */
1505 if (strlen(recp) > 0) {
1506 e = alias(recp); /* alias and mail type */
1507 if ((recp[0] == 0) || (e == MES_ERROR)) {
1508 cprintf("%d Unknown address - cannot send message.\n",
1509 ERROR + NO_SUCH_USER);
1512 if (e == MES_LOCAL) {
1513 a = getuser(&tempUS, recp);
1515 cprintf("%d No such user.\n",
1516 ERROR + NO_SUCH_USER);
1521 /* At this point, message has been approved. */
1522 if (extract_int(entargs, 0) == 0) {
1523 cprintf("%d OK to send\n", OK);
1526 /* open a temp file to hold the message */
1527 fp = fopen(CC->temp, "wb");
1529 cprintf("%d Cannot open %s: %s\n",
1530 ERROR + INTERNAL_ERROR,
1531 CC->temp, strerror(errno));
1534 msglen = extract_long(entargs, 2);
1535 cprintf("%d %ld\n", SEND_BINARY, msglen);
1536 while (msglen > 0L) {
1537 bloklen = ((msglen >= 255L) ? 255 : msglen);
1538 client_read(buf, (int) bloklen);
1539 fwrite(buf, (int) bloklen, 1, fp);
1540 msglen = msglen - bloklen;
1544 save_message(CC->temp, recp, "", e, 0);
1549 * API function to delete messages which match a set of criteria
1550 * (returns the actual number of messages deleted)
1551 * FIX ... still need to implement delete by content type
1553 int CtdlDeleteMessages(char *room_name, /* which room */
1554 long dmsgnum, /* or "0" for any */
1555 char *content_type /* or NULL for any */
1559 struct quickroom qrbuf;
1560 struct cdbdata *cdbfr;
1561 long *msglist = NULL;
1564 int num_deleted = 0;
1566 struct SuppMsgInfo smi;
1568 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1569 room_name, dmsgnum, content_type);
1571 /* get room record, obtaining a lock... */
1572 if (lgetroom(&qrbuf, room_name) != 0) {
1573 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1575 return (0); /* room not found */
1577 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1579 lprintf(9, "doing mallok/memcpy loop\n");
1580 if (cdbfr != NULL) {
1581 msglist = mallok(cdbfr->len);
1582 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1583 num_msgs = cdbfr->len / sizeof(long);
1587 for (i = 0; i < num_msgs; ++i) {
1590 /* Set/clear a bit for each criterion */
1592 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1593 delete_this |= 0x01;
1595 if (content_type == NULL) {
1596 delete_this |= 0x02;
1598 GetSuppMsgInfo(&smi, msglist[i]);
1599 if (!strcasecmp(smi.smi_content_type,
1601 delete_this |= 0x02;
1605 /* Delete message only if all bits are set */
1606 if (delete_this == 0x03) {
1607 AdjRefCount(msglist[i], -1);
1613 num_msgs = sort_msglist(msglist, num_msgs);
1614 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1615 msglist, (num_msgs * sizeof(long)));
1617 qrbuf.QRhighest = msglist[num_msgs - 1];
1621 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1622 return (num_deleted);
1628 * Delete message from current room
1630 void cmd_dele(char *delstr)
1635 getuser(&CC->usersupp, CC->curr_user);
1636 if ((CC->usersupp.axlevel < 6)
1637 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1638 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1639 cprintf("%d Higher access required.\n",
1640 ERROR + HIGHER_ACCESS_REQUIRED);
1643 delnum = extract_long(delstr, 0);
1645 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1648 cprintf("%d %d message%s deleted.\n", OK,
1649 num_deleted, ((num_deleted != 1) ? "s" : ""));
1651 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1657 * move a message to another room
1659 void cmd_move(char *args)
1663 struct quickroom qtemp;
1666 num = extract_long(args, 0);
1667 extract(targ, args, 1);
1669 getuser(&CC->usersupp, CC->curr_user);
1670 if ((CC->usersupp.axlevel < 6)
1671 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1672 cprintf("%d Higher access required.\n",
1673 ERROR + HIGHER_ACCESS_REQUIRED);
1676 if (getroom(&qtemp, targ) != 0) {
1677 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1680 /* Bump the reference count, otherwise the message will be deleted
1681 * from disk when we remove it from the source room.
1683 AdjRefCount(num, 1);
1685 /* yank the message out of the current room... */
1686 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1689 /* put the message into the target room */
1690 lgetroom(&qtemp, targ);
1691 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1693 cprintf("%d Message moved.\n", OK);
1695 AdjRefCount(num, (-1)); /* oops */
1696 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1703 * GetSuppMsgInfo() - Get the supplementary record for a message
1705 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1708 struct cdbdata *cdbsmi;
1711 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1712 smibuf->smi_msgnum = msgnum;
1713 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1715 /* Use the negative of the message number for its supp record index */
1716 TheIndex = (0L - msgnum);
1718 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1719 if (cdbsmi == NULL) {
1720 return; /* record not found; go with defaults */
1722 memcpy(smibuf, cdbsmi->ptr,
1723 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1724 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1731 * PutSuppMsgInfo() - (re)write supplementary record for a message
1733 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1737 /* Use the negative of the message number for its supp record index */
1738 TheIndex = (0L - smibuf->smi_msgnum);
1740 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1741 smibuf->smi_msgnum, smibuf->smi_refcount);
1743 cdb_store(CDB_MSGMAIN,
1744 &TheIndex, sizeof(long),
1745 smibuf, sizeof(struct SuppMsgInfo));
1749 * AdjRefCount - change the reference count for a message;
1750 * delete the message if it reaches zero
1751 */ void AdjRefCount(long msgnum, int incr)
1754 struct SuppMsgInfo smi;
1757 /* This is a *tight* critical section; please keep it that way, as
1758 * it may get called while nested in other critical sections.
1759 * Complicating this any further will surely cause deadlock!
1761 begin_critical_section(S_SUPPMSGMAIN);
1762 GetSuppMsgInfo(&smi, msgnum);
1763 smi.smi_refcount += incr;
1764 PutSuppMsgInfo(&smi);
1765 end_critical_section(S_SUPPMSGMAIN);
1767 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1768 msgnum, smi.smi_refcount);
1770 /* If the reference count is now zero, delete the message
1771 * (and its supplementary record as well).
1773 if (smi.smi_refcount == 0) {
1774 lprintf(9, "Deleting message <%ld>\n", msgnum);
1776 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1777 delnum = (0L - msgnum);
1778 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1783 * Write a generic object to this room
1785 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1786 char *content_type, /* MIME type of this object */
1787 char *tempfilename, /* Where to fetch it from */
1788 int is_mailbox, /* Private mailbox room? */
1789 int is_binary, /* Is encoding necessary? */
1790 int is_unique /* Del others of this type? */
1795 char filename[PATH_MAX];
1798 struct quickroom qrbuf;
1799 char roomname[ROOMNAMELEN];
1802 MailboxName(roomname, &CC->usersupp, req_room);
1804 safestrncpy(roomname, req_room, sizeof(roomname));
1806 strcpy(filename, tmpnam(NULL));
1807 fp = fopen(filename, "w");
1811 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1812 fprintf(fp, "T%ld%c", time(NULL), 0);
1813 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1814 fprintf(fp, "O%s%c", roomname, 0);
1815 fprintf(fp, "N%s%c", config.c_nodename, 0);
1816 fprintf(fp, "MContent-type: %s\n", content_type);
1818 tempfp = fopen(tempfilename, "r");
1819 if (tempfp == NULL) {
1824 if (is_binary == 0) {
1825 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1826 while (ch = getc(tempfp), ch > 0)
1832 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1835 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1836 tempfilename, filename);
1840 /* Create the requested room if we have to. */
1841 if (getroom(&qrbuf, roomname) != 0) {
1842 create_room(roomname, 4, "", 0);
1844 /* If the caller specified this object as unique, delete all
1845 * other objects of this type that are currently in the room.
1848 lprintf(9, "Deleted %d other msgs of this type\n",
1849 CtdlDeleteMessages(roomname, 0L, content_type));
1851 /* Now write the data */
1852 save_message(filename, "", roomname, MES_LOCAL, 1);