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) <bc>
1318 void cmd_ent0(char *entargs)
1321 char recipient[256];
1323 int format_type = 0;
1324 char newusername[256]; /* <bc> */
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);
1362 if (post == 2) { /* <bc> */
1363 if (CC->usersupp.axlevel < 6) {
1364 cprintf("%d You don't have permission to do an aide post.\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);
1423 SKFALL:b = MES_NORMAL;
1424 if (CC->quickroom.QRflags & QR_ANONONLY)
1426 if (CC->quickroom.QRflags & QR_ANONOPT) {
1430 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1433 /* If we're only checking the validity of the request, return
1434 * success without creating the message.
1437 cprintf("%d %s\n", OK, buf);
1440 cprintf("%d send message\n", SEND_LISTING);
1441 if (CC->fake_postname[0])
1442 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1443 else if (CC->fake_username[0])
1444 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1446 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1447 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1448 CC->fake_postname[0] = '\0';
1455 * message entry - mode 3 (raw)
1457 void cmd_ent3(char *entargs)
1463 struct usersupp tempUS;
1468 if (CC->internal_pgm == 0) {
1469 cprintf("%d This command is for internal programs only.\n",
1473 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1474 for (a = 0; a < strlen(recp); ++a)
1475 if (!isprint(recp[a]))
1476 strcpy(&recp[a], &recp[a + 1]);
1477 while (isspace(recp[0]))
1478 strcpy(recp, &recp[1]);
1479 while (isspace(recp[strlen(recp) - 1]))
1480 recp[strlen(recp) - 1] = 0;
1482 /* If we're in Mail, check the recipient */
1483 if (strlen(recp) > 0) {
1484 e = alias(recp); /* alias and mail type */
1485 if ((recp[0] == 0) || (e == MES_ERROR)) {
1486 cprintf("%d Unknown address - cannot send message.\n",
1487 ERROR + NO_SUCH_USER);
1490 if (e == MES_LOCAL) {
1491 a = getuser(&tempUS, recp);
1493 cprintf("%d No such user.\n",
1494 ERROR + NO_SUCH_USER);
1499 /* At this point, message has been approved. */
1500 if (extract_int(entargs, 0) == 0) {
1501 cprintf("%d OK to send\n", OK);
1504 /* open a temp file to hold the message */
1505 fp = fopen(CC->temp, "wb");
1507 cprintf("%d Cannot open %s: %s\n",
1508 ERROR + INTERNAL_ERROR,
1509 CC->temp, strerror(errno));
1512 msglen = extract_long(entargs, 2);
1513 cprintf("%d %ld\n", SEND_BINARY, msglen);
1514 while (msglen > 0L) {
1515 bloklen = ((msglen >= 255L) ? 255 : msglen);
1516 client_read(buf, (int) bloklen);
1517 fwrite(buf, (int) bloklen, 1, fp);
1518 msglen = msglen - bloklen;
1522 save_message(CC->temp, recp, "", e, 0);
1527 * API function to delete messages which match a set of criteria
1528 * (returns the actual number of messages deleted)
1529 * FIX ... still need to implement delete by content type
1531 int CtdlDeleteMessages(char *room_name, /* which room */
1532 long dmsgnum, /* or "0" for any */
1533 char *content_type /* or NULL for any */
1537 struct quickroom qrbuf;
1538 struct cdbdata *cdbfr;
1539 long *msglist = NULL;
1542 int num_deleted = 0;
1544 struct SuppMsgInfo smi;
1546 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1547 room_name, dmsgnum, content_type);
1549 /* get room record, obtaining a lock... */
1550 if (lgetroom(&qrbuf, room_name) != 0) {
1551 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1553 return (0); /* room not found */
1555 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1557 lprintf(9, "doing mallok/memcpy loop\n");
1558 if (cdbfr != NULL) {
1559 msglist = mallok(cdbfr->len);
1560 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1561 num_msgs = cdbfr->len / sizeof(long);
1565 for (i = 0; i < num_msgs; ++i) {
1568 /* Set/clear a bit for each criterion */
1570 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1571 delete_this |= 0x01;
1573 if (content_type == NULL) {
1574 delete_this |= 0x02;
1576 GetSuppMsgInfo(&smi, msglist[i]);
1577 if (!strcasecmp(smi.smi_content_type,
1579 delete_this |= 0x02;
1583 /* Delete message only if all bits are set */
1584 if (delete_this == 0x03) {
1585 AdjRefCount(msglist[i], -1);
1591 num_msgs = sort_msglist(msglist, num_msgs);
1592 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1593 msglist, (num_msgs * sizeof(long)));
1595 qrbuf.QRhighest = msglist[num_msgs - 1];
1599 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1600 return (num_deleted);
1606 * Delete message from current room
1608 void cmd_dele(char *delstr)
1613 getuser(&CC->usersupp, CC->curr_user);
1614 if ((CC->usersupp.axlevel < 6)
1615 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1616 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1617 cprintf("%d Higher access required.\n",
1618 ERROR + HIGHER_ACCESS_REQUIRED);
1621 delnum = extract_long(delstr, 0);
1623 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1626 cprintf("%d %d message%s deleted.\n", OK,
1627 num_deleted, ((num_deleted != 1) ? "s" : ""));
1629 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1635 * move a message to another room
1637 void cmd_move(char *args)
1641 struct quickroom qtemp;
1644 num = extract_long(args, 0);
1645 extract(targ, args, 1);
1647 getuser(&CC->usersupp, CC->curr_user);
1648 if ((CC->usersupp.axlevel < 6)
1649 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1650 cprintf("%d Higher access required.\n",
1651 ERROR + HIGHER_ACCESS_REQUIRED);
1654 if (getroom(&qtemp, targ) != 0) {
1655 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1658 /* Bump the reference count, otherwise the message will be deleted
1659 * from disk when we remove it from the source room.
1661 AdjRefCount(num, 1);
1663 /* yank the message out of the current room... */
1664 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1667 /* put the message into the target room */
1668 lgetroom(&qtemp, targ);
1669 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1671 cprintf("%d Message moved.\n", OK);
1673 AdjRefCount(num, (-1)); /* oops */
1674 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1681 * GetSuppMsgInfo() - Get the supplementary record for a message
1683 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1686 struct cdbdata *cdbsmi;
1689 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1690 smibuf->smi_msgnum = msgnum;
1691 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1693 /* Use the negative of the message number for its supp record index */
1694 TheIndex = (0L - msgnum);
1696 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1697 if (cdbsmi == NULL) {
1698 return; /* record not found; go with defaults */
1700 memcpy(smibuf, cdbsmi->ptr,
1701 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1702 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1709 * PutSuppMsgInfo() - (re)write supplementary record for a message
1711 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1715 /* Use the negative of the message number for its supp record index */
1716 TheIndex = (0L - smibuf->smi_msgnum);
1718 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1719 smibuf->smi_msgnum, smibuf->smi_refcount);
1721 cdb_store(CDB_MSGMAIN,
1722 &TheIndex, sizeof(long),
1723 smibuf, sizeof(struct SuppMsgInfo));
1727 * AdjRefCount - change the reference count for a message;
1728 * delete the message if it reaches zero
1729 */ void AdjRefCount(long msgnum, int incr)
1732 struct SuppMsgInfo smi;
1735 /* This is a *tight* critical section; please keep it that way, as
1736 * it may get called while nested in other critical sections.
1737 * Complicating this any further will surely cause deadlock!
1739 begin_critical_section(S_SUPPMSGMAIN);
1740 GetSuppMsgInfo(&smi, msgnum);
1741 smi.smi_refcount += incr;
1742 PutSuppMsgInfo(&smi);
1743 end_critical_section(S_SUPPMSGMAIN);
1745 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1746 msgnum, smi.smi_refcount);
1748 /* If the reference count is now zero, delete the message
1749 * (and its supplementary record as well).
1751 if (smi.smi_refcount == 0) {
1752 lprintf(9, "Deleting message <%ld>\n", msgnum);
1754 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1755 delnum = (0L - msgnum);
1756 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1761 * Write a generic object to this room
1763 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1764 char *content_type, /* MIME type of this object */
1765 char *tempfilename, /* Where to fetch it from */
1766 int is_mailbox, /* Private mailbox room? */
1767 int is_binary, /* Is encoding necessary? */
1768 int is_unique /* Del others of this type? */
1773 char filename[PATH_MAX];
1776 struct quickroom qrbuf;
1777 char roomname[ROOMNAMELEN];
1780 MailboxName(roomname, &CC->usersupp, req_room);
1782 safestrncpy(roomname, req_room, sizeof(roomname));
1784 strcpy(filename, tmpnam(NULL));
1785 fp = fopen(filename, "w");
1789 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1790 fprintf(fp, "T%ld%c", time(NULL), 0);
1791 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1792 fprintf(fp, "O%s%c", roomname, 0);
1793 fprintf(fp, "N%s%c", config.c_nodename, 0);
1794 fprintf(fp, "MContent-type: %s\n", content_type);
1796 tempfp = fopen(tempfilename, "r");
1797 if (tempfp == NULL) {
1802 if (is_binary == 0) {
1803 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1804 while (ch = getc(tempfp), ch > 0)
1810 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1813 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1814 tempfilename, filename);
1818 /* Create the requested room if we have to. */
1819 if (getroom(&qrbuf, roomname) != 0) {
1820 create_room(roomname, 4, "", 0);
1822 /* If the caller specified this object as unique, delete all
1823 * other objects of this type that are currently in the room.
1826 lprintf(9, "Deleted %d other msgs of this type\n",
1827 CtdlDeleteMessages(roomname, 0L, content_type));
1829 /* Now write the data */
1830 save_message(filename, "", roomname, MES_LOCAL, 1);