22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
32 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
34 extern struct config config;
38 * This function is self explanatory.
39 * (What can I say, I'm in a weird mood today...)
41 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
45 for (i = 0; i < strlen(name); ++i)
48 if (isspace(name[i - 1])) {
49 strcpy(&name[i - 1], &name[i]);
52 while (isspace(name[i + 1])) {
53 strcpy(&name[i + 1], &name[i + 2]);
60 * Aliasing for network mail.
61 * (Error messages have been commented out, because this is a server.)
64 { /* process alias and routing info for mail */
67 char aaa[300], bbb[300];
69 lprintf(9, "alias() called for <%s>\n", name);
71 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
73 fp = fopen("network/mail.aliases", "r");
75 fp = fopen("/dev/null", "r");
80 while (fgets(aaa, sizeof aaa, fp) != NULL) {
81 while (isspace(name[0]))
82 strcpy(name, &name[1]);
83 aaa[strlen(aaa) - 1] = 0;
85 for (a = 0; a < strlen(aaa); ++a) {
87 strcpy(bbb, &aaa[a + 1]);
91 if (!strcasecmp(name, aaa))
95 lprintf(7, "Mail is being forwarded to %s\n", name);
97 /* determine local or remote type, see citadel.h */
98 for (a = 0; a < strlen(name); ++a)
100 return (MES_INTERNET);
101 for (a = 0; a < strlen(name); ++a)
103 for (b = a; b < strlen(name); ++b)
105 return (MES_INTERNET);
107 for (a = 0; a < strlen(name); ++a)
111 lprintf(7, "Too many @'s in address\n");
115 for (a = 0; a < strlen(name); ++a)
117 strcpy(bbb, &name[a + 1]);
119 strcpy(bbb, &bbb[1]);
120 fp = fopen("network/mail.sysinfo", "r");
124 a = getstring(fp, aaa);
125 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
126 a = getstring(fp, aaa);
127 if (!strncmp(aaa, "use ", 4)) {
128 strcpy(bbb, &aaa[4]);
133 if (!strncmp(aaa, "uum", 3)) {
135 for (a = 0; a < strlen(bbb); ++a) {
141 while (bbb[strlen(bbb) - 1] == '_')
142 bbb[strlen(bbb) - 1] = 0;
143 sprintf(name, &aaa[4], bbb);
144 return (MES_INTERNET);
146 if (!strncmp(aaa, "bin", 3)) {
149 while (aaa[strlen(aaa) - 1] != '@')
150 aaa[strlen(aaa) - 1] = 0;
151 aaa[strlen(aaa) - 1] = 0;
152 while (aaa[strlen(aaa) - 1] == ' ')
153 aaa[strlen(aaa) - 1] = 0;
154 while (bbb[0] != '@')
155 strcpy(bbb, &bbb[1]);
156 strcpy(bbb, &bbb[1]);
157 while (bbb[0] == ' ')
158 strcpy(bbb, &bbb[1]);
159 sprintf(name, "%s @%s", aaa, bbb);
172 fp = fopen("citadel.control", "r");
173 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
179 void simple_listing(long msgnum)
181 cprintf("%ld\n", msgnum);
186 * API function to perform an operation for each qualifying message in the
189 void CtdlForEachMessage(int mode, long ref,
191 void (*CallBack) (long msgnum))
196 struct cdbdata *cdbfr;
197 long *msglist = NULL;
200 struct SuppMsgInfo smi;
202 /* Learn about the user and room in question */
204 getuser(&CC->usersupp, CC->curr_user);
205 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
207 /* Load the message list */
208 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
210 msglist = mallok(cdbfr->len);
211 memcpy(msglist, cdbfr->ptr, cdbfr->len);
212 num_msgs = cdbfr->len / sizeof(long);
215 return; /* No messages at all? No further action. */
219 /* If the caller is looking for a specific MIME type, then filter
220 * out all messages which are not of the type requested.
223 if (content_type != NULL)
224 if (strlen(content_type) > 0)
225 for (a = 0; a < num_msgs; ++a) {
226 GetSuppMsgInfo(&smi, msglist[a]);
227 if (strcasecmp(smi.smi_content_type, content_type)) {
232 num_msgs = sort_msglist(msglist, num_msgs);
235 * Now iterate through the message list, according to the
236 * criteria supplied by the caller.
239 for (a = 0; a < num_msgs; ++a) {
240 thismsg = msglist[a];
241 lprintf(9, "Iterating through <%ld>\n", thismsg);
246 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
247 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
248 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
249 && (CC->usersupp.flags & US_LASTOLD))
250 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
251 || ((mode == MSGS_FIRST) && (a < ref))
252 || ((mode == MSGS_GT) && (thismsg > ref))
255 lprintf(9, "Issuing callback for <%ld>\n", thismsg);
259 phree(msglist); /* Clean up */
265 * cmd_msgs() - get list of message #'s in this room
266 * implements the MSGS server command using CtdlForEachMessage()
268 void cmd_msgs(char *cmdbuf)
274 extract(which, cmdbuf, 0);
275 cm_ref = extract_int(cmdbuf, 1);
279 if (!strncasecmp(which, "OLD", 3))
281 else if (!strncasecmp(which, "NEW", 3))
283 else if (!strncasecmp(which, "FIRST", 5))
285 else if (!strncasecmp(which, "LAST", 4))
287 else if (!strncasecmp(which, "GT", 2))
290 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
291 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
294 cprintf("%d Message list...\n", LISTING_FOLLOWS);
295 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
303 * help_subst() - support routine for help file viewer
305 void help_subst(char *strbuf, char *source, char *dest)
310 while (p = pattern2(strbuf, source), (p >= 0)) {
311 strcpy(workbuf, &strbuf[p + strlen(source)]);
312 strcpy(&strbuf[p], dest);
313 strcat(strbuf, workbuf);
318 void do_help_subst(char *buffer)
322 help_subst(buffer, "^nodename", config.c_nodename);
323 help_subst(buffer, "^humannode", config.c_humannode);
324 help_subst(buffer, "^fqdn", config.c_fqdn);
325 help_subst(buffer, "^username", CC->usersupp.fullname);
326 sprintf(buf2, "%ld", CC->usersupp.usernum);
327 help_subst(buffer, "^usernum", buf2);
328 help_subst(buffer, "^sysadm", config.c_sysadm);
329 help_subst(buffer, "^variantname", CITADEL);
330 sprintf(buf2, "%d", config.c_maxsessions);
331 help_subst(buffer, "^maxsessions", buf2);
337 * memfmout() - Citadel text formatter and paginator.
338 * Although the original purpose of this routine was to format
339 * text to the reader's screen width, all we're really using it
340 * for here is to format text out to 80 columns before sending it
341 * to the client. The client software may reformat it again.
343 void memfmout(int width, char *mptr, char subst)
344 /* screen width to use */
345 /* where are we going to get our text from? */
346 /* nonzero if we should use hypertext mode */
358 c = 1; /* c is the current pos */
361 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
363 buffer[strlen(buffer) + 1] = 0;
364 buffer[strlen(buffer)] = ch;
367 if (buffer[0] == '^')
368 do_help_subst(buffer);
370 buffer[strlen(buffer) + 1] = 0;
372 strcpy(buffer, &buffer[1]);
381 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
383 if (((old == 13) || (old == 10)) && (isspace(real))) {
391 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
392 cprintf("\n%s", aaa);
401 if ((strlen(aaa) + c) > (width - 5)) {
411 if ((ch == 13) || (ch == 10)) {
412 cprintf("%s\n", aaa);
419 FMTEND:cprintf("%s\n", aaa);
425 * Callback function for mime parser that simply lists the part
427 void list_this_part(char *name, char *filename, char *partnum, char *disp,
428 void *content, char *cbtype, size_t length)
431 cprintf("part=%s|%s|%s|%s|%s|%d\n",
432 name, filename, partnum, disp, cbtype, length);
437 * Callback function for mime parser that wants to display text
439 void fixed_output(char *name, char *filename, char *partnum, char *disp,
440 void *content, char *cbtype, size_t length)
443 if (!strcasecmp(cbtype, "text/plain")) {
444 client_write(content, length);
446 cprintf("Part %s: %s (%s) (%d bytes)\n",
447 partnum, filename, cbtype, length);
453 * Callback function for mime parser that opens a section for downloading
455 void mime_download(char *name, char *filename, char *partnum, char *disp,
456 void *content, char *cbtype, size_t length)
459 /* Silently go away if there's already a download open... */
460 if (CC->download_fp != NULL)
463 /* ...or if this is not the desired section */
464 if (strcasecmp(desired_section, partnum))
467 CC->download_fp = tmpfile();
468 if (CC->download_fp == NULL)
471 fwrite(content, length, 1, CC->download_fp);
472 fflush(CC->download_fp);
473 rewind(CC->download_fp);
475 OpenCmdResult(filename, cbtype);
481 * Load a message from disk into memory.
482 * This is used by output_message() and other fetch functions.
484 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
485 * using the CtdlMessageFree() function.
487 struct CtdlMessage *CtdlFetchMessage(long msgnum)
489 struct cdbdata *dmsgtext;
490 struct CtdlMessage *ret = NULL;
493 CIT_UBYTE field_header;
497 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
498 if (dmsgtext == NULL) {
499 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
502 mptr = dmsgtext->ptr;
504 /* Parse the three bytes that begin EVERY message on disk.
505 * The first is always 0xFF, the on-disk magic number.
506 * The second is the anonymous/public type byte.
507 * The third is the format type byte (vari, fixed, or MIME).
511 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
515 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
516 memset(ret, 0, sizeof(struct CtdlMessage));
518 ret->cm_magic = CTDLMESSAGE_MAGIC;
519 ret->cm_anon_type = *mptr++; /* Anon type byte */
520 ret->cm_format_type = *mptr++; /* Format type byte */
523 * The rest is zero or more arbitrary fields. Load them in.
524 * We're done when we encounter either a zero-length field or
525 * have just processed the 'M' (message text) field.
528 field_length = strlen(mptr);
529 if (field_length == 0)
531 field_header = *mptr++;
532 ret->cm_fields[field_header] = mallok(field_length);
533 strcpy(ret->cm_fields[field_header], mptr);
535 while (*mptr++ != 0); /* advance to next field */
537 } while ((field_length > 0) && (field_header != 'M'));
544 * 'Destructor' for struct CtdlMessage
546 void CtdlFreeMessage(struct CtdlMessage *msg)
552 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
553 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
556 for (i = 0; i < 256; ++i)
557 if (msg->cm_fields[i] != NULL)
558 phree(msg->cm_fields[i]);
566 * Get a message off disk. (return value is the message's timestamp)
569 void output_message(char *msgid, int mode, int headers_only)
577 struct CtdlMessage *TheMessage = NULL;
581 /* buffers needed for RFC822 translation */
589 msg_num = atol(msgid);
591 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
592 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
595 /* FIX ... small security issue
596 * We need to check to make sure the requested message is actually
597 * in the current room, and set msg_ok to 1 only if it is. This
598 * functionality is currently missing because I'm in a hurry to replace
599 * broken production code with nonbroken pre-beta code. :( -- ajc
602 cprintf("%d Message %ld is not in this room.\n",
609 * Fetch the message from disk
611 TheMessage = CtdlFetchMessage(msg_num);
612 if (TheMessage == NULL) {
613 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
617 /* Are we downloading a MIME component? */
618 if (mode == MT_DOWNLOAD) {
619 if (TheMessage->cm_format_type != 4) {
620 cprintf("%d This is not a MIME message.\n",
622 } else if (CC->download_fp != NULL) {
623 cprintf("%d You already have a download open.\n",
626 /* Parse the message text component */
627 mptr = TheMessage->cm_fields['M'];
628 mime_parser(mptr, NULL, *mime_download);
629 /* If there's no file open by this time, the requested
630 * section wasn't found, so print an error
632 if (CC->download_fp == NULL) {
633 cprintf("%d Section %s not found.\n",
634 ERROR + FILE_NOT_FOUND,
638 CtdlFreeMessage(TheMessage);
642 /* now for the user-mode message reading loops */
643 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
645 if (mode == MT_CITADEL)
646 cprintf("type=%d\n", TheMessage->cm_format_type);
648 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
649 cprintf("nhdr=yes\n");
652 /* begin header processing loop for Citadel message format */
654 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
656 if (TheMessage->cm_fields['P']) {
657 cprintf("path=%s\n", TheMessage->cm_fields['P']);
659 if (TheMessage->cm_fields['I']) {
660 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
662 if (TheMessage->cm_fields['T']) {
663 cprintf("time=%s\n", TheMessage->cm_fields['T']);
665 if (TheMessage->cm_fields['A']) {
666 strcpy(buf, TheMessage->cm_fields['A']);
667 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
668 if (TheMessage->cm_anon_type == MES_ANON)
669 cprintf("from=****");
670 else if (TheMessage->cm_anon_type == MES_AN2)
671 cprintf("from=anonymous");
673 cprintf("from=%s", buf);
675 && ((TheMessage->cm_anon_type == MES_ANON)
676 || (TheMessage->cm_anon_type == MES_AN2))) {
677 cprintf(" [%s]", buf);
681 if (TheMessage->cm_fields['O']) {
682 cprintf("room=%s\n", TheMessage->cm_fields['O']);
684 if (TheMessage->cm_fields['N']) {
685 cprintf("node=%s\n", TheMessage->cm_fields['N']);
687 if (TheMessage->cm_fields['H']) {
688 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
690 if (TheMessage->cm_fields['R']) {
691 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
693 if (TheMessage->cm_fields['U']) {
694 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
698 /* begin header processing loop for RFC822 transfer format */
702 strcpy(snode, NODENAME);
703 strcpy(lnode, HUMANNODE);
704 if (mode == MT_RFC822) {
705 for (i = 0; i < 256; ++i) {
706 if (TheMessage->cm_fields[i]) {
707 mptr = TheMessage->cm_fields[i];
711 } else if (i == 'P') {
712 cprintf("Path: %s\n", mptr);
713 for (a = 0; a < strlen(mptr); ++a) {
714 if (mptr[a] == '!') {
715 strcpy(mptr, &mptr[a + 1]);
721 cprintf("Subject: %s\n", mptr);
727 cprintf("X-Citadel-Room: %s\n", mptr);
731 cprintf("To: %s\n", mptr);
734 cprintf("Date: %s", asctime(localtime(&xtime)));
740 if (mode == MT_RFC822) {
741 if (!strcasecmp(snode, NODENAME)) {
744 cprintf("Message-ID: <%s@%s>\n", mid, snode);
745 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
746 cprintf("From: %s@%s (%s)\n",
747 suser, snode, luser);
748 cprintf("Organization: %s\n", lnode);
751 /* end header processing loop ... at this point, we're in the text */
753 mptr = TheMessage->cm_fields['M'];
755 /* do some sort of MIME output */
756 if (TheMessage->cm_format_type == 4) {
757 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
758 mime_parser(mptr, NULL, *list_this_part);
760 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
762 CtdlFreeMessage(TheMessage);
769 CtdlFreeMessage(TheMessage);
773 /* signify start of msg text */
774 if (mode == MT_CITADEL)
776 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
779 /* If the format type on disk is 1 (fixed-format), then we want
780 * everything to be output completely literally ... regardless of
781 * what message transfer format is in use.
783 if (TheMessage->cm_format_type == 1) {
785 while (ch = *mptr++, ch > 0) {
788 if ((ch == 10) || (strlen(buf) > 250)) {
789 cprintf("%s\n", buf);
792 buf[strlen(buf) + 1] = 0;
793 buf[strlen(buf)] = ch;
797 cprintf("%s\n", buf);
800 /* If the message on disk is format 0 (Citadel vari-format), we
801 * output using the formatter at 80 columns. This is the final output
802 * form if the transfer format is RFC822, but if the transfer format
803 * is Citadel proprietary, it'll still work, because the indentation
804 * for new paragraphs is correct and the client will reformat the
805 * message to the reader's screen width.
807 if (TheMessage->cm_format_type == 0) {
808 memfmout(80, mptr, 0);
811 /* If the message on disk is format 4 (MIME), we've gotta hand it
812 * off to the MIME parser. The client has already been told that
813 * this message is format 1 (fixed format), so the callback function
814 * we use will display those parts as-is.
816 if (TheMessage->cm_format_type == 4) {
817 mime_parser(mptr, NULL, *fixed_output);
822 CtdlFreeMessage(TheMessage);
829 * display a message (mode 0 - Citadel proprietary)
831 void cmd_msg0(char *cmdbuf)
834 int headers_only = 0;
836 extract(msgid, cmdbuf, 0);
837 headers_only = extract_int(cmdbuf, 1);
839 output_message(msgid, MT_CITADEL, headers_only);
845 * display a message (mode 2 - RFC822)
847 void cmd_msg2(char *cmdbuf)
850 int headers_only = 0;
852 extract(msgid, cmdbuf, 0);
853 headers_only = extract_int(cmdbuf, 1);
855 output_message(msgid, MT_RFC822, headers_only);
861 * display a message (mode 3 - IGnet raw format - internal programs only)
863 void cmd_msg3(char *cmdbuf)
866 struct cdbdata *dmsgtext;
868 if (CC->internal_pgm == 0) {
869 cprintf("%d This command is for internal programs only.\n",
874 msgnum = extract_long(cmdbuf, 0);
876 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
877 if (dmsgtext == NULL) {
878 cprintf("%d Message %ld not found\n", ERROR, msgnum);
881 cprintf("%d %ld\n", BINARY_FOLLOWS, dmsgtext->len);
882 client_write(dmsgtext->ptr, dmsgtext->len);
889 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
891 void cmd_msg4(char *cmdbuf)
895 extract(msgid, cmdbuf, 0);
897 output_message(msgid, MT_MIME, 0);
901 * Open a component of a MIME message as a download file
903 void cmd_opna(char *cmdbuf)
907 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
909 extract(msgid, cmdbuf, 0);
910 extract(desired_section, cmdbuf, 1);
912 output_message(msgid, MT_DOWNLOAD, 0);
916 * Message base operation to send a message to the master file
917 * (returns new message number)
919 long send_message(char *message_in_memory,
920 /* pointer to buffer */
921 size_t message_length, /* length of buffer */
923 { /* 1 to generate an I field */
926 char *actual_message;
927 size_t actual_length;
931 /* Get a new message number */
932 newmsgid = get_new_message_number();
935 sprintf(msgidbuf, "I%ld", newmsgid);
936 actual_length = message_length + strlen(msgidbuf) + 1;
937 actual_message = mallok(actual_length);
938 memcpy(actual_message, message_in_memory, 3);
939 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
940 memcpy(&actual_message[strlen(msgidbuf) + 4],
941 &message_in_memory[3], message_length - 3);
943 actual_message = message_in_memory;
944 actual_length = message_length;
947 /* Write our little bundle of joy into the message base */
948 begin_critical_section(S_MSGMAIN);
949 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
950 actual_message, actual_length) < 0) {
951 lprintf(2, "Can't store message\n");
956 end_critical_section(S_MSGMAIN);
959 phree(actual_message);
961 /* Finally, return the pointers */
968 * this is a simple file copy routine.
970 void copy_file(char *from, char *to)
975 ffp = fopen(from, "r");
978 tfp = fopen(to, "w");
983 while (a = getc(ffp), a >= 0) {
994 * message base operation to save a message and install its pointers
996 void save_message(char *mtmp, /* file containing proper message */
997 char *rec, /* Recipient (if mail) */
998 char *force, /* if non-zero length, force a room */
999 int mailtype, /* local or remote type, see citadel.h */
1001 { /* set to 1 to generate an 'I' field */
1003 char hold_rm[ROOMNAMELEN];
1004 char actual_rm[ROOMNAMELEN];
1005 char force_room[ROOMNAMELEN];
1006 char content_type[256]; /* We have to learn this */
1008 char recipient[256];
1010 char *message_in_memory;
1012 struct stat statbuf;
1015 struct usersupp userbuf;
1017 static int seqnum = 0;
1018 int successful_local_recipients = 0;
1019 struct quickroom qtemp;
1020 struct SuppMsgInfo smi;
1022 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1023 mtmp, rec, force, mailtype, generate_id);
1025 strcpy(force_room, force);
1027 /* Strip non-printable characters out of the recipient name */
1028 strcpy(recipient, rec);
1029 for (a = 0; a < strlen(recipient); ++a)
1030 if (!isprint(recipient[a]))
1031 strcpy(&recipient[a], &recipient[a + 1]);
1033 /* Measure the message */
1034 stat(mtmp, &statbuf);
1035 templen = statbuf.st_size;
1037 /* Now read it into memory */
1038 message_in_memory = (char *) mallok(templen);
1039 if (message_in_memory == NULL) {
1040 lprintf(2, "Can't allocate memory to save message!\n");
1043 fp = fopen(mtmp, "rb");
1044 fread(message_in_memory, templen, 1, fp);
1047 /* Learn about what's inside, because it's what's inside that counts */
1048 mptr = message_in_memory;
1049 ++mptr; /* advance past 0xFF header */
1050 ++mptr; /* advance past anon flag */
1054 strcpy(content_type, "text/x-citadel-variformat");
1057 strcpy(content_type, "text/plain");
1060 strcpy(content_type, "text/plain");
1061 /* advance past header fields */
1062 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1069 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1070 safestrncpy(content_type, mptr,
1071 sizeof(content_type));
1072 lprintf(9, "%s\n", content_type);
1073 strcpy(content_type, &content_type[14]);
1074 for (a = 0; a < strlen(content_type); ++a)
1075 if ((content_type[a] == ';')
1076 || (content_type[a] == ' ')
1077 || (content_type[a] == 13)
1078 || (content_type[a] == 10))
1079 content_type[a] = 0;
1085 lprintf(9, "Content type is <%s>\n", content_type);
1087 /* Save it to disk */
1088 newmsgid = send_message(message_in_memory, templen, generate_id);
1089 phree(message_in_memory);
1093 strcpy(actual_rm, CC->quickroom.QRname);
1094 strcpy(hold_rm, "");
1096 /* If this is being done by the networker delivering a private
1097 * message, we want to BYPASS saving the sender's copy (because there
1098 * is no local sender; it would otherwise go to the Trashcan).
1100 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1101 /* If the user is a twit, move to the twit room for posting */
1103 if (CC->usersupp.axlevel == 2) {
1104 strcpy(hold_rm, actual_rm);
1105 strcpy(actual_rm, config.c_twitroom);
1107 /* ...or if this message is destined for Aide> then go there. */
1108 lprintf(9, "actual room forcing loop\n");
1109 if (strlen(force_room) > 0) {
1110 strcpy(hold_rm, actual_rm);
1111 strcpy(actual_rm, force_room);
1113 /* This call to usergoto() changes rooms if necessary. It also
1114 * causes the latest message list to be read into memory.
1116 usergoto(actual_rm, 0);
1118 /* read in the quickroom record, obtaining a lock... */
1119 lgetroom(&CC->quickroom, actual_rm);
1121 /* Fix an obscure bug */
1122 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1123 CC->quickroom.QRflags =
1124 CC->quickroom.QRflags & ~QR_MAILBOX;
1126 /* Add the message pointer to the room */
1127 CC->quickroom.QRhighest =
1128 AddMessageToRoom(&CC->quickroom, newmsgid);
1130 /* update quickroom */
1131 lputroom(&CC->quickroom);
1132 ++successful_local_recipients;
1134 /* Network mail - send a copy to the network program. */
1135 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1136 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1137 (long) getpid(), CC->cs_pid, ++seqnum);
1138 copy_file(mtmp, aaa);
1139 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1141 /* Bump this user's messages posted counter. */
1142 lgetuser(&CC->usersupp, CC->curr_user);
1143 CC->usersupp.posted = CC->usersupp.posted + 1;
1144 lputuser(&CC->usersupp);
1146 /* If this is private, local mail, make a copy in the
1147 * recipient's mailbox and bump the reference count.
1149 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1150 if (getuser(&userbuf, recipient) == 0) {
1151 MailboxName(actual_rm, &userbuf, MAILROOM);
1152 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1153 if (lgetroom(&qtemp, actual_rm) == 0) {
1155 AddMessageToRoom(&qtemp, newmsgid);
1157 ++successful_local_recipients;
1161 /* If we've posted in a room other than the current room, then we
1162 * have to now go back to the current room...
1164 if (strlen(hold_rm) > 0) {
1165 usergoto(hold_rm, 0);
1167 unlink(mtmp); /* delete the temporary file */
1169 /* Write a supplemental message info record. This doesn't have to
1170 * be a critical section because nobody else knows about this message
1173 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1174 smi.smi_msgnum = newmsgid;
1175 smi.smi_refcount = successful_local_recipients;
1176 safestrncpy(smi.smi_content_type, content_type, 64);
1177 PutSuppMsgInfo(&smi);
1182 * Generate an administrative message and post it in the Aide> room.
1184 void aide_message(char *text)
1188 fp = fopen(CC->temp, "wb");
1189 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1190 fprintf(fp, "Psysop%c", 0);
1191 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1192 fprintf(fp, "ACitadel%c", 0);
1193 fprintf(fp, "OAide%c", 0);
1194 fprintf(fp, "N%s%c", NODENAME, 0);
1195 fprintf(fp, "M%s\n%c", text, 0);
1197 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1198 syslog(LOG_NOTICE, text);
1201 * Build a binary message to be saved on disk.
1202 */ void make_message(
1203 char *filename, /* temporary file name */
1204 struct usersupp *author, /* author's usersupp structure */
1205 char *recipient, /* NULL if it's not mail */
1206 char *room, /* room where it's going */
1207 int type, /* see MES_ types in header file */
1208 int net_type, /* see MES_ types in header file */
1209 int format_type, /* local or remote (see citadel.h) */
1211 { /* who we're masquerading as */
1219 /* Don't confuse the poor folks if it's not routed mail. */
1220 strcpy(dest_node, "");
1223 /* If net_type is MES_BINARY, split out the destination node. */
1224 if (net_type == MES_BINARY) {
1225 strcpy(dest_node, NODENAME);
1226 for (a = 0; a < strlen(recipient); ++a) {
1227 if (recipient[a] == '@') {
1229 strcpy(dest_node, &recipient[a + 1]);
1233 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1234 strcpy(dest_node, "internet");
1236 while (isspace(recipient[strlen(recipient) - 1]))
1237 recipient[strlen(recipient) - 1] = 0;
1240 fp = fopen(filename, "w");
1242 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1243 putc(format_type, fp); /* Formatted or unformatted */
1244 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1245 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1247 fprintf(fp, "A%s%c", fake_name, 0);
1249 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1251 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1252 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1254 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1257 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1258 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1260 if (recipient[0] != 0)
1261 fprintf(fp, "R%s%c", recipient, 0);
1262 if (dest_node[0] != 0)
1263 fprintf(fp, "D%s%c", dest_node, 0);
1267 while (client_gets(buf), strcmp(buf, "000")) {
1268 fprintf(fp, "%s\n", buf);
1279 * message entry - mode 0 (normal) <bc>
1281 void cmd_ent0(char *entargs)
1284 char recipient[256];
1286 int format_type = 0;
1287 char newusername[256]; /* <bc> */
1292 struct usersupp tempUS;
1295 post = extract_int(entargs, 0);
1296 extract(recipient, entargs, 1);
1297 anon_flag = extract_int(entargs, 2);
1298 format_type = extract_int(entargs, 3);
1300 /* first check to make sure the request is valid. */
1302 if (!(CC->logged_in)) {
1303 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1306 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1307 cprintf("%d Need to be validated to enter ",
1308 ERROR + HIGHER_ACCESS_REQUIRED);
1309 cprintf("(except in %s> to sysop)\n", MAILROOM);
1312 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1313 cprintf("%d Need net privileges to enter here.\n",
1314 ERROR + HIGHER_ACCESS_REQUIRED);
1317 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1318 cprintf("%d Sorry, this is a read-only room.\n",
1319 ERROR + HIGHER_ACCESS_REQUIRED);
1325 if (post == 2) { /* <bc> */
1326 if (CC->usersupp.axlevel < 6) {
1327 cprintf("%d You don't have permission to do an aide post.\n",
1328 ERROR + HIGHER_ACCESS_REQUIRED);
1331 extract(newusername, entargs, 4);
1332 memset(CC->fake_postname, 0, 32);
1333 strcpy(CC->fake_postname, newusername);
1334 cprintf("%d Ok\n", OK);
1337 CC->cs_flags |= CS_POSTING;
1340 if (CC->quickroom.QRflags & QR_MAILBOX) {
1341 if (CC->usersupp.axlevel >= 2) {
1342 strcpy(buf, recipient);
1344 strcpy(buf, "sysop");
1345 lprintf(9, "calling alias()\n");
1346 e = alias(buf); /* alias and mail type */
1347 lprintf(9, "alias() returned %d\n", e);
1348 if ((buf[0] == 0) || (e == MES_ERROR)) {
1349 cprintf("%d Unknown address - cannot send message.\n",
1350 ERROR + NO_SUCH_USER);
1353 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1354 cprintf("%d Net privileges required for network mail.\n",
1355 ERROR + HIGHER_ACCESS_REQUIRED);
1358 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1359 && ((CC->usersupp.flags & US_INTERNET) == 0)
1360 && (!CC->internal_pgm)) {
1361 cprintf("%d You don't have access to Internet mail.\n",
1362 ERROR + HIGHER_ACCESS_REQUIRED);
1365 if (!strcasecmp(buf, "sysop")) {
1370 goto SKFALL; /* don't search local file */
1371 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1372 cprintf("%d Can't send mail to yourself!\n",
1373 ERROR + NO_SUCH_USER);
1376 /* Check to make sure the user exists; also get the correct
1377 * upper/lower casing of the name.
1379 a = getuser(&tempUS, buf);
1381 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1384 strcpy(buf, tempUS.fullname);
1386 SKFALL:b = MES_NORMAL;
1387 if (CC->quickroom.QRflags & QR_ANONONLY)
1389 if (CC->quickroom.QRflags & QR_ANONOPT) {
1393 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1396 /* If we're only checking the validity of the request, return
1397 * success without creating the message.
1400 cprintf("%d %s\n", OK, buf);
1403 cprintf("%d send message\n", SEND_LISTING);
1404 if (CC->fake_postname[0])
1405 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1406 else if (CC->fake_username[0])
1407 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1409 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1410 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1411 CC->fake_postname[0] = '\0';
1418 * message entry - mode 3 (raw)
1420 void cmd_ent3(char *entargs)
1426 struct usersupp tempUS;
1431 if (CC->internal_pgm == 0) {
1432 cprintf("%d This command is for internal programs only.\n",
1436 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1437 for (a = 0; a < strlen(recp); ++a)
1438 if (!isprint(recp[a]))
1439 strcpy(&recp[a], &recp[a + 1]);
1440 while (isspace(recp[0]))
1441 strcpy(recp, &recp[1]);
1442 while (isspace(recp[strlen(recp) - 1]))
1443 recp[strlen(recp) - 1] = 0;
1445 /* If we're in Mail, check the recipient */
1446 if (strlen(recp) > 0) {
1447 e = alias(recp); /* alias and mail type */
1448 if ((recp[0] == 0) || (e == MES_ERROR)) {
1449 cprintf("%d Unknown address - cannot send message.\n",
1450 ERROR + NO_SUCH_USER);
1453 if (e == MES_LOCAL) {
1454 a = getuser(&tempUS, recp);
1456 cprintf("%d No such user.\n",
1457 ERROR + NO_SUCH_USER);
1462 /* At this point, message has been approved. */
1463 if (extract_int(entargs, 0) == 0) {
1464 cprintf("%d OK to send\n", OK);
1467 /* open a temp file to hold the message */
1468 fp = fopen(CC->temp, "wb");
1470 cprintf("%d Cannot open %s: %s\n",
1471 ERROR + INTERNAL_ERROR,
1472 CC->temp, strerror(errno));
1475 msglen = extract_long(entargs, 2);
1476 cprintf("%d %ld\n", SEND_BINARY, msglen);
1477 while (msglen > 0L) {
1478 bloklen = ((msglen >= 255L) ? 255 : msglen);
1479 client_read(buf, (int) bloklen);
1480 fwrite(buf, (int) bloklen, 1, fp);
1481 msglen = msglen - bloklen;
1485 save_message(CC->temp, recp, "", e, 0);
1490 * API function to delete messages which match a set of criteria
1491 * (returns the actual number of messages deleted)
1492 * FIX ... still need to implement delete by content type
1494 int CtdlDeleteMessages(char *room_name, /* which room */
1495 long dmsgnum, /* or "0" for any */
1496 char *content_type /* or NULL for any */
1500 struct quickroom qrbuf;
1501 struct cdbdata *cdbfr;
1502 long *msglist = NULL;
1505 int num_deleted = 0;
1507 struct SuppMsgInfo smi;
1509 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1510 room_name, dmsgnum, content_type);
1512 /* get room record, obtaining a lock... */
1513 if (lgetroom(&qrbuf, room_name) != 0) {
1514 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1516 return (0); /* room not found */
1518 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1520 lprintf(9, "doing mallok/memcpy loop\n");
1521 if (cdbfr != NULL) {
1522 msglist = mallok(cdbfr->len);
1523 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1524 num_msgs = cdbfr->len / sizeof(long);
1528 for (i = 0; i < num_msgs; ++i) {
1531 /* Set/clear a bit for each criterion */
1533 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1534 delete_this |= 0x01;
1536 if (content_type == NULL) {
1537 delete_this |= 0x02;
1539 GetSuppMsgInfo(&smi, msglist[i]);
1540 if (!strcasecmp(smi.smi_content_type,
1542 delete_this |= 0x02;
1546 /* Delete message only if all bits are set */
1547 if (delete_this == 0x03) {
1548 AdjRefCount(msglist[i], -1);
1554 num_msgs = sort_msglist(msglist, num_msgs);
1555 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1556 msglist, (num_msgs * sizeof(long)));
1558 qrbuf.QRhighest = msglist[num_msgs - 1];
1562 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1563 return (num_deleted);
1569 * Delete message from current room
1571 void cmd_dele(char *delstr)
1576 getuser(&CC->usersupp, CC->curr_user);
1577 if ((CC->usersupp.axlevel < 6)
1578 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1579 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1580 cprintf("%d Higher access required.\n",
1581 ERROR + HIGHER_ACCESS_REQUIRED);
1584 delnum = extract_long(delstr, 0);
1586 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1589 cprintf("%d %d message%s deleted.\n", OK,
1590 num_deleted, ((num_deleted != 1) ? "s" : ""));
1592 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1598 * move a message to another room
1600 void cmd_move(char *args)
1604 struct quickroom qtemp;
1607 num = extract_long(args, 0);
1608 extract(targ, args, 1);
1610 getuser(&CC->usersupp, CC->curr_user);
1611 if ((CC->usersupp.axlevel < 6)
1612 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1613 cprintf("%d Higher access required.\n",
1614 ERROR + HIGHER_ACCESS_REQUIRED);
1617 if (getroom(&qtemp, targ) != 0) {
1618 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1621 /* Bump the reference count, otherwise the message will be deleted
1622 * from disk when we remove it from the source room.
1624 AdjRefCount(num, 1);
1626 /* yank the message out of the current room... */
1627 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1630 /* put the message into the target room */
1631 lgetroom(&qtemp, targ);
1632 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1634 cprintf("%d Message moved.\n", OK);
1636 AdjRefCount(num, (-1)); /* oops */
1637 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1644 * GetSuppMsgInfo() - Get the supplementary record for a message
1646 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1649 struct cdbdata *cdbsmi;
1652 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1653 smibuf->smi_msgnum = msgnum;
1654 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1656 /* Use the negative of the message number for its supp record index */
1657 TheIndex = (0L - msgnum);
1659 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1660 if (cdbsmi == NULL) {
1661 return; /* record not found; go with defaults */
1663 memcpy(smibuf, cdbsmi->ptr,
1664 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1665 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1672 * PutSuppMsgInfo() - (re)write supplementary record for a message
1674 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1678 /* Use the negative of the message number for its supp record index */
1679 TheIndex = (0L - smibuf->smi_msgnum);
1681 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1682 smibuf->smi_msgnum, smibuf->smi_refcount);
1684 cdb_store(CDB_MSGMAIN,
1685 &TheIndex, sizeof(long),
1686 smibuf, sizeof(struct SuppMsgInfo));
1690 * AdjRefCount - change the reference count for a message;
1691 * delete the message if it reaches zero
1692 */ void AdjRefCount(long msgnum, int incr)
1695 struct SuppMsgInfo smi;
1698 /* This is a *tight* critical section; please keep it that way, as
1699 * it may get called while nested in other critical sections.
1700 * Complicating this any further will surely cause deadlock!
1702 begin_critical_section(S_SUPPMSGMAIN);
1703 GetSuppMsgInfo(&smi, msgnum);
1704 smi.smi_refcount += incr;
1705 PutSuppMsgInfo(&smi);
1706 end_critical_section(S_SUPPMSGMAIN);
1708 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1709 msgnum, smi.smi_refcount);
1711 /* If the reference count is now zero, delete the message
1712 * (and its supplementary record as well).
1714 if (smi.smi_refcount == 0) {
1715 lprintf(9, "Deleting message <%ld>\n", msgnum);
1717 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1718 delnum = (0L - msgnum);
1719 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1724 * Write a generic object to this room
1726 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1727 char *content_type, /* MIME type of this object */
1728 char *tempfilename, /* Where to fetch it from */
1729 int is_mailbox, /* Private mailbox room? */
1730 int is_binary, /* Is encoding necessary? */
1731 int is_unique /* Del others of this type? */
1736 char filename[PATH_MAX];
1739 struct quickroom qrbuf;
1740 char roomname[ROOMNAMELEN];
1743 MailboxName(roomname, &CC->usersupp, req_room);
1745 safestrncpy(roomname, req_room, sizeof(roomname));
1747 strcpy(filename, tmpnam(NULL));
1748 fp = fopen(filename, "w");
1752 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1753 fprintf(fp, "T%ld%c", time(NULL), 0);
1754 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1755 fprintf(fp, "O%s%c", roomname, 0);
1756 fprintf(fp, "N%s%c", config.c_nodename, 0);
1757 fprintf(fp, "MContent-type: %s\n", content_type);
1759 tempfp = fopen(tempfilename, "r");
1760 if (tempfp == NULL) {
1765 if (is_binary == 0) {
1766 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1767 while (ch = getc(tempfp), ch > 0)
1773 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1776 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1777 tempfilename, filename);
1781 /* Create the requested room if we have to. */
1782 if (getroom(&qrbuf, roomname) != 0) {
1783 create_room(roomname, 4, "", 0);
1785 /* If the caller specified this object as unique, delete all
1786 * other objects of this type that are currently in the room.
1789 lprintf(9, "Deleted %d other msgs of this type\n",
1790 CtdlDeleteMessages(roomname, 0L, content_type));
1792 /* Now write the data */
1793 save_message(filename, "", roomname, MES_LOCAL, 1);