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);
1238 * Build a binary message to be saved on disk.
1239 */ void make_message(
1240 char *filename, /* temporary file name */
1241 struct usersupp *author, /* author's usersupp structure */
1242 char *recipient, /* NULL if it's not mail */
1243 char *room, /* room where it's going */
1244 int type, /* see MES_ types in header file */
1245 int net_type, /* see MES_ types in header file */
1246 int format_type, /* local or remote (see citadel.h) */
1248 { /* who we're masquerading as */
1256 /* Don't confuse the poor folks if it's not routed mail. */
1257 strcpy(dest_node, "");
1260 /* If net_type is MES_BINARY, split out the destination node. */
1261 if (net_type == MES_BINARY) {
1262 strcpy(dest_node, NODENAME);
1263 for (a = 0; a < strlen(recipient); ++a) {
1264 if (recipient[a] == '@') {
1266 strcpy(dest_node, &recipient[a + 1]);
1270 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1271 strcpy(dest_node, "internet");
1273 while (isspace(recipient[strlen(recipient) - 1]))
1274 recipient[strlen(recipient) - 1] = 0;
1277 fp = fopen(filename, "w");
1279 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1280 putc(format_type, fp); /* Formatted or unformatted */
1281 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1282 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1284 fprintf(fp, "A%s%c", fake_name, 0);
1286 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1288 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1289 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1291 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1294 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1295 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1297 if (recipient[0] != 0)
1298 fprintf(fp, "R%s%c", recipient, 0);
1299 if (dest_node[0] != 0)
1300 fprintf(fp, "D%s%c", dest_node, 0);
1304 while (client_gets(buf), strcmp(buf, "000")) {
1305 fprintf(fp, "%s\n", buf);
1316 * message entry - mode 0 (normal)
1318 void cmd_ent0(char *entargs)
1321 char recipient[256];
1323 int format_type = 0;
1324 char newusername[256];
1329 struct usersupp tempUS;
1332 post = extract_int(entargs, 0);
1333 extract(recipient, entargs, 1);
1334 anon_flag = extract_int(entargs, 2);
1335 format_type = extract_int(entargs, 3);
1337 /* first check to make sure the request is valid. */
1339 if (!(CC->logged_in)) {
1340 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1343 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1344 cprintf("%d Need to be validated to enter ",
1345 ERROR + HIGHER_ACCESS_REQUIRED);
1346 cprintf("(except in %s> to sysop)\n", MAILROOM);
1349 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1350 cprintf("%d Need net privileges to enter here.\n",
1351 ERROR + HIGHER_ACCESS_REQUIRED);
1354 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1355 cprintf("%d Sorry, this is a read-only room.\n",
1356 ERROR + HIGHER_ACCESS_REQUIRED);
1363 if (CC->usersupp.axlevel < 6) {
1364 cprintf("%d You don't have permission to masquerade.\n",
1365 ERROR + HIGHER_ACCESS_REQUIRED);
1368 extract(newusername, entargs, 4);
1369 memset(CC->fake_postname, 0, 32);
1370 strcpy(CC->fake_postname, newusername);
1371 cprintf("%d Ok\n", OK);
1374 CC->cs_flags |= CS_POSTING;
1377 if (CC->quickroom.QRflags & QR_MAILBOX) {
1378 if (CC->usersupp.axlevel >= 2) {
1379 strcpy(buf, recipient);
1381 strcpy(buf, "sysop");
1382 lprintf(9, "calling alias()\n");
1383 e = alias(buf); /* alias and mail type */
1384 lprintf(9, "alias() returned %d\n", e);
1385 if ((buf[0] == 0) || (e == MES_ERROR)) {
1386 cprintf("%d Unknown address - cannot send message.\n",
1387 ERROR + NO_SUCH_USER);
1390 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1391 cprintf("%d Net privileges required for network mail.\n",
1392 ERROR + HIGHER_ACCESS_REQUIRED);
1395 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1396 && ((CC->usersupp.flags & US_INTERNET) == 0)
1397 && (!CC->internal_pgm)) {
1398 cprintf("%d You don't have access to Internet mail.\n",
1399 ERROR + HIGHER_ACCESS_REQUIRED);
1402 if (!strcasecmp(buf, "sysop")) {
1407 goto SKFALL; /* don't search local file */
1408 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1409 cprintf("%d Can't send mail to yourself!\n",
1410 ERROR + NO_SUCH_USER);
1413 /* Check to make sure the user exists; also get the correct
1414 * upper/lower casing of the name.
1416 a = getuser(&tempUS, buf);
1418 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1421 strcpy(buf, tempUS.fullname);
1424 SKFALL: b = MES_NORMAL;
1425 if (CC->quickroom.QRflags & QR_ANONONLY)
1427 if (CC->quickroom.QRflags & QR_ANONOPT) {
1431 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1434 /* If we're only checking the validity of the request, return
1435 * success without creating the message.
1438 cprintf("%d %s\n", OK, buf);
1442 cprintf("%d send message\n", SEND_LISTING);
1444 if (CC->fake_postname[0])
1445 make_message(CC->temp, &CC->usersupp, buf,
1446 CC->quickroom.QRname, b, e, format_type,
1448 else if (CC->fake_username[0])
1449 make_message(CC->temp, &CC->usersupp, buf,
1450 CC->quickroom.QRname, b, e, format_type,
1453 make_message(CC->temp, &CC->usersupp, buf,
1454 CC->quickroom.QRname, b, e, format_type, "");
1456 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1457 CC->fake_postname[0] = '\0';
1464 * message entry - mode 3 (raw)
1466 void cmd_ent3(char *entargs)
1472 struct usersupp tempUS;
1477 if (CC->internal_pgm == 0) {
1478 cprintf("%d This command is for internal programs only.\n",
1482 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1483 for (a = 0; a < strlen(recp); ++a)
1484 if (!isprint(recp[a]))
1485 strcpy(&recp[a], &recp[a + 1]);
1486 while (isspace(recp[0]))
1487 strcpy(recp, &recp[1]);
1488 while (isspace(recp[strlen(recp) - 1]))
1489 recp[strlen(recp) - 1] = 0;
1491 /* If we're in Mail, check the recipient */
1492 if (strlen(recp) > 0) {
1493 e = alias(recp); /* alias and mail type */
1494 if ((recp[0] == 0) || (e == MES_ERROR)) {
1495 cprintf("%d Unknown address - cannot send message.\n",
1496 ERROR + NO_SUCH_USER);
1499 if (e == MES_LOCAL) {
1500 a = getuser(&tempUS, recp);
1502 cprintf("%d No such user.\n",
1503 ERROR + NO_SUCH_USER);
1508 /* At this point, message has been approved. */
1509 if (extract_int(entargs, 0) == 0) {
1510 cprintf("%d OK to send\n", OK);
1513 /* open a temp file to hold the message */
1514 fp = fopen(CC->temp, "wb");
1516 cprintf("%d Cannot open %s: %s\n",
1517 ERROR + INTERNAL_ERROR,
1518 CC->temp, strerror(errno));
1521 msglen = extract_long(entargs, 2);
1522 cprintf("%d %ld\n", SEND_BINARY, msglen);
1523 while (msglen > 0L) {
1524 bloklen = ((msglen >= 255L) ? 255 : msglen);
1525 client_read(buf, (int) bloklen);
1526 fwrite(buf, (int) bloklen, 1, fp);
1527 msglen = msglen - bloklen;
1531 save_message(CC->temp, recp, "", e, 0);
1536 * API function to delete messages which match a set of criteria
1537 * (returns the actual number of messages deleted)
1538 * FIX ... still need to implement delete by content type
1540 int CtdlDeleteMessages(char *room_name, /* which room */
1541 long dmsgnum, /* or "0" for any */
1542 char *content_type /* or NULL for any */
1546 struct quickroom qrbuf;
1547 struct cdbdata *cdbfr;
1548 long *msglist = NULL;
1551 int num_deleted = 0;
1553 struct SuppMsgInfo smi;
1555 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1556 room_name, dmsgnum, content_type);
1558 /* get room record, obtaining a lock... */
1559 if (lgetroom(&qrbuf, room_name) != 0) {
1560 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1562 return (0); /* room not found */
1564 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1566 lprintf(9, "doing mallok/memcpy loop\n");
1567 if (cdbfr != NULL) {
1568 msglist = mallok(cdbfr->len);
1569 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1570 num_msgs = cdbfr->len / sizeof(long);
1574 for (i = 0; i < num_msgs; ++i) {
1577 /* Set/clear a bit for each criterion */
1579 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1580 delete_this |= 0x01;
1582 if (content_type == NULL) {
1583 delete_this |= 0x02;
1585 GetSuppMsgInfo(&smi, msglist[i]);
1586 if (!strcasecmp(smi.smi_content_type,
1588 delete_this |= 0x02;
1592 /* Delete message only if all bits are set */
1593 if (delete_this == 0x03) {
1594 AdjRefCount(msglist[i], -1);
1600 num_msgs = sort_msglist(msglist, num_msgs);
1601 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1602 msglist, (num_msgs * sizeof(long)));
1604 qrbuf.QRhighest = msglist[num_msgs - 1];
1608 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1609 return (num_deleted);
1615 * Delete message from current room
1617 void cmd_dele(char *delstr)
1622 getuser(&CC->usersupp, CC->curr_user);
1623 if ((CC->usersupp.axlevel < 6)
1624 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1625 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1626 cprintf("%d Higher access required.\n",
1627 ERROR + HIGHER_ACCESS_REQUIRED);
1630 delnum = extract_long(delstr, 0);
1632 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1635 cprintf("%d %d message%s deleted.\n", OK,
1636 num_deleted, ((num_deleted != 1) ? "s" : ""));
1638 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1644 * move a message to another room
1646 void cmd_move(char *args)
1650 struct quickroom qtemp;
1653 num = extract_long(args, 0);
1654 extract(targ, args, 1);
1656 getuser(&CC->usersupp, CC->curr_user);
1657 if ((CC->usersupp.axlevel < 6)
1658 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1659 cprintf("%d Higher access required.\n",
1660 ERROR + HIGHER_ACCESS_REQUIRED);
1663 if (getroom(&qtemp, targ) != 0) {
1664 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1667 /* Bump the reference count, otherwise the message will be deleted
1668 * from disk when we remove it from the source room.
1670 AdjRefCount(num, 1);
1672 /* yank the message out of the current room... */
1673 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1676 /* put the message into the target room */
1677 lgetroom(&qtemp, targ);
1678 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1680 cprintf("%d Message moved.\n", OK);
1682 AdjRefCount(num, (-1)); /* oops */
1683 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1690 * GetSuppMsgInfo() - Get the supplementary record for a message
1692 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1695 struct cdbdata *cdbsmi;
1698 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1699 smibuf->smi_msgnum = msgnum;
1700 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1702 /* Use the negative of the message number for its supp record index */
1703 TheIndex = (0L - msgnum);
1705 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1706 if (cdbsmi == NULL) {
1707 return; /* record not found; go with defaults */
1709 memcpy(smibuf, cdbsmi->ptr,
1710 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1711 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1718 * PutSuppMsgInfo() - (re)write supplementary record for a message
1720 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1724 /* Use the negative of the message number for its supp record index */
1725 TheIndex = (0L - smibuf->smi_msgnum);
1727 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1728 smibuf->smi_msgnum, smibuf->smi_refcount);
1730 cdb_store(CDB_MSGMAIN,
1731 &TheIndex, sizeof(long),
1732 smibuf, sizeof(struct SuppMsgInfo));
1736 * AdjRefCount - change the reference count for a message;
1737 * delete the message if it reaches zero
1738 */ void AdjRefCount(long msgnum, int incr)
1741 struct SuppMsgInfo smi;
1744 /* This is a *tight* critical section; please keep it that way, as
1745 * it may get called while nested in other critical sections.
1746 * Complicating this any further will surely cause deadlock!
1748 begin_critical_section(S_SUPPMSGMAIN);
1749 GetSuppMsgInfo(&smi, msgnum);
1750 smi.smi_refcount += incr;
1751 PutSuppMsgInfo(&smi);
1752 end_critical_section(S_SUPPMSGMAIN);
1754 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1755 msgnum, smi.smi_refcount);
1757 /* If the reference count is now zero, delete the message
1758 * (and its supplementary record as well).
1760 if (smi.smi_refcount == 0) {
1761 lprintf(9, "Deleting message <%ld>\n", msgnum);
1763 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1764 delnum = (0L - msgnum);
1765 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1770 * Write a generic object to this room
1772 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1773 char *content_type, /* MIME type of this object */
1774 char *tempfilename, /* Where to fetch it from */
1775 int is_mailbox, /* Private mailbox room? */
1776 int is_binary, /* Is encoding necessary? */
1777 int is_unique /* Del others of this type? */
1782 char filename[PATH_MAX];
1785 struct quickroom qrbuf;
1786 char roomname[ROOMNAMELEN];
1789 MailboxName(roomname, &CC->usersupp, req_room);
1791 safestrncpy(roomname, req_room, sizeof(roomname));
1793 strcpy(filename, tmpnam(NULL));
1794 fp = fopen(filename, "w");
1798 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1799 fprintf(fp, "T%ld%c", time(NULL), 0);
1800 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1801 fprintf(fp, "O%s%c", roomname, 0);
1802 fprintf(fp, "N%s%c", config.c_nodename, 0);
1803 fprintf(fp, "MContent-type: %s\n", content_type);
1805 tempfp = fopen(tempfilename, "r");
1806 if (tempfp == NULL) {
1811 if (is_binary == 0) {
1812 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1813 while (ch = getc(tempfp), ch > 0)
1819 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1822 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1823 tempfilename, filename);
1827 /* Create the requested room if we have to. */
1828 if (getroom(&qrbuf, roomname) != 0) {
1829 create_room(roomname, 4, "", 0);
1831 /* If the caller specified this object as unique, delete all
1832 * other objects of this type that are currently in the room.
1835 lprintf(9, "Deleted %d other msgs of this type\n",
1836 CtdlDeleteMessages(roomname, 0L, content_type));
1838 /* Now write the data */
1839 save_message(filename, "", roomname, MES_LOCAL, 1);