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];
247 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
248 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
249 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
250 && (CC->usersupp.flags & US_LASTOLD))
251 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
252 || ((mode == MSGS_FIRST) && (a < ref))
253 || ((mode == MSGS_GT) && (thismsg > ref))
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)
444 if (!strcasecmp(cbtype, "multipart/alternative")) {
445 strcpy(ma->prefix, partnum);
446 strcat(ma->prefix, ".");
452 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
454 && (ma->did_print == 1) ) {
455 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
461 if (!strcasecmp(cbtype, "text/plain")) {
462 client_write(content, length);
464 else if (!strcasecmp(cbtype, "text/html")) {
465 ptr = html_to_ascii(content, 80, 0);
466 client_write(ptr, strlen(ptr));
469 else if (strncasecmp(cbtype, "multipart/", 10)) {
470 cprintf("Part %s: %s (%s) (%d bytes)\n",
471 partnum, filename, cbtype, length);
477 * Callback function for mime parser that opens a section for downloading
479 void mime_download(char *name, char *filename, char *partnum, char *disp,
480 void *content, char *cbtype, size_t length)
483 /* Silently go away if there's already a download open... */
484 if (CC->download_fp != NULL)
487 /* ...or if this is not the desired section */
488 if (strcasecmp(desired_section, partnum))
491 CC->download_fp = tmpfile();
492 if (CC->download_fp == NULL)
495 fwrite(content, length, 1, CC->download_fp);
496 fflush(CC->download_fp);
497 rewind(CC->download_fp);
499 OpenCmdResult(filename, cbtype);
505 * Load a message from disk into memory.
506 * This is used by output_message() and other fetch functions.
508 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
509 * using the CtdlMessageFree() function.
511 struct CtdlMessage *CtdlFetchMessage(long msgnum)
513 struct cdbdata *dmsgtext;
514 struct CtdlMessage *ret = NULL;
517 CIT_UBYTE field_header;
521 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
522 if (dmsgtext == NULL) {
523 lprintf(9, "CtdlFetchMessage(%ld) failed.\n");
526 mptr = dmsgtext->ptr;
528 /* Parse the three bytes that begin EVERY message on disk.
529 * The first is always 0xFF, the on-disk magic number.
530 * The second is the anonymous/public type byte.
531 * The third is the format type byte (vari, fixed, or MIME).
535 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
539 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
540 memset(ret, 0, sizeof(struct CtdlMessage));
542 ret->cm_magic = CTDLMESSAGE_MAGIC;
543 ret->cm_anon_type = *mptr++; /* Anon type byte */
544 ret->cm_format_type = *mptr++; /* Format type byte */
547 * The rest is zero or more arbitrary fields. Load them in.
548 * We're done when we encounter either a zero-length field or
549 * have just processed the 'M' (message text) field.
552 field_length = strlen(mptr);
553 if (field_length == 0)
555 field_header = *mptr++;
556 ret->cm_fields[field_header] = mallok(field_length);
557 strcpy(ret->cm_fields[field_header], mptr);
559 while (*mptr++ != 0); /* advance to next field */
561 } while ((field_length > 0) && (field_header != 'M'));
568 * 'Destructor' for struct CtdlMessage
570 void CtdlFreeMessage(struct CtdlMessage *msg)
576 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
577 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
580 for (i = 0; i < 256; ++i)
581 if (msg->cm_fields[i] != NULL)
582 phree(msg->cm_fields[i]);
590 * Get a message off disk. (return value is the message's timestamp)
593 void output_message(char *msgid, int mode, int headers_only)
601 struct CtdlMessage *TheMessage = NULL;
605 /* buffers needed for RFC822 translation */
613 msg_num = atol(msgid);
615 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
616 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
620 /* FIX ... small security issue
621 * We need to check to make sure the requested message is actually
622 * in the current room, and set msg_ok to 1 only if it is. This
623 * functionality is currently missing because I'm in a hurry to replace
624 * broken production code with nonbroken pre-beta code. :( -- ajc
627 cprintf("%d Message %ld is not in this room.\n",
634 * Fetch the message from disk
636 TheMessage = CtdlFetchMessage(msg_num);
637 if (TheMessage == NULL) {
638 cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num);
642 /* Are we downloading a MIME component? */
643 if (mode == MT_DOWNLOAD) {
644 if (TheMessage->cm_format_type != 4) {
645 cprintf("%d This is not a MIME message.\n",
647 } else if (CC->download_fp != NULL) {
648 cprintf("%d You already have a download open.\n",
651 /* Parse the message text component */
652 mptr = TheMessage->cm_fields['M'];
653 mime_parser(mptr, NULL, *mime_download);
654 /* If there's no file open by this time, the requested
655 * section wasn't found, so print an error
657 if (CC->download_fp == NULL) {
658 cprintf("%d Section %s not found.\n",
659 ERROR + FILE_NOT_FOUND,
663 CtdlFreeMessage(TheMessage);
667 /* now for the user-mode message reading loops */
668 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
670 /* Tell the client which format type we're using. If this is a
671 * MIME message, *lie* about it and tell the user it's fixed-format.
673 if (mode == MT_CITADEL) {
674 if (TheMessage->cm_format_type == 4)
677 cprintf("type=%d\n", TheMessage->cm_format_type);
680 /* nhdr=yes means that we're only displaying headers, no body */
681 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
682 cprintf("nhdr=yes\n");
685 /* begin header processing loop for Citadel message format */
687 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
689 if (TheMessage->cm_fields['P']) {
690 cprintf("path=%s\n", TheMessage->cm_fields['P']);
692 if (TheMessage->cm_fields['I']) {
693 cprintf("msgn=%s\n", TheMessage->cm_fields['I']);
695 if (TheMessage->cm_fields['T']) {
696 cprintf("time=%s\n", TheMessage->cm_fields['T']);
698 if (TheMessage->cm_fields['A']) {
699 strcpy(buf, TheMessage->cm_fields['A']);
700 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
701 if (TheMessage->cm_anon_type == MES_ANON)
702 cprintf("from=****");
703 else if (TheMessage->cm_anon_type == MES_AN2)
704 cprintf("from=anonymous");
706 cprintf("from=%s", buf);
708 && ((TheMessage->cm_anon_type == MES_ANON)
709 || (TheMessage->cm_anon_type == MES_AN2))) {
710 cprintf(" [%s]", buf);
714 if (TheMessage->cm_fields['O']) {
715 cprintf("room=%s\n", TheMessage->cm_fields['O']);
717 if (TheMessage->cm_fields['N']) {
718 cprintf("node=%s\n", TheMessage->cm_fields['N']);
720 if (TheMessage->cm_fields['H']) {
721 cprintf("hnod=%s\n", TheMessage->cm_fields['H']);
723 if (TheMessage->cm_fields['R']) {
724 cprintf("rcpt=%s\n", TheMessage->cm_fields['R']);
726 if (TheMessage->cm_fields['U']) {
727 cprintf("subj=%s\n", TheMessage->cm_fields['U']);
731 /* begin header processing loop for RFC822 transfer format */
735 strcpy(snode, NODENAME);
736 strcpy(lnode, HUMANNODE);
737 if (mode == MT_RFC822) {
738 for (i = 0; i < 256; ++i) {
739 if (TheMessage->cm_fields[i]) {
740 mptr = TheMessage->cm_fields[i];
744 } else if (i == 'P') {
745 cprintf("Path: %s\n", mptr);
746 for (a = 0; a < strlen(mptr); ++a) {
747 if (mptr[a] == '!') {
748 strcpy(mptr, &mptr[a + 1]);
754 cprintf("Subject: %s\n", mptr);
760 cprintf("X-Citadel-Room: %s\n", mptr);
764 cprintf("To: %s\n", mptr);
767 cprintf("Date: %s", asctime(localtime(&xtime)));
773 if (mode == MT_RFC822) {
774 if (!strcasecmp(snode, NODENAME)) {
777 cprintf("Message-ID: <%s@%s>\n", mid, snode);
778 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
779 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
780 cprintf("Organization: %s\n", lnode);
783 /* end header processing loop ... at this point, we're in the text */
785 mptr = TheMessage->cm_fields['M'];
787 /* Tell the client about the MIME parts in this message */
788 if (TheMessage->cm_format_type == 4) { /* legacy textual dump */
789 if (mode == MT_CITADEL) {
790 mime_parser(mptr, NULL, *list_this_part);
792 else if (mode == MT_MIME) { /* list parts only */
793 mime_parser(mptr, NULL, *list_this_part);
795 CtdlFreeMessage(TheMessage);
802 CtdlFreeMessage(TheMessage);
806 /* signify start of msg text */
807 if (mode == MT_CITADEL)
809 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4))
812 /* If the format type on disk is 1 (fixed-format), then we want
813 * everything to be output completely literally ... regardless of
814 * what message transfer format is in use.
816 if (TheMessage->cm_format_type == 1) {
818 while (ch = *mptr++, ch > 0) {
821 if ((ch == 10) || (strlen(buf) > 250)) {
822 cprintf("%s\n", buf);
825 buf[strlen(buf) + 1] = 0;
826 buf[strlen(buf)] = ch;
830 cprintf("%s\n", buf);
833 /* If the message on disk is format 0 (Citadel vari-format), we
834 * output using the formatter at 80 columns. This is the final output
835 * form if the transfer format is RFC822, but if the transfer format
836 * is Citadel proprietary, it'll still work, because the indentation
837 * for new paragraphs is correct and the client will reformat the
838 * message to the reader's screen width.
840 if (TheMessage->cm_format_type == 0) {
841 memfmout(80, mptr, 0);
844 /* If the message on disk is format 4 (MIME), we've gotta hand it
845 * off to the MIME parser. The client has already been told that
846 * this message is format 1 (fixed format), so the callback function
847 * we use will display those parts as-is.
849 if (TheMessage->cm_format_type == 4) {
850 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
851 memset(ma, 0, sizeof(struct ma_info));
852 mime_parser(mptr, NULL, *fixed_output);
857 CtdlFreeMessage(TheMessage);
864 * display a message (mode 0 - Citadel proprietary)
866 void cmd_msg0(char *cmdbuf)
869 int headers_only = 0;
871 extract(msgid, cmdbuf, 0);
872 headers_only = extract_int(cmdbuf, 1);
874 output_message(msgid, MT_CITADEL, headers_only);
880 * display a message (mode 2 - RFC822)
882 void cmd_msg2(char *cmdbuf)
885 int headers_only = 0;
887 extract(msgid, cmdbuf, 0);
888 headers_only = extract_int(cmdbuf, 1);
890 output_message(msgid, MT_RFC822, headers_only);
896 * display a message (mode 3 - IGnet raw format - internal programs only)
898 void cmd_msg3(char *cmdbuf)
901 struct CtdlMessage *msg;
902 struct sermsgret smr;
904 if (CC->internal_pgm == 0) {
905 cprintf("%d This command is for internal programs only.\n",
910 msgnum = extract_long(cmdbuf, 0);
911 msg = CtdlFetchMessage(msgnum);
913 cprintf("%d Message %ld not found.\n",
918 serialize_message(&smr, msg);
919 CtdlFreeMessage(msg);
922 cprintf("%d Unable to serialize message\n",
923 ERROR+INTERNAL_ERROR);
927 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
928 client_write(smr.ser, smr.len);
935 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
937 void cmd_msg4(char *cmdbuf)
941 extract(msgid, cmdbuf, 0);
943 output_message(msgid, MT_MIME, 0);
947 * Open a component of a MIME message as a download file
949 void cmd_opna(char *cmdbuf)
953 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
955 extract(msgid, cmdbuf, 0);
956 extract(desired_section, cmdbuf, 1);
958 output_message(msgid, MT_DOWNLOAD, 0);
962 * Message base operation to send a message to the master file
963 * (returns new message number)
965 long send_message(char *message_in_memory,
966 /* pointer to buffer */
967 size_t message_length, /* length of buffer */
969 { /* 1 to generate an I field */
972 char *actual_message;
973 size_t actual_length;
977 /* Get a new message number */
978 newmsgid = get_new_message_number();
981 sprintf(msgidbuf, "I%ld", newmsgid);
982 actual_length = message_length + strlen(msgidbuf) + 1;
983 actual_message = mallok(actual_length);
984 memcpy(actual_message, message_in_memory, 3);
985 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
986 memcpy(&actual_message[strlen(msgidbuf) + 4],
987 &message_in_memory[3], message_length - 3);
989 actual_message = message_in_memory;
990 actual_length = message_length;
993 /* Write our little bundle of joy into the message base */
994 begin_critical_section(S_MSGMAIN);
995 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
996 actual_message, actual_length) < 0) {
997 lprintf(2, "Can't store message\n");
1002 end_critical_section(S_MSGMAIN);
1005 phree(actual_message);
1007 /* Finally, return the pointers */
1014 * this is a simple file copy routine.
1016 void copy_file(char *from, char *to)
1021 ffp = fopen(from, "r");
1024 tfp = fopen(to, "w");
1029 while (a = getc(ffp), a >= 0) {
1039 * Serialize a struct CtdlMessage into the format used on disk and network.
1041 * This function returns a "struct sermsgret" (defined in msgbase.h) which
1042 * contains the length of the serialized message and a pointer to the
1043 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1045 void serialize_message(struct sermsgret *ret, /* return values */
1046 struct CtdlMessage *msg) /* unserialized msg */
1050 static char *forder = FORDER;
1052 lprintf(9, "serialize_message() called\n");
1054 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1055 lprintf(3, "serialize_message() -- self-check failed\n");
1060 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1061 ret->len = ret->len +
1062 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1064 lprintf(9, "calling malloc\n");
1065 ret->ser = mallok(ret->len);
1066 if (ret->ser == NULL) {
1072 ret->ser[1] = msg->cm_anon_type;
1073 ret->ser[2] = msg->cm_format_type;
1075 lprintf(9, "stuff\n");
1077 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1078 ret->ser[wlen++] = (char)forder[i];
1079 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1080 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1082 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1095 * message base operation to save a message and install its pointers
1097 void save_message(char *mtmp, /* file containing proper message */
1098 char *rec, /* Recipient (if mail) */
1099 char *force, /* if non-zero length, force a room */
1100 int mailtype, /* local or remote type, see citadel.h */
1102 { /* set to 1 to generate an 'I' field */
1104 char hold_rm[ROOMNAMELEN];
1105 char actual_rm[ROOMNAMELEN];
1106 char force_room[ROOMNAMELEN];
1107 char content_type[256]; /* We have to learn this */
1109 char recipient[256];
1111 char *message_in_memory;
1113 struct stat statbuf;
1116 struct usersupp userbuf;
1118 static int seqnum = 0;
1119 int successful_local_recipients = 0;
1120 struct quickroom qtemp;
1121 struct SuppMsgInfo smi;
1123 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1124 mtmp, rec, force, mailtype, generate_id);
1126 strcpy(force_room, force);
1128 /* Strip non-printable characters out of the recipient name */
1129 strcpy(recipient, rec);
1130 for (a = 0; a < strlen(recipient); ++a)
1131 if (!isprint(recipient[a]))
1132 strcpy(&recipient[a], &recipient[a + 1]);
1134 /* Measure the message */
1135 stat(mtmp, &statbuf);
1136 templen = statbuf.st_size;
1138 /* Now read it into memory */
1139 message_in_memory = (char *) mallok(templen);
1140 if (message_in_memory == NULL) {
1141 lprintf(2, "Can't allocate memory to save message!\n");
1144 fp = fopen(mtmp, "rb");
1145 fread(message_in_memory, templen, 1, fp);
1148 /* Learn about what's inside, because it's what's inside that counts */
1149 mptr = message_in_memory;
1150 ++mptr; /* advance past 0xFF header */
1151 ++mptr; /* advance past anon flag */
1155 strcpy(content_type, "text/x-citadel-variformat");
1158 strcpy(content_type, "text/plain");
1161 strcpy(content_type, "text/plain");
1162 /* advance past header fields */
1163 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1170 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1171 safestrncpy(content_type, mptr,
1172 sizeof(content_type));
1173 strcpy(content_type, &content_type[14]);
1174 for (a = 0; a < strlen(content_type); ++a)
1175 if ((content_type[a] == ';')
1176 || (content_type[a] == ' ')
1177 || (content_type[a] == 13)
1178 || (content_type[a] == 10))
1179 content_type[a] = 0;
1186 /* Save it to disk */
1187 newmsgid = send_message(message_in_memory, templen, generate_id);
1188 phree(message_in_memory);
1192 strcpy(actual_rm, CC->quickroom.QRname);
1193 strcpy(hold_rm, "");
1195 /* If this is being done by the networker delivering a private
1196 * message, we want to BYPASS saving the sender's copy (because there
1197 * is no local sender; it would otherwise go to the Trashcan).
1199 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1200 /* If the user is a twit, move to the twit room for posting */
1202 if (CC->usersupp.axlevel == 2) {
1203 strcpy(hold_rm, actual_rm);
1204 strcpy(actual_rm, config.c_twitroom);
1206 /* ...or if this message is destined for Aide> then go there. */
1207 if (strlen(force_room) > 0) {
1208 strcpy(hold_rm, actual_rm);
1209 strcpy(actual_rm, force_room);
1211 /* This call to usergoto() changes rooms if necessary. It also
1212 * causes the latest message list to be read into memory.
1214 usergoto(actual_rm, 0);
1216 /* read in the quickroom record, obtaining a lock... */
1217 lgetroom(&CC->quickroom, actual_rm);
1219 /* Fix an obscure bug */
1220 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1221 CC->quickroom.QRflags =
1222 CC->quickroom.QRflags & ~QR_MAILBOX;
1224 /* Add the message pointer to the room */
1225 CC->quickroom.QRhighest =
1226 AddMessageToRoom(&CC->quickroom, newmsgid);
1228 /* update quickroom */
1229 lputroom(&CC->quickroom);
1230 ++successful_local_recipients;
1232 /* Network mail - send a copy to the network program. */
1233 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1234 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1235 (long) getpid(), CC->cs_pid, ++seqnum);
1236 copy_file(mtmp, aaa);
1237 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1239 /* Bump this user's messages posted counter. */
1240 lgetuser(&CC->usersupp, CC->curr_user);
1241 CC->usersupp.posted = CC->usersupp.posted + 1;
1242 lputuser(&CC->usersupp);
1244 /* If this is private, local mail, make a copy in the
1245 * recipient's mailbox and bump the reference count.
1247 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1248 if (getuser(&userbuf, recipient) == 0) {
1249 MailboxName(actual_rm, &userbuf, MAILROOM);
1250 if (lgetroom(&qtemp, actual_rm) == 0) {
1252 AddMessageToRoom(&qtemp, newmsgid);
1254 ++successful_local_recipients;
1258 /* If we've posted in a room other than the current room, then we
1259 * have to now go back to the current room...
1261 if (strlen(hold_rm) > 0) {
1262 usergoto(hold_rm, 0);
1264 unlink(mtmp); /* delete the temporary file */
1266 /* Write a supplemental message info record. This doesn't have to
1267 * be a critical section because nobody else knows about this message
1270 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1271 smi.smi_msgnum = newmsgid;
1272 smi.smi_refcount = successful_local_recipients;
1273 safestrncpy(smi.smi_content_type, content_type, 64);
1274 PutSuppMsgInfo(&smi);
1279 * Generate an administrative message and post it in the Aide> room.
1281 void aide_message(char *text)
1285 fp = fopen(CC->temp, "wb");
1286 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1287 fprintf(fp, "Psysop%c", 0);
1288 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1289 fprintf(fp, "ACitadel%c", 0);
1290 fprintf(fp, "OAide%c", 0);
1291 fprintf(fp, "N%s%c", NODENAME, 0);
1292 fprintf(fp, "M%s\n%c", text, 0);
1294 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1295 syslog(LOG_NOTICE, text);
1300 * Build a binary message to be saved on disk.
1304 char *filename, /* temporary file name */
1305 struct usersupp *author, /* author's usersupp structure */
1306 char *recipient, /* NULL if it's not mail */
1307 char *room, /* room where it's going */
1308 int type, /* see MES_ types in header file */
1309 int net_type, /* see MES_ types in header file */
1310 int format_type, /* local or remote (see citadel.h) */
1311 char *fake_name) /* who we're masquerading as */
1321 /* Don't confuse the poor folks if it's not routed mail. */
1322 strcpy(dest_node, "");
1324 /* If net_type is MES_BINARY, split out the destination node. */
1325 if (net_type == MES_BINARY) {
1326 strcpy(dest_node, NODENAME);
1327 for (a = 0; a < strlen(recipient); ++a) {
1328 if (recipient[a] == '@') {
1330 strcpy(dest_node, &recipient[a + 1]);
1335 /* if net_type is MES_INTERNET, set the dest node to 'internet' */ if (net_type == MES_INTERNET) {
1336 strcpy(dest_node, "internet");
1339 while (isspace(recipient[strlen(recipient) - 1]))
1340 recipient[strlen(recipient) - 1] = 0;
1343 fp = fopen(filename, "w");
1345 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1346 putc(format_type, fp); /* Formatted or unformatted */
1347 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1348 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1351 fprintf(fp, "A%s%c", fake_name, 0);
1353 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1355 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1356 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1358 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1361 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1362 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1364 if (recipient[0] != 0)
1365 fprintf(fp, "R%s%c", recipient, 0);
1366 if (dest_node[0] != 0)
1367 fprintf(fp, "D%s%c", dest_node, 0);
1371 while (client_gets(buf), strcmp(buf, "000")) {
1372 if (msglen < config.c_maxmsglen)
1373 fprintf(fp, "%s\n", buf);
1375 lprintf(7, "Message exceeded %d byte limit\n",
1376 config.c_maxmsglen);
1377 msglen = msglen + strlen(buf) + 1;
1389 * message entry - mode 0 (normal)
1391 void cmd_ent0(char *entargs)
1394 char recipient[256];
1396 int format_type = 0;
1397 char newusername[256];
1402 struct usersupp tempUS;
1405 post = extract_int(entargs, 0);
1406 extract(recipient, entargs, 1);
1407 anon_flag = extract_int(entargs, 2);
1408 format_type = extract_int(entargs, 3);
1410 /* first check to make sure the request is valid. */
1412 if (!(CC->logged_in)) {
1413 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1416 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1417 cprintf("%d Need to be validated to enter ",
1418 ERROR + HIGHER_ACCESS_REQUIRED);
1419 cprintf("(except in %s> to sysop)\n", MAILROOM);
1422 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1423 cprintf("%d Need net privileges to enter here.\n",
1424 ERROR + HIGHER_ACCESS_REQUIRED);
1427 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1428 cprintf("%d Sorry, this is a read-only room.\n",
1429 ERROR + HIGHER_ACCESS_REQUIRED);
1436 if (CC->usersupp.axlevel < 6) {
1437 cprintf("%d You don't have permission to masquerade.\n",
1438 ERROR + HIGHER_ACCESS_REQUIRED);
1441 extract(newusername, entargs, 4);
1442 memset(CC->fake_postname, 0, 32);
1443 strcpy(CC->fake_postname, newusername);
1444 cprintf("%d Ok\n", OK);
1447 CC->cs_flags |= CS_POSTING;
1450 if (CC->quickroom.QRflags & QR_MAILBOX) {
1451 if (CC->usersupp.axlevel >= 2) {
1452 strcpy(buf, recipient);
1454 strcpy(buf, "sysop");
1455 e = alias(buf); /* alias and mail type */
1456 if ((buf[0] == 0) || (e == MES_ERROR)) {
1457 cprintf("%d Unknown address - cannot send message.\n",
1458 ERROR + NO_SUCH_USER);
1461 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1462 cprintf("%d Net privileges required for network mail.\n",
1463 ERROR + HIGHER_ACCESS_REQUIRED);
1466 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1467 && ((CC->usersupp.flags & US_INTERNET) == 0)
1468 && (!CC->internal_pgm)) {
1469 cprintf("%d You don't have access to Internet mail.\n",
1470 ERROR + HIGHER_ACCESS_REQUIRED);
1473 if (!strcasecmp(buf, "sysop")) {
1478 goto SKFALL; /* don't search local file */
1479 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1480 cprintf("%d Can't send mail to yourself!\n",
1481 ERROR + NO_SUCH_USER);
1484 /* Check to make sure the user exists; also get the correct
1485 * upper/lower casing of the name.
1487 a = getuser(&tempUS, buf);
1489 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1492 strcpy(buf, tempUS.fullname);
1495 SKFALL: b = MES_NORMAL;
1496 if (CC->quickroom.QRflags & QR_ANONONLY)
1498 if (CC->quickroom.QRflags & QR_ANONOPT) {
1502 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1505 /* If we're only checking the validity of the request, return
1506 * success without creating the message.
1509 cprintf("%d %s\n", OK, buf);
1513 cprintf("%d send message\n", SEND_LISTING);
1515 if (CC->fake_postname[0])
1516 make_message(CC->temp, &CC->usersupp, buf,
1517 CC->quickroom.QRname, b, e, format_type,
1519 else if (CC->fake_username[0])
1520 make_message(CC->temp, &CC->usersupp, buf,
1521 CC->quickroom.QRname, b, e, format_type,
1524 make_message(CC->temp, &CC->usersupp, buf,
1525 CC->quickroom.QRname, b, e, format_type, "");
1527 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1528 CC->fake_postname[0] = '\0';
1535 * message entry - mode 3 (raw)
1537 void cmd_ent3(char *entargs)
1543 struct usersupp tempUS;
1548 if (CC->internal_pgm == 0) {
1549 cprintf("%d This command is for internal programs only.\n",
1553 /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1);
1554 for (a = 0; a < strlen(recp); ++a)
1555 if (!isprint(recp[a]))
1556 strcpy(&recp[a], &recp[a + 1]);
1557 while (isspace(recp[0]))
1558 strcpy(recp, &recp[1]);
1559 while (isspace(recp[strlen(recp) - 1]))
1560 recp[strlen(recp) - 1] = 0;
1562 /* If we're in Mail, check the recipient */
1563 if (strlen(recp) > 0) {
1564 e = alias(recp); /* alias and mail type */
1565 if ((recp[0] == 0) || (e == MES_ERROR)) {
1566 cprintf("%d Unknown address - cannot send message.\n",
1567 ERROR + NO_SUCH_USER);
1570 if (e == MES_LOCAL) {
1571 a = getuser(&tempUS, recp);
1573 cprintf("%d No such user.\n",
1574 ERROR + NO_SUCH_USER);
1579 /* At this point, message has been approved. */
1580 if (extract_int(entargs, 0) == 0) {
1581 cprintf("%d OK to send\n", OK);
1584 /* open a temp file to hold the message */
1585 fp = fopen(CC->temp, "wb");
1587 cprintf("%d Cannot open %s: %s\n",
1588 ERROR + INTERNAL_ERROR,
1589 CC->temp, strerror(errno));
1592 msglen = extract_long(entargs, 2);
1593 cprintf("%d %ld\n", SEND_BINARY, msglen);
1594 while (msglen > 0L) {
1595 bloklen = ((msglen >= 255L) ? 255 : msglen);
1596 client_read(buf, (int) bloklen);
1597 fwrite(buf, (int) bloklen, 1, fp);
1598 msglen = msglen - bloklen;
1602 save_message(CC->temp, recp, "", e, 0);
1607 * API function to delete messages which match a set of criteria
1608 * (returns the actual number of messages deleted)
1609 * FIX ... still need to implement delete by content type
1611 int CtdlDeleteMessages(char *room_name, /* which room */
1612 long dmsgnum, /* or "0" for any */
1613 char *content_type /* or NULL for any */
1617 struct quickroom qrbuf;
1618 struct cdbdata *cdbfr;
1619 long *msglist = NULL;
1622 int num_deleted = 0;
1624 struct SuppMsgInfo smi;
1626 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1627 room_name, dmsgnum, content_type);
1629 /* get room record, obtaining a lock... */
1630 if (lgetroom(&qrbuf, room_name) != 0) {
1631 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1633 return (0); /* room not found */
1635 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1637 if (cdbfr != NULL) {
1638 msglist = mallok(cdbfr->len);
1639 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1640 num_msgs = cdbfr->len / sizeof(long);
1644 for (i = 0; i < num_msgs; ++i) {
1647 /* Set/clear a bit for each criterion */
1649 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
1650 delete_this |= 0x01;
1652 if (content_type == NULL) {
1653 delete_this |= 0x02;
1655 GetSuppMsgInfo(&smi, msglist[i]);
1656 if (!strcasecmp(smi.smi_content_type,
1658 delete_this |= 0x02;
1662 /* Delete message only if all bits are set */
1663 if (delete_this == 0x03) {
1664 AdjRefCount(msglist[i], -1);
1670 num_msgs = sort_msglist(msglist, num_msgs);
1671 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1672 msglist, (num_msgs * sizeof(long)));
1674 qrbuf.QRhighest = msglist[num_msgs - 1];
1678 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1679 return (num_deleted);
1685 * Delete message from current room
1687 void cmd_dele(char *delstr)
1692 getuser(&CC->usersupp, CC->curr_user);
1693 if ((CC->usersupp.axlevel < 6)
1694 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1695 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1696 cprintf("%d Higher access required.\n",
1697 ERROR + HIGHER_ACCESS_REQUIRED);
1700 delnum = extract_long(delstr, 0);
1702 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1705 cprintf("%d %d message%s deleted.\n", OK,
1706 num_deleted, ((num_deleted != 1) ? "s" : ""));
1708 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1714 * move a message to another room
1716 void cmd_move(char *args)
1720 struct quickroom qtemp;
1723 num = extract_long(args, 0);
1724 extract(targ, args, 1);
1726 getuser(&CC->usersupp, CC->curr_user);
1727 if ((CC->usersupp.axlevel < 6)
1728 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1729 cprintf("%d Higher access required.\n",
1730 ERROR + HIGHER_ACCESS_REQUIRED);
1733 if (getroom(&qtemp, targ) != 0) {
1734 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1737 /* Bump the reference count, otherwise the message will be deleted
1738 * from disk when we remove it from the source room.
1740 AdjRefCount(num, 1);
1742 /* yank the message out of the current room... */
1743 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1746 /* put the message into the target room */
1747 lgetroom(&qtemp, targ);
1748 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1750 cprintf("%d Message moved.\n", OK);
1752 AdjRefCount(num, (-1)); /* oops */
1753 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1760 * GetSuppMsgInfo() - Get the supplementary record for a message
1762 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1765 struct cdbdata *cdbsmi;
1768 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1769 smibuf->smi_msgnum = msgnum;
1770 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1772 /* Use the negative of the message number for its supp record index */
1773 TheIndex = (0L - msgnum);
1775 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1776 if (cdbsmi == NULL) {
1777 return; /* record not found; go with defaults */
1779 memcpy(smibuf, cdbsmi->ptr,
1780 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1781 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1788 * PutSuppMsgInfo() - (re)write supplementary record for a message
1790 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1794 /* Use the negative of the message number for its supp record index */
1795 TheIndex = (0L - smibuf->smi_msgnum);
1797 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1798 smibuf->smi_msgnum, smibuf->smi_refcount);
1800 cdb_store(CDB_MSGMAIN,
1801 &TheIndex, sizeof(long),
1802 smibuf, sizeof(struct SuppMsgInfo));
1806 * AdjRefCount - change the reference count for a message;
1807 * delete the message if it reaches zero
1808 */ void AdjRefCount(long msgnum, int incr)
1811 struct SuppMsgInfo smi;
1814 /* This is a *tight* critical section; please keep it that way, as
1815 * it may get called while nested in other critical sections.
1816 * Complicating this any further will surely cause deadlock!
1818 begin_critical_section(S_SUPPMSGMAIN);
1819 GetSuppMsgInfo(&smi, msgnum);
1820 smi.smi_refcount += incr;
1821 PutSuppMsgInfo(&smi);
1822 end_critical_section(S_SUPPMSGMAIN);
1824 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1825 msgnum, smi.smi_refcount);
1827 /* If the reference count is now zero, delete the message
1828 * (and its supplementary record as well).
1830 if (smi.smi_refcount == 0) {
1831 lprintf(9, "Deleting message <%ld>\n", msgnum);
1833 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1834 delnum = (0L - msgnum);
1835 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1840 * Write a generic object to this room
1842 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
1843 char *content_type, /* MIME type of this object */
1844 char *tempfilename, /* Where to fetch it from */
1845 int is_mailbox, /* Private mailbox room? */
1846 int is_binary, /* Is encoding necessary? */
1847 int is_unique /* Del others of this type? */
1852 char filename[PATH_MAX];
1855 struct quickroom qrbuf;
1856 char roomname[ROOMNAMELEN];
1859 MailboxName(roomname, &CC->usersupp, req_room);
1861 safestrncpy(roomname, req_room, sizeof(roomname));
1863 strcpy(filename, tmpnam(NULL));
1864 fp = fopen(filename, "w");
1868 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1869 fprintf(fp, "T%ld%c", time(NULL), 0);
1870 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1871 fprintf(fp, "O%s%c", roomname, 0);
1872 fprintf(fp, "N%s%c", config.c_nodename, 0);
1873 fprintf(fp, "MContent-type: %s\n", content_type);
1875 tempfp = fopen(tempfilename, "r");
1876 if (tempfp == NULL) {
1881 if (is_binary == 0) {
1882 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1883 while (ch = getc(tempfp), ch > 0)
1889 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1892 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1893 tempfilename, filename);
1897 /* Create the requested room if we have to. */
1898 if (getroom(&qrbuf, roomname) != 0) {
1899 create_room(roomname, 4, "", 0);
1901 /* If the caller specified this object as unique, delete all
1902 * other objects of this type that are currently in the room.
1905 lprintf(9, "Deleted %d other msgs of this type\n",
1906 CtdlDeleteMessages(roomname, 0L, content_type));
1908 /* Now write the data */
1909 save_message(filename, "", roomname, MES_LOCAL, 1);