* Eliminate EVT_OUTPUTMSG server extensions (don't need them anymore)
[citadel.git] / citadel / msgbase.c
1 /*
2  * $Id$
3  *
4  * Implements the message store.
5  *
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29
30 #include <ctype.h>
31 #include <string.h>
32 #include <limits.h>
33 #include <errno.h>
34 #include <stdarg.h>
35 #include <sys/stat.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "serv_extensions.h"
39 #include "database.h"
40 #include "msgbase.h"
41 #include "support.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
44 #include "room_ops.h"
45 #include "user_ops.h"
46 #include "file_ops.h"
47 #include "control.h"
48 #include "tools.h"
49 #include "mime_parser.h"
50 #include "html.h"
51 #include "genstamp.h"
52 #include "internet_addressing.h"
53
54 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
55 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
56 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
57
58 extern struct config config;
59 long config_msgnum;
60
61
62 /* 
63  * This really belongs in serv_network.c, but I don't know how to export
64  * symbols between modules.
65  */
66 struct FilterList *filterlist = NULL;
67
68
69 /*
70  * These are the four-character field headers we use when outputting
71  * messages in Citadel format (as opposed to RFC822 format).
72  */
73 char *msgkeys[] = {
74         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
75         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
76         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
77         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
78         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
79         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
80         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
81         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
82         NULL, 
83         "from",
84         NULL, NULL, NULL,
85         "exti",
86         "rfca",
87         NULL, 
88         "hnod",
89         "msgn",
90         NULL, NULL, NULL,
91         "text",
92         "node",
93         "room",
94         "path",
95         NULL,
96         "rcpt",
97         "spec",
98         "time",
99         "subj",
100         NULL,
101         NULL,
102         NULL,
103         NULL,
104         NULL
105 };
106
107 /*
108  * This function is self explanatory.
109  * (What can I say, I'm in a weird mood today...)
110  */
111 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 {
113         int i;
114
115         for (i = 0; i < strlen(name); ++i) {
116                 if (name[i] == '@') {
117                         while (isspace(name[i - 1]) && i > 0) {
118                                 strcpy(&name[i - 1], &name[i]);
119                                 --i;
120                         }
121                         while (isspace(name[i + 1])) {
122                                 strcpy(&name[i + 1], &name[i + 2]);
123                         }
124                 }
125         }
126 }
127
128
129 /*
130  * Aliasing for network mail.
131  * (Error messages have been commented out, because this is a server.)
132  */
133 int alias(char *name)
134 {                               /* process alias and routing info for mail */
135         FILE *fp;
136         int a, i;
137         char aaa[SIZ], bbb[SIZ];
138         char *ignetcfg = NULL;
139         char *ignetmap = NULL;
140         int at = 0;
141         char node[SIZ];
142         char testnode[SIZ];
143         char buf[SIZ];
144
145         striplt(name);
146         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
147
148         fp = fopen("network/mail.aliases", "r");
149         if (fp == NULL) {
150                 fp = fopen("/dev/null", "r");
151         }
152         if (fp == NULL) {
153                 return (MES_ERROR);
154         }
155         strcpy(aaa, "");
156         strcpy(bbb, "");
157         while (fgets(aaa, sizeof aaa, fp) != NULL) {
158                 while (isspace(name[0]))
159                         strcpy(name, &name[1]);
160                 aaa[strlen(aaa) - 1] = 0;
161                 strcpy(bbb, "");
162                 for (a = 0; a < strlen(aaa); ++a) {
163                         if (aaa[a] == ',') {
164                                 strcpy(bbb, &aaa[a + 1]);
165                                 aaa[a] = 0;
166                         }
167                 }
168                 if (!strcasecmp(name, aaa))
169                         strcpy(name, bbb);
170         }
171         fclose(fp);
172
173         /* Hit the Global Address Book */
174         if (CtdlDirectoryLookup(aaa, name) == 0) {
175                 strcpy(name, aaa);
176         }
177
178         lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
179
180         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
181         for (a=0; a<strlen(name); ++a) {
182                 if (name[a] == '@') {
183                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
184                                 name[a] = 0;
185                                 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
186                         }
187                 }
188         }
189
190         /* determine local or remote type, see citadel.h */
191         at = haschar(name, '@');
192         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
193         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
194         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
195
196         /* figure out the delivery mode */
197         extract_token(node, name, 1, '@');
198
199         /* If there are one or more dots in the nodename, we assume that it
200          * is an FQDN and will attempt SMTP delivery to the Internet.
201          */
202         if (haschar(node, '.') > 0) {
203                 return(MES_INTERNET);
204         }
205
206         /* Otherwise we look in the IGnet maps for a valid Citadel node.
207          * Try directly-connected nodes first...
208          */
209         ignetcfg = CtdlGetSysConfig(IGNETCFG);
210         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
211                 extract_token(buf, ignetcfg, i, '\n');
212                 extract_token(testnode, buf, 0, '|');
213                 if (!strcasecmp(node, testnode)) {
214                         phree(ignetcfg);
215                         return(MES_IGNET);
216                 }
217         }
218         phree(ignetcfg);
219
220         /*
221          * Then try nodes that are two or more hops away.
222          */
223         ignetmap = CtdlGetSysConfig(IGNETMAP);
224         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
225                 extract_token(buf, ignetmap, i, '\n');
226                 extract_token(testnode, buf, 0, '|');
227                 if (!strcasecmp(node, testnode)) {
228                         phree(ignetmap);
229                         return(MES_IGNET);
230                 }
231         }
232         phree(ignetmap);
233
234         /* If we get to this point it's an invalid node name */
235         return (MES_ERROR);
236 }
237
238
239 void get_mm(void)
240 {
241         FILE *fp;
242
243         fp = fopen("citadel.control", "r");
244         fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
245         fclose(fp);
246 }
247
248
249
250 void simple_listing(long msgnum, void *userdata)
251 {
252         cprintf("%ld\n", msgnum);
253 }
254
255
256
257 /* Determine if a given message matches the fields in a message template.
258  * Return 0 for a successful match.
259  */
260 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
261         int i;
262
263         /* If there aren't any fields in the template, all messages will
264          * match.
265          */
266         if (template == NULL) return(0);
267
268         /* Null messages are bogus. */
269         if (msg == NULL) return(1);
270
271         for (i='A'; i<='Z'; ++i) {
272                 if (template->cm_fields[i] != NULL) {
273                         if (msg->cm_fields[i] == NULL) {
274                                 return 1;
275                         }
276                         if (strcasecmp(msg->cm_fields[i],
277                                 template->cm_fields[i])) return 1;
278                 }
279         }
280
281         /* All compares succeeded: we have a match! */
282         return 0;
283 }
284
285
286
287 /*
288  * Retrieve the "seen" message list for the current room.
289  */
290 void CtdlGetSeen(char *buf, int which_set) {
291         struct visit vbuf;
292
293         /* Learn about the user and room in question */
294         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
295
296         if (which_set == ctdlsetseen_seen)
297                 safestrncpy(buf, vbuf.v_seen, SIZ);
298         if (which_set == ctdlsetseen_answered)
299                 safestrncpy(buf, vbuf.v_answered, SIZ);
300 }
301
302
303
304 /*
305  * Manipulate the "seen msgs" string (or other message set strings)
306  */
307 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
308         char newseen[SIZ];
309         struct cdbdata *cdbfr;
310         int i;
311         int is_seen = 0;
312         int was_seen = 1;
313         long lo = (-1L);
314         long hi = (-1L);
315         struct visit vbuf;
316         long *msglist;
317         int num_msgs = 0;
318         char vset[SIZ];
319
320         /* Learn about the user and room in question */
321         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
322
323         /* Load the message list */
324         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
325         if (cdbfr != NULL) {
326                 msglist = mallok(cdbfr->len);
327                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
328                 num_msgs = cdbfr->len / sizeof(long);
329                 cdb_free(cdbfr);
330         } else {
331                 return; /* No messages at all?  No further action. */
332         }
333
334         /* Decide which message set we're manipulating */
335         if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
336         if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
337
338         lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
339         strcpy(newseen, "");
340
341         for (i=0; i<num_msgs; ++i) {
342                 is_seen = 0;
343
344                 if (msglist[i] == target_msgnum) {
345                         is_seen = target_setting;
346                 }
347                 else {
348                         if (is_msg_in_mset(vset, msglist[i])) {
349                                 is_seen = 1;
350                         }
351                 }
352
353                 if (is_seen == 1) {
354                         if (lo < 0L) lo = msglist[i];
355                         hi = msglist[i];
356                 }
357                 if (  ((is_seen == 0) && (was_seen == 1))
358                    || ((is_seen == 1) && (i == num_msgs-1)) ) {
359                         size_t tmp;
360
361                         if ( (strlen(newseen) + 20) > SIZ) {
362                                 strcpy(newseen, &newseen[20]);
363                                 newseen[0] = '*';
364                         }
365                         tmp = strlen(newseen);
366                         if (tmp > 0) {
367                                 strcat(newseen, ",");
368                                 tmp++;
369                         }
370                         if (lo == hi) {
371                                 snprintf(&newseen[tmp], sizeof newseen - tmp,
372                                          "%ld", lo);
373                         }
374                         else {
375                                 snprintf(&newseen[tmp], sizeof newseen - tmp,
376                                          "%ld:%ld", lo, hi);
377                         }
378                         lo = (-1L);
379                         hi = (-1L);
380                 }
381                 was_seen = is_seen;
382         }
383
384         /* Decide which message set we're manipulating */
385         if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
386         if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
387
388         lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
389         phree(msglist);
390         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
391 }
392
393
394 /*
395  * API function to perform an operation for each qualifying message in the
396  * current room.  (Returns the number of messages processed.)
397  */
398 int CtdlForEachMessage(int mode, long ref,
399                         char *content_type,
400                         struct CtdlMessage *compare,
401                         void (*CallBack) (long, void *),
402                         void *userdata)
403 {
404
405         int a;
406         struct visit vbuf;
407         struct cdbdata *cdbfr;
408         long *msglist = NULL;
409         int num_msgs = 0;
410         int num_processed = 0;
411         long thismsg;
412         struct MetaData smi;
413         struct CtdlMessage *msg;
414         int is_seen;
415         long lastold = 0L;
416         int printed_lastold = 0;
417
418         /* Learn about the user and room in question */
419         get_mm();
420         getuser(&CC->user, CC->curr_user);
421         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
422
423         /* Load the message list */
424         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
425         if (cdbfr != NULL) {
426                 msglist = mallok(cdbfr->len);
427                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
428                 num_msgs = cdbfr->len / sizeof(long);
429                 cdb_free(cdbfr);
430         } else {
431                 return 0;       /* No messages at all?  No further action. */
432         }
433
434
435         /*
436          * Now begin the traversal.
437          */
438         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
439
440                 /* If the caller is looking for a specific MIME type, filter
441                  * out all messages which are not of the type requested.
442                  */
443                 if (content_type != NULL) if (strlen(content_type) > 0) {
444
445                         /* This call to GetMetaData() sits inside this loop
446                          * so that we only do the extra database read per msg
447                          * if we need to.  Doing the extra read all the time
448                          * really kills the server.  If we ever need to use
449                          * metadata for another search criterion, we need to
450                          * move the read somewhere else -- but still be smart
451                          * enough to only do the read if the caller has
452                          * specified something that will need it.
453                          */
454                         GetMetaData(&smi, msglist[a]);
455
456                         if (strcasecmp(smi.meta_content_type, content_type)) {
457                                 msglist[a] = 0L;
458                         }
459                 }
460         }
461
462         num_msgs = sort_msglist(msglist, num_msgs);
463
464         /* If a template was supplied, filter out the messages which
465          * don't match.  (This could induce some delays!)
466          */
467         if (num_msgs > 0) {
468                 if (compare != NULL) {
469                         for (a = 0; a < num_msgs; ++a) {
470                                 msg = CtdlFetchMessage(msglist[a]);
471                                 if (msg != NULL) {
472                                         if (CtdlMsgCmp(msg, compare)) {
473                                                 msglist[a] = 0L;
474                                         }
475                                         CtdlFreeMessage(msg);
476                                 }
477                         }
478                 }
479         }
480
481         
482         /*
483          * Now iterate through the message list, according to the
484          * criteria supplied by the caller.
485          */
486         if (num_msgs > 0)
487                 for (a = 0; a < num_msgs; ++a) {
488                         thismsg = msglist[a];
489                         is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
490                         if (is_seen) lastold = thismsg;
491                         if ((thismsg > 0L)
492                             && (
493
494                                        (mode == MSGS_ALL)
495                                        || ((mode == MSGS_OLD) && (is_seen))
496                                        || ((mode == MSGS_NEW) && (!is_seen))
497                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
498                                    || ((mode == MSGS_FIRST) && (a < ref))
499                                 || ((mode == MSGS_GT) && (thismsg > ref))
500                                 || ((mode == MSGS_EQ) && (thismsg == ref))
501                             )
502                             ) {
503                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
504                                         if (CallBack)
505                                                 CallBack(lastold, userdata);
506                                         printed_lastold = 1;
507                                         ++num_processed;
508                                 }
509                                 if (CallBack) CallBack(thismsg, userdata);
510                                 ++num_processed;
511                         }
512                 }
513         phree(msglist);         /* Clean up */
514         return num_processed;
515 }
516
517
518
519 /*
520  * cmd_msgs()  -  get list of message #'s in this room
521  *                implements the MSGS server command using CtdlForEachMessage()
522  */
523 void cmd_msgs(char *cmdbuf)
524 {
525         int mode = 0;
526         char which[SIZ];
527         char buf[SIZ];
528         char tfield[SIZ];
529         char tvalue[SIZ];
530         int cm_ref = 0;
531         int i;
532         int with_template = 0;
533         struct CtdlMessage *template = NULL;
534
535         extract(which, cmdbuf, 0);
536         cm_ref = extract_int(cmdbuf, 1);
537         with_template = extract_int(cmdbuf, 2);
538
539         mode = MSGS_ALL;
540         strcat(which, "   ");
541         if (!strncasecmp(which, "OLD", 3))
542                 mode = MSGS_OLD;
543         else if (!strncasecmp(which, "NEW", 3))
544                 mode = MSGS_NEW;
545         else if (!strncasecmp(which, "FIRST", 5))
546                 mode = MSGS_FIRST;
547         else if (!strncasecmp(which, "LAST", 4))
548                 mode = MSGS_LAST;
549         else if (!strncasecmp(which, "GT", 2))
550                 mode = MSGS_GT;
551
552         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
553                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
554                 return;
555         }
556
557         if (with_template) {
558                 cprintf("%d Send template then receive message list\n",
559                         START_CHAT_MODE);
560                 template = (struct CtdlMessage *)
561                         mallok(sizeof(struct CtdlMessage));
562                 memset(template, 0, sizeof(struct CtdlMessage));
563                 while(client_gets(buf), strcmp(buf,"000")) {
564                         extract(tfield, buf, 0);
565                         extract(tvalue, buf, 1);
566                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
567                                 if (!strcasecmp(tfield, msgkeys[i])) {
568                                         template->cm_fields[i] =
569                                                 strdoop(tvalue);
570                                 }
571                         }
572                 }
573         }
574         else {
575                 cprintf("%d Message list...\n", LISTING_FOLLOWS);
576         }
577
578         CtdlForEachMessage(mode, cm_ref,
579                 NULL, template, simple_listing, NULL);
580         if (template != NULL) CtdlFreeMessage(template);
581         cprintf("000\n");
582 }
583
584
585
586
587 /* 
588  * help_subst()  -  support routine for help file viewer
589  */
590 void help_subst(char *strbuf, char *source, char *dest)
591 {
592         char workbuf[SIZ];
593         int p;
594
595         while (p = pattern2(strbuf, source), (p >= 0)) {
596                 strcpy(workbuf, &strbuf[p + strlen(source)]);
597                 strcpy(&strbuf[p], dest);
598                 strcat(strbuf, workbuf);
599         }
600 }
601
602
603 void do_help_subst(char *buffer)
604 {
605         char buf2[16];
606
607         help_subst(buffer, "^nodename", config.c_nodename);
608         help_subst(buffer, "^humannode", config.c_humannode);
609         help_subst(buffer, "^fqdn", config.c_fqdn);
610         help_subst(buffer, "^username", CC->user.fullname);
611         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
612         help_subst(buffer, "^usernum", buf2);
613         help_subst(buffer, "^sysadm", config.c_sysadm);
614         help_subst(buffer, "^variantname", CITADEL);
615         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
616         help_subst(buffer, "^maxsessions", buf2);
617         help_subst(buffer, "^bbsdir", BBSDIR);
618 }
619
620
621
622 /*
623  * memfmout()  -  Citadel text formatter and paginator.
624  *             Although the original purpose of this routine was to format
625  *             text to the reader's screen width, all we're really using it
626  *             for here is to format text out to 80 columns before sending it
627  *             to the client.  The client software may reformat it again.
628  */
629 void memfmout(
630         int width,              /* screen width to use */
631         char *mptr,             /* where are we going to get our text from? */
632         char subst,             /* nonzero if we should do substitutions */
633         char *nl)               /* string to terminate lines with */
634 {
635         int a, b, c;
636         int real = 0;
637         int old = 0;
638         cit_uint8_t ch;
639         char aaa[140];
640         char buffer[SIZ];
641
642         strcpy(aaa, "");
643         old = 255;
644         strcpy(buffer, "");
645         c = 1;                  /* c is the current pos */
646
647         do {
648                 if (subst) {
649                         while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
650                                 ch = *mptr++;
651                                 buffer[strlen(buffer) + 1] = 0;
652                                 buffer[strlen(buffer)] = ch;
653                         }
654
655                         if (buffer[0] == '^')
656                                 do_help_subst(buffer);
657
658                         buffer[strlen(buffer) + 1] = 0;
659                         a = buffer[0];
660                         strcpy(buffer, &buffer[1]);
661                 } else {
662                         ch = *mptr++;
663                 }
664
665                 old = real;
666                 real = ch;
667
668                 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
669                         ch = 32;
670                 if (((old == 13) || (old == 10)) && (isspace(real))) {
671                         cprintf("%s", nl);
672                         c = 1;
673                 }
674                 if (ch > 126)
675                         continue;
676
677                 if (ch > 32) {
678                         if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
679                                 cprintf("%s%s", nl, aaa);
680                                 c = strlen(aaa);
681                                 aaa[0] = 0;
682                         }
683                         b = strlen(aaa);
684                         aaa[b] = ch;
685                         aaa[b + 1] = 0;
686                 }
687                 if (ch == 32) {
688                         if ((strlen(aaa) + c) > (width - 5)) {
689                                 cprintf("%s", nl);
690                                 c = 1;
691                         }
692                         cprintf("%s ", aaa);
693                         ++c;
694                         c = c + strlen(aaa);
695                         strcpy(aaa, "");
696                 }
697                 if ((ch == 13) || (ch == 10)) {
698                         cprintf("%s%s", aaa, nl);
699                         c = 1;
700                         strcpy(aaa, "");
701                 }
702
703         } while (ch > 0);
704
705         cprintf("%s%s", aaa, nl);
706 }
707
708
709
710 /*
711  * Callback function for mime parser that simply lists the part
712  */
713 void list_this_part(char *name, char *filename, char *partnum, char *disp,
714                     void *content, char *cbtype, size_t length, char *encoding,
715                     void *cbuserdata)
716 {
717
718         cprintf("part=%s|%s|%s|%s|%s|%ld\n",
719                 name, filename, partnum, disp, cbtype, (long)length);
720 }
721
722 /* 
723  * Callback function for multipart prefix
724  */
725 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
726                     void *content, char *cbtype, size_t length, char *encoding,
727                     void *cbuserdata)
728 {
729         cprintf("pref=%s|%s\n", partnum, cbtype);
730 }
731
732 /* 
733  * Callback function for multipart sufffix
734  */
735 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
736                     void *content, char *cbtype, size_t length, char *encoding,
737                     void *cbuserdata)
738 {
739         cprintf("suff=%s|%s\n", partnum, cbtype);
740 }
741
742
743 /*
744  * Callback function for mime parser that opens a section for downloading
745  */
746 void mime_download(char *name, char *filename, char *partnum, char *disp,
747                    void *content, char *cbtype, size_t length, char *encoding,
748                    void *cbuserdata)
749 {
750
751         /* Silently go away if there's already a download open... */
752         if (CC->download_fp != NULL)
753                 return;
754
755         /* ...or if this is not the desired section */
756         if (strcasecmp(desired_section, partnum))
757                 return;
758
759         CC->download_fp = tmpfile();
760         if (CC->download_fp == NULL)
761                 return;
762
763         fwrite(content, length, 1, CC->download_fp);
764         fflush(CC->download_fp);
765         rewind(CC->download_fp);
766
767         OpenCmdResult(filename, cbtype);
768 }
769
770
771
772 /*
773  * Load a message from disk into memory.
774  * This is used by CtdlOutputMsg() and other fetch functions.
775  *
776  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
777  *       using the CtdlMessageFree() function.
778  */
779 struct CtdlMessage *CtdlFetchMessage(long msgnum)
780 {
781         struct cdbdata *dmsgtext;
782         struct CtdlMessage *ret = NULL;
783         char *mptr;
784         cit_uint8_t ch;
785         cit_uint8_t field_header;
786         size_t field_length;
787
788         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
789         if (dmsgtext == NULL) {
790                 return NULL;
791         }
792         mptr = dmsgtext->ptr;
793
794         /* Parse the three bytes that begin EVERY message on disk.
795          * The first is always 0xFF, the on-disk magic number.
796          * The second is the anonymous/public type byte.
797          * The third is the format type byte (vari, fixed, or MIME).
798          */
799         ch = *mptr++;
800         if (ch != 255) {
801                 lprintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
802                 cdb_free(dmsgtext);
803                 return NULL;
804         }
805         ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
806         memset(ret, 0, sizeof(struct CtdlMessage));
807
808         ret->cm_magic = CTDLMESSAGE_MAGIC;
809         ret->cm_anon_type = *mptr++;    /* Anon type byte */
810         ret->cm_format_type = *mptr++;  /* Format type byte */
811
812         /*
813          * The rest is zero or more arbitrary fields.  Load them in.
814          * We're done when we encounter either a zero-length field or
815          * have just processed the 'M' (message text) field.
816          */
817         do {
818                 field_length = strlen(mptr);
819                 if (field_length == 0)
820                         break;
821                 field_header = *mptr++;
822                 ret->cm_fields[field_header] = mallok(field_length);
823                 strcpy(ret->cm_fields[field_header], mptr);
824
825                 while (*mptr++ != 0);   /* advance to next field */
826
827         } while ((field_length > 0) && (field_header != 'M'));
828
829         cdb_free(dmsgtext);
830
831         /* Always make sure there's something in the msg text field */
832         if (ret->cm_fields['M'] == NULL)
833                 ret->cm_fields['M'] = strdoop("<no text>\n");
834
835         /* Perform "before read" hooks (aborting if any return nonzero) */
836         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
837                 CtdlFreeMessage(ret);
838                 return NULL;
839         }
840
841         return (ret);
842 }
843
844
845 /*
846  * Returns 1 if the supplied pointer points to a valid Citadel message.
847  * If the pointer is NULL or the magic number check fails, returns 0.
848  */
849 int is_valid_message(struct CtdlMessage *msg) {
850         if (msg == NULL)
851                 return 0;
852         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
853                 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
854                 return 0;
855         }
856         return 1;
857 }
858
859
860 /*
861  * 'Destructor' for struct CtdlMessage
862  */
863 void CtdlFreeMessage(struct CtdlMessage *msg)
864 {
865         int i;
866
867         if (is_valid_message(msg) == 0) return;
868
869         for (i = 0; i < 256; ++i)
870                 if (msg->cm_fields[i] != NULL) {
871                         phree(msg->cm_fields[i]);
872                 }
873
874         msg->cm_magic = 0;      /* just in case */
875         phree(msg);
876 }
877
878
879 /*
880  * Pre callback function for multipart/alternative
881  *
882  * NOTE: this differs from the standard behavior for a reason.  Normally when
883  *       displaying multipart/alternative you want to show the _last_ usable
884  *       format in the message.  Here we show the _first_ one, because it's
885  *       usually text/plain.  Since this set of functions is designed for text
886  *       output to non-MIME-aware clients, this is the desired behavior.
887  *
888  */
889 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
890                 void *content, char *cbtype, size_t length, char *encoding,
891                 void *cbuserdata)
892 {
893                 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);  
894                 if (!strcasecmp(cbtype, "multipart/alternative")) {
895                         ++ma->is_ma;
896                         ma->did_print = 0;
897                         return;
898                 }
899 }
900
901 /*
902  * Post callback function for multipart/alternative
903  */
904 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
905                 void *content, char *cbtype, size_t length, char *encoding,
906                 void *cbuserdata)
907 {
908                 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype); 
909                 if (!strcasecmp(cbtype, "multipart/alternative")) {
910                         --ma->is_ma;
911                         ma->did_print = 0;
912                         return;
913                 }
914 }
915
916 /*
917  * Inline callback function for mime parser that wants to display text
918  */
919 void fixed_output(char *name, char *filename, char *partnum, char *disp,
920                 void *content, char *cbtype, size_t length, char *encoding,
921                 void *cbuserdata)
922         {
923                 char *ptr;
924                 char *wptr;
925                 size_t wlen;
926
927                 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);      
928
929                 /*
930                  * If we're in the middle of a multipart/alternative scope and
931                  * we've already printed another section, skip this one.
932                  */     
933                 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
934                         lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
935                         return;
936                 }
937                 ma->did_print = 1;
938         
939                 if ( (!strcasecmp(cbtype, "text/plain")) 
940                    || (strlen(cbtype)==0) ) {
941                         wptr = content;
942                         if (length > 0) {
943                                 client_write(wptr, length);
944                                 if (wptr[length-1] != '\n') {
945                                         cprintf("\n");
946                                 }
947                         }
948                 }
949                 else if (!strcasecmp(cbtype, "text/html")) {
950                         ptr = html_to_ascii(content, 80, 0);
951                         wlen = strlen(ptr);
952                         client_write(ptr, wlen);
953                         if (ptr[wlen-1] != '\n') {
954                                 cprintf("\n");
955                         }
956                         phree(ptr);
957                 }
958                 else if (strncasecmp(cbtype, "multipart/", 10)) {
959                         cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
960                                 partnum, filename, cbtype, (long)length);
961                 }
962         }
963
964 /*
965  * The client is elegant and sophisticated and wants to be choosy about
966  * MIME content types, so figure out which multipart/alternative part
967  * we're going to send.
968  */
969 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
970                 void *content, char *cbtype, size_t length, char *encoding,
971                 void *cbuserdata)
972 {
973         char buf[SIZ];
974         int i;
975
976         if (ma->is_ma > 0) {
977                 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
978                         extract(buf, CC->preferred_formats, i);
979                         if (!strcasecmp(buf, cbtype)) {
980                                 strcpy(ma->chosen_part, partnum);
981                         }
982                 }
983         }
984 }
985
986 /*
987  * Now that we've chosen our preferred part, output it.
988  */
989 void output_preferred(char *name, char *filename, char *partnum, char *disp,
990                 void *content, char *cbtype, size_t length, char *encoding,
991                 void *cbuserdata)
992 {
993         int i;
994         char buf[SIZ];
995         int add_newline = 0;
996         char *text_content;
997
998         /* This is not the MIME part you're looking for... */
999         if (strcasecmp(partnum, ma->chosen_part)) return;
1000
1001         /* If the content-type of this part is in our preferred formats
1002          * list, we can simply output it verbatim.
1003          */
1004         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1005                 extract(buf, CC->preferred_formats, i);
1006                 if (!strcasecmp(buf, cbtype)) {
1007                         /* Yeah!  Go!  W00t!! */
1008
1009                         text_content = (char *)content;
1010                         if (text_content[length-1] != '\n') {
1011                                 ++add_newline;
1012                         }
1013
1014                         cprintf("Content-type: %s\n", cbtype);
1015                         cprintf("Content-length: %d\n",
1016                                 (int)(length + add_newline) );
1017                         if (strlen(encoding) > 0) {
1018                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1019                         }
1020                         else {
1021                                 cprintf("Content-transfer-encoding: 7bit\n");
1022                         }
1023                         cprintf("\n");
1024                         client_write(content, length);
1025                         if (add_newline) cprintf("\n");
1026                         return;
1027                 }
1028         }
1029
1030         /* No translations required or possible: output as text/plain */
1031         cprintf("Content-type: text/plain\n\n");
1032         fixed_output(name, filename, partnum, disp, content, cbtype,
1033                         length, encoding, cbuserdata);
1034 }
1035
1036
1037 /*
1038  * Get a message off disk.  (returns om_* values found in msgbase.h)
1039  * 
1040  */
1041 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1042                 int mode,               /* how would you like that message? */
1043                 int headers_only,       /* eschew the message body? */
1044                 int do_proto,           /* do Citadel protocol responses? */
1045                 int crlf                /* Use CRLF newlines instead of LF? */
1046 ) {
1047         struct CtdlMessage *TheMessage;
1048         int retcode;
1049
1050         lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n", 
1051                 msg_num, mode);
1052
1053         TheMessage = NULL;
1054
1055         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1056                 if (do_proto) cprintf("%d Not logged in.\n",
1057                         ERROR + NOT_LOGGED_IN);
1058                 return(om_not_logged_in);
1059         }
1060
1061         /* FIXME ... small security issue
1062          * We need to check to make sure the requested message is actually
1063          * in the current room, and set msg_ok to 1 only if it is.  This
1064          * functionality is currently missing because I'm in a hurry to replace
1065          * broken production code with nonbroken pre-beta code.  :(   -- ajc
1066          *
1067          if (!msg_ok) {
1068          if (do_proto) cprintf("%d Message %ld is not in this room.\n",
1069          ERROR + MESSAGE_NOT_FOUND, msg_num);
1070          return(om_no_such_msg);
1071          }
1072          */
1073
1074         /*
1075          * Fetch the message from disk.
1076          */
1077         TheMessage = CtdlFetchMessage(msg_num);
1078
1079         if (TheMessage == NULL) {
1080                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1081                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1082                 return(om_no_such_msg);
1083         }
1084         
1085         retcode = CtdlOutputPreLoadedMsg(
1086                         TheMessage, msg_num, mode,
1087                         headers_only, do_proto, crlf);
1088
1089         CtdlFreeMessage(TheMessage);
1090
1091         return(retcode);
1092 }
1093
1094
1095 /*
1096  * Get a message off disk.  (returns om_* values found in msgbase.h)
1097  * 
1098  */
1099 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1100                 long msg_num,
1101                 int mode,               /* how would you like that message? */
1102                 int headers_only,       /* eschew the message body? */
1103                 int do_proto,           /* do Citadel protocol responses? */
1104                 int crlf                /* Use CRLF newlines instead of LF? */
1105 ) {
1106         int i, k;
1107         char buf[1024];
1108         cit_uint8_t ch;
1109         char allkeys[SIZ];
1110         char display_name[SIZ];
1111         char *mptr;
1112         char *nl;       /* newline string */
1113         int suppress_f = 0;
1114         int subject_found = 0;
1115
1116         /* buffers needed for RFC822 translation */
1117         char suser[SIZ];
1118         char luser[SIZ];
1119         char fuser[SIZ];
1120         char snode[SIZ];
1121         char lnode[SIZ];
1122         char mid[SIZ];
1123         char datestamp[SIZ];
1124         /*                                       */
1125
1126         snprintf(mid, sizeof mid, "%ld", msg_num);
1127         nl = (crlf ? "\r\n" : "\n");
1128
1129         if (!is_valid_message(TheMessage)) {
1130                 lprintf(CTDL_ERR, "ERROR: invalid preloaded message for output\n");
1131                 return(om_no_such_msg);
1132         }
1133
1134         /* Are we downloading a MIME component? */
1135         if (mode == MT_DOWNLOAD) {
1136                 if (TheMessage->cm_format_type != FMT_RFC822) {
1137                         if (do_proto)
1138                                 cprintf("%d This is not a MIME message.\n",
1139                                 ERROR + ILLEGAL_VALUE);
1140                 } else if (CC->download_fp != NULL) {
1141                         if (do_proto) cprintf(
1142                                 "%d You already have a download open.\n",
1143                                 ERROR + RESOURCE_BUSY);
1144                 } else {
1145                         /* Parse the message text component */
1146                         mptr = TheMessage->cm_fields['M'];
1147                         mime_parser(mptr, NULL,
1148                                 *mime_download, NULL, NULL,
1149                                 NULL, 0);
1150                         /* If there's no file open by this time, the requested
1151                          * section wasn't found, so print an error
1152                          */
1153                         if (CC->download_fp == NULL) {
1154                                 if (do_proto) cprintf(
1155                                         "%d Section %s not found.\n",
1156                                         ERROR + FILE_NOT_FOUND,
1157                                         desired_section);
1158                         }
1159                 }
1160                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1161         }
1162
1163         /* now for the user-mode message reading loops */
1164         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1165
1166         /* Does the caller want to skip the headers? */
1167         if (headers_only == HEADERS_NONE) goto START_TEXT;
1168
1169         /* Tell the client which format type we're using. */
1170         if ( (mode == MT_CITADEL) && (do_proto) ) {
1171                 cprintf("type=%d\n", TheMessage->cm_format_type);
1172         }
1173
1174         /* nhdr=yes means that we're only displaying headers, no body */
1175         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1176            && (mode == MT_CITADEL)
1177            && (do_proto)
1178            ) {
1179                 cprintf("nhdr=yes\n");
1180         }
1181
1182         /* begin header processing loop for Citadel message format */
1183
1184         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1185
1186                 strcpy(display_name, "<unknown>");
1187                 if (TheMessage->cm_fields['A']) {
1188                         strcpy(buf, TheMessage->cm_fields['A']);
1189                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1190                                 strcpy(display_name, "****");
1191                         }
1192                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1193                                 strcpy(display_name, "anonymous");
1194                         }
1195                         else {
1196                                 strcpy(display_name, buf);
1197                         }
1198                         if ((is_room_aide())
1199                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1200                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1201                                 size_t tmp = strlen(display_name);
1202                                 snprintf(&display_name[tmp],
1203                                          sizeof display_name - tmp,
1204                                          " [%s]", buf);
1205                         }
1206                 }
1207
1208                 /* Don't show Internet address for users on the
1209                  * local Citadel network.
1210                  */
1211                 suppress_f = 0;
1212                 if (TheMessage->cm_fields['N'] != NULL)
1213                    if (strlen(TheMessage->cm_fields['N']) > 0)
1214                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1215                         suppress_f = 1;
1216                 }
1217                 
1218                 /* Now spew the header fields in the order we like them. */
1219                 strcpy(allkeys, FORDER);
1220                 for (i=0; i<strlen(allkeys); ++i) {
1221                         k = (int) allkeys[i];
1222                         if (k != 'M') {
1223                                 if ( (TheMessage->cm_fields[k] != NULL)
1224                                    && (msgkeys[k] != NULL) ) {
1225                                         if (k == 'A') {
1226                                                 if (do_proto) cprintf("%s=%s\n",
1227                                                         msgkeys[k],
1228                                                         display_name);
1229                                         }
1230                                         else if ((k == 'F') && (suppress_f)) {
1231                                                 /* do nothing */
1232                                         }
1233                                         /* Masquerade display name if needed */
1234                                         else {
1235                                                 if (do_proto) cprintf("%s=%s\n",
1236                                                         msgkeys[k],
1237                                                         TheMessage->cm_fields[k]
1238                                         );
1239                                         }
1240                                 }
1241                         }
1242                 }
1243
1244         }
1245
1246         /* begin header processing loop for RFC822 transfer format */
1247
1248         strcpy(suser, "");
1249         strcpy(luser, "");
1250         strcpy(fuser, "");
1251         strcpy(snode, NODENAME);
1252         strcpy(lnode, HUMANNODE);
1253         if (mode == MT_RFC822) {
1254                 cprintf("X-UIDL: %ld%s", msg_num, nl);
1255                 for (i = 0; i < 256; ++i) {
1256                         if (TheMessage->cm_fields[i]) {
1257                                 mptr = TheMessage->cm_fields[i];
1258
1259                                 if (i == 'A') {
1260                                         strcpy(luser, mptr);
1261                                         strcpy(suser, mptr);
1262                                 }
1263 /****
1264  "Path:" removed for now because it confuses brain-dead Microsoft shitware
1265  into thinking that mail messages are newsgroup messages instead.  When we
1266  add NNTP support back into Citadel we'll have to add code to only output
1267  this field when appropriate.
1268                                 else if (i == 'P') {
1269                                         cprintf("Path: %s%s", mptr, nl);
1270                                 }
1271  ****/
1272                                 else if (i == 'U') {
1273                                         cprintf("Subject: %s%s", mptr, nl);
1274                                         subject_found = 1;
1275                                 }
1276                                 else if (i == 'I')
1277                                         safestrncpy(mid, mptr, sizeof mid);
1278                                 else if (i == 'H')
1279                                         safestrncpy(lnode, mptr, sizeof lnode);
1280                                 else if (i == 'F')
1281                                         safestrncpy(fuser, mptr, sizeof fuser);
1282                                 else if (i == 'O')
1283                                         cprintf("X-Citadel-Room: %s%s",
1284                                                 mptr, nl);
1285                                 else if (i == 'N')
1286                                         safestrncpy(snode, mptr, sizeof snode);
1287                                 else if (i == 'R')
1288                                         cprintf("To: %s%s", mptr, nl);
1289                                 else if (i == 'T') {
1290                                         datestring(datestamp, sizeof datestamp,
1291                                                 atol(mptr), DATESTRING_RFC822);
1292                                         cprintf("Date: %s%s", datestamp, nl);
1293                                 }
1294                         }
1295                 }
1296                 if (subject_found == 0) {
1297                         cprintf("Subject: (no subject)%s", nl);
1298                 }
1299         }
1300
1301         for (i=0; i<strlen(suser); ++i) {
1302                 suser[i] = tolower(suser[i]);
1303                 if (!isalnum(suser[i])) suser[i]='_';
1304         }
1305
1306         if (mode == MT_RFC822) {
1307                 if (!strcasecmp(snode, NODENAME)) {
1308                         strcpy(snode, FQDN);
1309                 }
1310
1311                 /* Construct a fun message id */
1312                 cprintf("Message-ID: <%s", mid);
1313                 if (strchr(mid, '@')==NULL) {
1314                         cprintf("@%s", snode);
1315                 }
1316                 cprintf(">%s", nl);
1317
1318                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1319                         cprintf("From: x@x.org (----)%s", nl);
1320                 }
1321                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1322                         cprintf("From: x@x.org (anonymous)%s", nl);
1323                 }
1324                 else if (strlen(fuser) > 0) {
1325                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1326                 }
1327                 else {
1328                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1329                 }
1330
1331                 cprintf("Organization: %s%s", lnode, nl);
1332
1333                 /* Blank line signifying RFC822 end-of-headers */
1334                 if (TheMessage->cm_format_type != FMT_RFC822) {
1335                         cprintf("%s", nl);
1336                 }
1337         }
1338
1339         /* end header processing loop ... at this point, we're in the text */
1340 START_TEXT:
1341         mptr = TheMessage->cm_fields['M'];
1342
1343         /* Tell the client about the MIME parts in this message */
1344         if (TheMessage->cm_format_type == FMT_RFC822) {
1345                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1346                         mime_parser(mptr, NULL,
1347                                 *list_this_part,
1348                                 *list_this_pref,
1349                                 *list_this_suff,
1350                                 NULL, 0);
1351                 }
1352                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1353                         /* FIXME ... we have to put some code in here to avoid
1354                          * printing duplicate header information when both
1355                          * Citadel and RFC822 headers exist.  Preference should
1356                          * probably be given to the RFC822 headers.
1357                          */
1358                         int done_rfc822_hdrs = 0;
1359                         while (ch=*(mptr++), ch!=0) {
1360                                 if (ch==13) {
1361                                         /* do nothing */
1362                                 }
1363                                 else if (ch==10) {
1364                                         if (!done_rfc822_hdrs) {
1365                                                 if (headers_only != HEADERS_NONE) {
1366                                                         cprintf("%s", nl);
1367                                                 }
1368                                         }
1369                                         else {
1370                                                 if (headers_only != HEADERS_ONLY) {
1371                                                         cprintf("%s", nl);
1372                                                 }
1373                                         }
1374                                         if ((*(mptr) == 13) || (*(mptr) == 10)) {
1375                                                 done_rfc822_hdrs = 1;
1376                                         }
1377                                 }
1378                                 else {
1379                                         if (done_rfc822_hdrs) {
1380                                                 if (headers_only != HEADERS_NONE) {
1381                                                         cprintf("%c", ch);
1382                                                 }
1383                                         }
1384                                         else {
1385                                                 if (headers_only != HEADERS_ONLY) {
1386                                                         cprintf("%c", ch);
1387                                                 }
1388                                         }
1389                                         if ((*mptr == 13) || (*mptr == 10)) {
1390                                                 done_rfc822_hdrs = 1;
1391                                         }
1392                                 }
1393                         }
1394                         goto DONE;
1395                 }
1396         }
1397
1398         if (headers_only == HEADERS_ONLY) {
1399                 goto DONE;
1400         }
1401
1402         /* signify start of msg text */
1403         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1404                 if (do_proto) cprintf("text\n");
1405         }
1406
1407         /* If the format type on disk is 1 (fixed-format), then we want
1408          * everything to be output completely literally ... regardless of
1409          * what message transfer format is in use.
1410          */
1411         if (TheMessage->cm_format_type == FMT_FIXED) {
1412                 if (mode == MT_MIME) {
1413                         cprintf("Content-type: text/plain\n\n");
1414                 }
1415                 strcpy(buf, "");
1416                 while (ch = *mptr++, ch > 0) {
1417                         if (ch == 13)
1418                                 ch = 10;
1419                         if ((ch == 10) || (strlen(buf) > 250)) {
1420                                 cprintf("%s%s", buf, nl);
1421                                 strcpy(buf, "");
1422                         } else {
1423                                 buf[strlen(buf) + 1] = 0;
1424                                 buf[strlen(buf)] = ch;
1425                         }
1426                 }
1427                 if (strlen(buf) > 0)
1428                         cprintf("%s%s", buf, nl);
1429         }
1430
1431         /* If the message on disk is format 0 (Citadel vari-format), we
1432          * output using the formatter at 80 columns.  This is the final output
1433          * form if the transfer format is RFC822, but if the transfer format
1434          * is Citadel proprietary, it'll still work, because the indentation
1435          * for new paragraphs is correct and the client will reformat the
1436          * message to the reader's screen width.
1437          */
1438         if (TheMessage->cm_format_type == FMT_CITADEL) {
1439                 if (mode == MT_MIME) {
1440                         cprintf("Content-type: text/x-citadel-variformat\n\n");
1441                 }
1442                 memfmout(80, mptr, 0, nl);
1443         }
1444
1445         /* If the message on disk is format 4 (MIME), we've gotta hand it
1446          * off to the MIME parser.  The client has already been told that
1447          * this message is format 1 (fixed format), so the callback function
1448          * we use will display those parts as-is.
1449          */
1450         if (TheMessage->cm_format_type == FMT_RFC822) {
1451                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1452                 memset(ma, 0, sizeof(struct ma_info));
1453
1454                 if (mode == MT_MIME) {
1455                         strcpy(ma->chosen_part, "1");
1456                         mime_parser(mptr, NULL,
1457                                 *choose_preferred, *fixed_output_pre,
1458                                 *fixed_output_post, NULL, 0);
1459                         mime_parser(mptr, NULL,
1460                                 *output_preferred, NULL, NULL, NULL, 0);
1461                 }
1462                 else {
1463                         mime_parser(mptr, NULL,
1464                                 *fixed_output, *fixed_output_pre,
1465                                 *fixed_output_post, NULL, 0);
1466                 }
1467         }
1468
1469 DONE:   /* now we're done */
1470         if (do_proto) cprintf("000\n");
1471         return(om_ok);
1472 }
1473
1474
1475
1476 /*
1477  * display a message (mode 0 - Citadel proprietary)
1478  */
1479 void cmd_msg0(char *cmdbuf)
1480 {
1481         long msgid;
1482         int headers_only = HEADERS_ALL;
1483
1484         msgid = extract_long(cmdbuf, 0);
1485         headers_only = extract_int(cmdbuf, 1);
1486
1487         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1488         return;
1489 }
1490
1491
1492 /*
1493  * display a message (mode 2 - RFC822)
1494  */
1495 void cmd_msg2(char *cmdbuf)
1496 {
1497         long msgid;
1498         int headers_only = HEADERS_ALL;
1499
1500         msgid = extract_long(cmdbuf, 0);
1501         headers_only = extract_int(cmdbuf, 1);
1502
1503         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1504 }
1505
1506
1507
1508 /* 
1509  * display a message (mode 3 - IGnet raw format - internal programs only)
1510  */
1511 void cmd_msg3(char *cmdbuf)
1512 {
1513         long msgnum;
1514         struct CtdlMessage *msg;
1515         struct ser_ret smr;
1516
1517         if (CC->internal_pgm == 0) {
1518                 cprintf("%d This command is for internal programs only.\n",
1519                         ERROR + HIGHER_ACCESS_REQUIRED);
1520                 return;
1521         }
1522
1523         msgnum = extract_long(cmdbuf, 0);
1524         msg = CtdlFetchMessage(msgnum);
1525         if (msg == NULL) {
1526                 cprintf("%d Message %ld not found.\n", 
1527                         ERROR + MESSAGE_NOT_FOUND, msgnum);
1528                 return;
1529         }
1530
1531         serialize_message(&smr, msg);
1532         CtdlFreeMessage(msg);
1533
1534         if (smr.len == 0) {
1535                 cprintf("%d Unable to serialize message\n",
1536                         ERROR + INTERNAL_ERROR);
1537                 return;
1538         }
1539
1540         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1541         client_write(smr.ser, smr.len);
1542         phree(smr.ser);
1543 }
1544
1545
1546
1547 /* 
1548  * Display a message using MIME content types
1549  */
1550 void cmd_msg4(char *cmdbuf)
1551 {
1552         long msgid;
1553
1554         msgid = extract_long(cmdbuf, 0);
1555         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1556 }
1557
1558
1559
1560 /* 
1561  * Client tells us its preferred message format(s)
1562  */
1563 void cmd_msgp(char *cmdbuf)
1564 {
1565         safestrncpy(CC->preferred_formats, cmdbuf,
1566                         sizeof(CC->preferred_formats));
1567         cprintf("%d ok\n", CIT_OK);
1568 }
1569
1570
1571 /*
1572  * Open a component of a MIME message as a download file 
1573  */
1574 void cmd_opna(char *cmdbuf)
1575 {
1576         long msgid;
1577
1578         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1579
1580         msgid = extract_long(cmdbuf, 0);
1581         extract(desired_section, cmdbuf, 1);
1582
1583         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1584 }                       
1585
1586
1587 /*
1588  * Save a message pointer into a specified room
1589  * (Returns 0 for success, nonzero for failure)
1590  * roomname may be NULL to use the current room
1591  */
1592 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1593         int i;
1594         char hold_rm[ROOMNAMELEN];
1595         struct cdbdata *cdbfr;
1596         int num_msgs;
1597         long *msglist;
1598         long highest_msg = 0L;
1599         struct CtdlMessage *msg = NULL;
1600
1601         lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1602                 roomname, msgid, flags);
1603
1604         strcpy(hold_rm, CC->room.QRname);
1605
1606         /* We may need to check to see if this message is real */
1607         if (  (flags & SM_VERIFY_GOODNESS)
1608            || (flags & SM_DO_REPL_CHECK)
1609            ) {
1610                 msg = CtdlFetchMessage(msgid);
1611                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1612         }
1613
1614         /* Perform replication checks if necessary */
1615         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1616
1617                 if (getroom(&CC->room,
1618                    ((roomname != NULL) ? roomname : CC->room.QRname) )
1619                    != 0) {
1620                         lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1621                         if (msg != NULL) CtdlFreeMessage(msg);
1622                         return(ERROR + ROOM_NOT_FOUND);
1623                 }
1624
1625                 if (ReplicationChecks(msg) != 0) {
1626                         getroom(&CC->room, hold_rm);
1627                         if (msg != NULL) CtdlFreeMessage(msg);
1628                         lprintf(CTDL_DEBUG, "Did replication, and newer exists\n");
1629                         return(0);
1630                 }
1631         }
1632
1633         /* Now the regular stuff */
1634         if (lgetroom(&CC->room,
1635            ((roomname != NULL) ? roomname : CC->room.QRname) )
1636            != 0) {
1637                 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1638                 if (msg != NULL) CtdlFreeMessage(msg);
1639                 return(ERROR + ROOM_NOT_FOUND);
1640         }
1641
1642         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1643         if (cdbfr == NULL) {
1644                 msglist = NULL;
1645                 num_msgs = 0;
1646         } else {
1647                 msglist = mallok(cdbfr->len);
1648                 if (msglist == NULL)
1649                         lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1650                 num_msgs = cdbfr->len / sizeof(long);
1651                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1652                 cdb_free(cdbfr);
1653         }
1654
1655
1656         /* Make sure the message doesn't already exist in this room.  It
1657          * is absolutely taboo to have more than one reference to the same
1658          * message in a room.
1659          */
1660         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1661                 if (msglist[i] == msgid) {
1662                         lputroom(&CC->room);    /* unlock the room */
1663                         getroom(&CC->room, hold_rm);
1664                         if (msg != NULL) CtdlFreeMessage(msg);
1665                         return(ERROR + ALREADY_EXISTS);
1666                 }
1667         }
1668
1669         /* Now add the new message */
1670         ++num_msgs;
1671         msglist = reallok(msglist,
1672                           (num_msgs * sizeof(long)));
1673
1674         if (msglist == NULL) {
1675                 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1676         }
1677         msglist[num_msgs - 1] = msgid;
1678
1679         /* Sort the message list, so all the msgid's are in order */
1680         num_msgs = sort_msglist(msglist, num_msgs);
1681
1682         /* Determine the highest message number */
1683         highest_msg = msglist[num_msgs - 1];
1684
1685         /* Write it back to disk. */
1686         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long),
1687                   msglist, num_msgs * sizeof(long));
1688
1689         /* Free up the memory we used. */
1690         phree(msglist);
1691
1692         /* Update the highest-message pointer and unlock the room. */
1693         CC->room.QRhighest = highest_msg;
1694         lputroom(&CC->room);
1695         getroom(&CC->room, hold_rm);
1696
1697         /* Bump the reference count for this message. */
1698         if ((flags & SM_DONT_BUMP_REF)==0) {
1699                 AdjRefCount(msgid, +1);
1700         }
1701
1702         /* Return success. */
1703         if (msg != NULL) CtdlFreeMessage(msg);
1704         return (0);
1705 }
1706
1707
1708
1709 /*
1710  * Message base operation to send a message to the master file
1711  * (returns new message number)
1712  *
1713  * This is the back end for CtdlSubmitMsg() and should not be directly
1714  * called by server-side modules.
1715  *
1716  */
1717 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1718                 FILE *save_a_copy)              /* save a copy to disk? */
1719 {
1720         long newmsgid;
1721         long retval;
1722         char msgidbuf[SIZ];
1723         struct ser_ret smr;
1724
1725         /* Get a new message number */
1726         newmsgid = get_new_message_number();
1727         snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1728
1729         /* Generate an ID if we don't have one already */
1730         if (msg->cm_fields['I']==NULL) {
1731                 msg->cm_fields['I'] = strdoop(msgidbuf);
1732         }
1733         
1734         serialize_message(&smr, msg);
1735
1736         if (smr.len == 0) {
1737                 cprintf("%d Unable to serialize message\n",
1738                         ERROR + INTERNAL_ERROR);
1739                 return (-1L);
1740         }
1741
1742         /* Write our little bundle of joy into the message base */
1743         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1744                       smr.ser, smr.len) < 0) {
1745                 lprintf(CTDL_ERR, "Can't store message\n");
1746                 retval = 0L;
1747         } else {
1748                 retval = newmsgid;
1749         }
1750
1751         /* If the caller specified that a copy should be saved to a particular
1752          * file handle, do that now too.
1753          */
1754         if (save_a_copy != NULL) {
1755                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1756         }
1757
1758         /* Free the memory we used for the serialized message */
1759         phree(smr.ser);
1760
1761         /* Return the *local* message ID to the caller
1762          * (even if we're storing an incoming network message)
1763          */
1764         return(retval);
1765 }
1766
1767
1768
1769 /*
1770  * Serialize a struct CtdlMessage into the format used on disk and network.
1771  * 
1772  * This function loads up a "struct ser_ret" (defined in server.h) which
1773  * contains the length of the serialized message and a pointer to the
1774  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1775  */
1776 void serialize_message(struct ser_ret *ret,             /* return values */
1777                         struct CtdlMessage *msg)        /* unserialized msg */
1778 {
1779         size_t wlen;
1780         int i;
1781         static char *forder = FORDER;
1782
1783         if (is_valid_message(msg) == 0) return;         /* self check */
1784
1785         ret->len = 3;
1786         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1787                 ret->len = ret->len +
1788                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1789
1790         lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1791         ret->ser = mallok(ret->len);
1792         if (ret->ser == NULL) {
1793                 ret->len = 0;
1794                 return;
1795         }
1796
1797         ret->ser[0] = 0xFF;
1798         ret->ser[1] = msg->cm_anon_type;
1799         ret->ser[2] = msg->cm_format_type;
1800         wlen = 3;
1801
1802         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1803                 ret->ser[wlen++] = (char)forder[i];
1804                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1805                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1806         }
1807         if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1808                 (long)ret->len, (long)wlen);
1809
1810         return;
1811 }
1812
1813
1814
1815 /*
1816  * Back end for the ReplicationChecks() function
1817  */
1818 void check_repl(long msgnum, void *userdata) {
1819         struct CtdlMessage *msg;
1820         time_t timestamp = (-1L);
1821
1822         lprintf(CTDL_DEBUG, "check_repl() found message %ld\n", msgnum);
1823         msg = CtdlFetchMessage(msgnum);
1824         if (msg == NULL) return;
1825         if (msg->cm_fields['T'] != NULL) {
1826                 timestamp = atol(msg->cm_fields['T']);
1827         }
1828         CtdlFreeMessage(msg);
1829
1830         if (timestamp > msg_repl->highest) {
1831                 msg_repl->highest = timestamp;  /* newer! */
1832                 lprintf(CTDL_DEBUG, "newer!\n");
1833                 return;
1834         }
1835         lprintf(CTDL_DEBUG, "older!\n");
1836
1837         /* Existing isn't newer?  Then delete the old one(s). */
1838         CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1839 }
1840
1841
1842 /*
1843  * Check to see if any messages already exist which carry the same Extended ID
1844  * as this one.  
1845  *
1846  * If any are found:
1847  * -> With older timestamps: delete them and return 0.  Message will be saved.
1848  * -> With newer timestamps: return 1.  Message save will be aborted.
1849  */
1850 int ReplicationChecks(struct CtdlMessage *msg) {
1851         struct CtdlMessage *template;
1852         int abort_this = 0;
1853
1854         lprintf(CTDL_DEBUG, "ReplicationChecks() started\n");
1855         /* No extended id?  Don't do anything. */
1856         if (msg->cm_fields['E'] == NULL) return 0;
1857         if (strlen(msg->cm_fields['E']) == 0) return 0;
1858         lprintf(CTDL_DEBUG, "Extended ID: <%s>\n", msg->cm_fields['E']);
1859
1860         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1861         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1862         msg_repl->highest = atol(msg->cm_fields['T']);
1863
1864         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1865         memset(template, 0, sizeof(struct CtdlMessage));
1866         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1867
1868         CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1869
1870         /* If a newer message exists with the same Extended ID, abort
1871          * this save.
1872          */
1873         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1874                 abort_this = 1;
1875                 }
1876
1877         CtdlFreeMessage(template);
1878         lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1879         return(abort_this);
1880 }
1881
1882
1883
1884
1885 /*
1886  * Save a message to disk and submit it into the delivery system.
1887  */
1888 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1889                 struct recptypes *recps,        /* recipients (if mail) */
1890                 char *force                     /* force a particular room? */
1891 ) {
1892         char aaa[SIZ];
1893         char hold_rm[ROOMNAMELEN];
1894         char actual_rm[ROOMNAMELEN];
1895         char force_room[ROOMNAMELEN];
1896         char content_type[SIZ];                 /* We have to learn this */
1897         char recipient[SIZ];
1898         long newmsgid;
1899         char *mptr = NULL;
1900         struct ctdluser userbuf;
1901         int a, i;
1902         struct MetaData smi;
1903         FILE *network_fp = NULL;
1904         static int seqnum = 1;
1905         struct CtdlMessage *imsg = NULL;
1906         char *instr;
1907         struct ser_ret smr;
1908         char *hold_R, *hold_D;
1909
1910         lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1911         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1912
1913         /* If this message has no timestamp, we take the liberty of
1914          * giving it one, right now.
1915          */
1916         if (msg->cm_fields['T'] == NULL) {
1917                 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1918                 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1919                 msg->cm_fields['T'] = strdoop(aaa);
1920         }
1921
1922         /* If this message has no path, we generate one.
1923          */
1924         if (msg->cm_fields['P'] == NULL) {
1925                 lprintf(CTDL_DEBUG, "Generating path\n");
1926                 if (msg->cm_fields['A'] != NULL) {
1927                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1928                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1929                                 if (isspace(msg->cm_fields['P'][a])) {
1930                                         msg->cm_fields['P'][a] = ' ';
1931                                 }
1932                         }
1933                 }
1934                 else {
1935                         msg->cm_fields['P'] = strdoop("unknown");
1936                 }
1937         }
1938
1939         if (force == NULL) {
1940                 strcpy(force_room, "");
1941         }
1942         else {
1943                 strcpy(force_room, force);
1944         }
1945
1946         /* Learn about what's inside, because it's what's inside that counts */
1947         lprintf(CTDL_DEBUG, "Learning what's inside\n");
1948         if (msg->cm_fields['M'] == NULL) {
1949                 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1950                 return(-1);
1951         }
1952
1953         switch (msg->cm_format_type) {
1954         case 0:
1955                 strcpy(content_type, "text/x-citadel-variformat");
1956                 break;
1957         case 1:
1958                 strcpy(content_type, "text/plain");
1959                 break;
1960         case 4:
1961                 strcpy(content_type, "text/plain");
1962                 /* advance past header fields */
1963                 mptr = msg->cm_fields['M'];
1964                 a = strlen(mptr);
1965                 while ((--a) > 0) {
1966                         if (!strncasecmp(mptr, "Content-type: ", 14)) {
1967                                 safestrncpy(content_type, mptr,
1968                                             sizeof(content_type));
1969                                 strcpy(content_type, &content_type[14]);
1970                                 for (a = 0; a < strlen(content_type); ++a)
1971                                         if ((content_type[a] == ';')
1972                                             || (content_type[a] == ' ')
1973                                             || (content_type[a] == 13)
1974                                             || (content_type[a] == 10))
1975                                                 content_type[a] = 0;
1976                                 break;
1977                         }
1978                         ++mptr;
1979                 }
1980         }
1981
1982         /* Goto the correct room */
1983         lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
1984         strcpy(hold_rm, CC->room.QRname);
1985         strcpy(actual_rm, CC->room.QRname);
1986         if (recps != NULL) {
1987                 strcpy(actual_rm, SENTITEMS);
1988         }
1989
1990         /* If the user is a twit, move to the twit room for posting */
1991         lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
1992                         (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
1993         if (TWITDETECT) {
1994                 if (CC->user.axlevel == 2) {
1995                         strcpy(hold_rm, actual_rm);
1996                         strcpy(actual_rm, config.c_twitroom);
1997                 }
1998         }
1999
2000         /* ...or if this message is destined for Aide> then go there. */
2001         if (strlen(force_room) > 0) {
2002                 strcpy(actual_rm, force_room);
2003         }
2004
2005         lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2006         if (strcasecmp(actual_rm, CC->room.QRname)) {
2007                 getroom(&CC->room, actual_rm);
2008         }
2009
2010         /*
2011          * If this message has no O (room) field, generate one.
2012          */
2013         if (msg->cm_fields['O'] == NULL) {
2014                 msg->cm_fields['O'] = strdoop(CC->room.QRname);
2015         }
2016
2017         /* Perform "before save" hooks (aborting if any return nonzero) */
2018         lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2019         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
2020
2021         /* If this message has an Extended ID, perform replication checks */
2022         lprintf(CTDL_DEBUG, "Performing replication checks\n");
2023         if (ReplicationChecks(msg) > 0) return(-1);
2024
2025         /* Save it to disk */
2026         lprintf(CTDL_DEBUG, "Saving to disk\n");
2027         newmsgid = send_message(msg, NULL);
2028         if (newmsgid <= 0L) return(-1);
2029
2030         /* Write a supplemental message info record.  This doesn't have to
2031          * be a critical section because nobody else knows about this message
2032          * yet.
2033          */
2034         lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2035         memset(&smi, 0, sizeof(struct MetaData));
2036         smi.meta_msgnum = newmsgid;
2037         smi.meta_refcount = 0;
2038         safestrncpy(smi.meta_content_type, content_type, 64);
2039         PutMetaData(&smi);
2040
2041         /* Now figure out where to store the pointers */
2042         lprintf(CTDL_DEBUG, "Storing pointers\n");
2043
2044         /* If this is being done by the networker delivering a private
2045          * message, we want to BYPASS saving the sender's copy (because there
2046          * is no local sender; it would otherwise go to the Trashcan).
2047          */
2048         if ((!CC->internal_pgm) || (recps == NULL)) {
2049                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2050                         lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2051                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2052                 }
2053         }
2054
2055         /* For internet mail, drop a copy in the outbound queue room */
2056         if (recps != NULL)
2057          if (recps->num_internet > 0) {
2058                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2059         }
2060
2061         /* If other rooms are specified, drop them there too. */
2062         if (recps != NULL)
2063          if (recps->num_room > 0)
2064           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2065                 extract(recipient, recps->recp_room, i);
2066                 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2067                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2068         }
2069
2070         /* Bump this user's messages posted counter. */
2071         lprintf(CTDL_DEBUG, "Updating user\n");
2072         lgetuser(&CC->user, CC->curr_user);
2073         CC->user.posted = CC->user.posted + 1;
2074         lputuser(&CC->user);
2075
2076         /* If this is private, local mail, make a copy in the
2077          * recipient's mailbox and bump the reference count.
2078          */
2079         if (recps != NULL)
2080          if (recps->num_local > 0)
2081           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2082                 extract(recipient, recps->recp_local, i);
2083                 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2084                         recipient);
2085                 if (getuser(&userbuf, recipient) == 0) {
2086                         MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2087                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2088                         BumpNewMailCounter(userbuf.usernum);
2089                 }
2090                 else {
2091                         lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2092                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2093                 }
2094         }
2095
2096         /* Perform "after save" hooks */
2097         lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2098         PerformMessageHooks(msg, EVT_AFTERSAVE);
2099
2100         /* For IGnet mail, we have to save a new copy into the spooler for
2101          * each recipient, with the R and D fields set to the recipient and
2102          * destination-node.  This has two ugly side effects: all other
2103          * recipients end up being unlisted in this recipient's copy of the
2104          * message, and it has to deliver multiple messages to the same
2105          * node.  We'll revisit this again in a year or so when everyone has
2106          * a network spool receiver that can handle the new style messages.
2107          */
2108         if (recps != NULL)
2109          if (recps->num_ignet > 0)
2110           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2111                 extract(recipient, recps->recp_ignet, i);
2112
2113                 hold_R = msg->cm_fields['R'];
2114                 hold_D = msg->cm_fields['D'];
2115                 msg->cm_fields['R'] = mallok(SIZ);
2116                 msg->cm_fields['D'] = mallok(SIZ);
2117                 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2118                 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2119                 
2120                 serialize_message(&smr, msg);
2121                 if (smr.len > 0) {
2122                         snprintf(aaa, sizeof aaa,
2123                                 "./network/spoolin/netmail.%04lx.%04x.%04x",
2124                                 (long) getpid(), CC->cs_pid, ++seqnum);
2125                         network_fp = fopen(aaa, "wb+");
2126                         if (network_fp != NULL) {
2127                                 fwrite(smr.ser, smr.len, 1, network_fp);
2128                                 fclose(network_fp);
2129                         }
2130                         phree(smr.ser);
2131                 }
2132
2133                 phree(msg->cm_fields['R']);
2134                 phree(msg->cm_fields['D']);
2135                 msg->cm_fields['R'] = hold_R;
2136                 msg->cm_fields['D'] = hold_D;
2137         }
2138
2139         /* Go back to the room we started from */
2140         lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2141         if (strcasecmp(hold_rm, CC->room.QRname))
2142                 getroom(&CC->room, hold_rm);
2143
2144         /* For internet mail, generate delivery instructions.
2145          * Yes, this is recursive.  Deal with it.  Infinite recursion does
2146          * not happen because the delivery instructions message does not
2147          * contain a recipient.
2148          */
2149         if (recps != NULL)
2150          if (recps->num_internet > 0) {
2151                 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2152                 instr = mallok(SIZ * 2);
2153                 snprintf(instr, SIZ * 2,
2154                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2155                         "bounceto|%s@%s\n",
2156                         SPOOLMIME, newmsgid, (long)time(NULL),
2157                         msg->cm_fields['A'], msg->cm_fields['N']
2158                 );
2159
2160                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2161                         size_t tmp = strlen(instr);
2162                         extract(recipient, recps->recp_internet, i);
2163                         snprintf(&instr[tmp], SIZ * 2 - tmp,
2164                                  "remote|%s|0||\n", recipient);
2165                 }
2166
2167                 imsg = mallok(sizeof(struct CtdlMessage));
2168                 memset(imsg, 0, sizeof(struct CtdlMessage));
2169                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2170                 imsg->cm_anon_type = MES_NORMAL;
2171                 imsg->cm_format_type = FMT_RFC822;
2172                 imsg->cm_fields['A'] = strdoop("Citadel");
2173                 imsg->cm_fields['M'] = instr;
2174                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2175                 CtdlFreeMessage(imsg);
2176         }
2177
2178         return(newmsgid);
2179 }
2180
2181
2182
2183 /*
2184  * Convenience function for generating small administrative messages.
2185  */
2186 void quickie_message(char *from, char *to, char *room, char *text, 
2187                         int format_type, char *subject)
2188 {
2189         struct CtdlMessage *msg;
2190         struct recptypes *recp = NULL;
2191
2192         msg = mallok(sizeof(struct CtdlMessage));
2193         memset(msg, 0, sizeof(struct CtdlMessage));
2194         msg->cm_magic = CTDLMESSAGE_MAGIC;
2195         msg->cm_anon_type = MES_NORMAL;
2196         msg->cm_format_type = format_type;
2197         msg->cm_fields['A'] = strdoop(from);
2198         if (room != NULL) msg->cm_fields['O'] = strdoop(room);
2199         msg->cm_fields['N'] = strdoop(NODENAME);
2200         if (to != NULL) {
2201                 msg->cm_fields['R'] = strdoop(to);
2202                 recp = validate_recipients(to);
2203         }
2204         if (subject != NULL) {
2205                 msg->cm_fields['U'] = strdoop(subject);
2206         }
2207         msg->cm_fields['M'] = strdoop(text);
2208
2209         CtdlSubmitMsg(msg, recp, room);
2210         CtdlFreeMessage(msg);
2211         if (recp != NULL) phree(recp);
2212 }
2213
2214
2215
2216 /*
2217  * Back end function used by CtdlMakeMessage() and similar functions
2218  */
2219 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
2220                         size_t maxlen,          /* maximum message length */
2221                         char *exist,            /* if non-null, append to it;
2222                                                    exist is ALWAYS freed  */
2223                         int crlf                /* CRLF newlines instead of LF */
2224                         ) {
2225         char buf[SIZ];
2226         int linelen;
2227         size_t message_len = 0;
2228         size_t buffer_len = 0;
2229         char *ptr;
2230         char *m;
2231         int flushing = 0;
2232         int finished = 0;
2233
2234         if (exist == NULL) {
2235                 m = mallok(4096);
2236                 m[0] = 0;
2237                 buffer_len = 4096;
2238                 message_len = 0;
2239         }
2240         else {
2241                 message_len = strlen(exist);
2242                 buffer_len = message_len + 4096;
2243                 m = reallok(exist, buffer_len);
2244                 if (m == NULL) {
2245                         phree(exist);
2246                         return m;
2247                 }
2248         }
2249
2250         /* flush the input if we have nowhere to store it */
2251         if (m == NULL) {
2252                 flushing = 1;
2253         }
2254
2255         /* read in the lines of message text one by one */
2256         do {
2257                 if (client_gets(buf) < 1) finished = 1;
2258                 if (!strcmp(buf, terminator)) finished = 1;
2259                 if (crlf) {
2260                         strcat(buf, "\r\n");
2261                 }
2262                 else {
2263                         strcat(buf, "\n");
2264                 }
2265
2266                 if ( (!flushing) && (!finished) ) {
2267                         /* Measure the line */
2268                         linelen = strlen(buf);
2269         
2270                         /* augment the buffer if we have to */
2271                         if ((message_len + linelen) >= buffer_len) {
2272                                 ptr = reallok(m, (buffer_len * 2) );
2273                                 if (ptr == NULL) {      /* flush if can't allocate */
2274                                         flushing = 1;
2275                                 } else {
2276                                         buffer_len = (buffer_len * 2);
2277                                         m = ptr;
2278                                         lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2279                                 }
2280                         }
2281         
2282                         /* Add the new line to the buffer.  NOTE: this loop must avoid
2283                         * using functions like strcat() and strlen() because they
2284                         * traverse the entire buffer upon every call, and doing that
2285                         * for a multi-megabyte message slows it down beyond usability.
2286                         */
2287                         strcpy(&m[message_len], buf);
2288                         message_len += linelen;
2289                 }
2290
2291                 /* if we've hit the max msg length, flush the rest */
2292                 if (message_len >= maxlen) flushing = 1;
2293
2294         } while (!finished);
2295         return(m);
2296 }
2297
2298
2299
2300
2301 /*
2302  * Build a binary message to be saved on disk.
2303  * (NOTE: if you supply 'preformatted_text', the buffer you give it
2304  * will become part of the message.  This means you are no longer
2305  * responsible for managing that memory -- it will be freed along with
2306  * the rest of the fields when CtdlFreeMessage() is called.)
2307  */
2308
2309 struct CtdlMessage *CtdlMakeMessage(
2310         struct ctdluser *author,        /* author's user structure */
2311         char *recipient,                /* NULL if it's not mail */
2312         char *room,                     /* room where it's going */
2313         int type,                       /* see MES_ types in header file */
2314         int format_type,                /* variformat, plain text, MIME... */
2315         char *fake_name,                /* who we're masquerading as */
2316         char *subject,                  /* Subject (optional) */
2317         char *preformatted_text         /* ...or NULL to read text from client */
2318 ) {
2319         char dest_node[SIZ];
2320         char buf[SIZ];
2321         struct CtdlMessage *msg;
2322
2323         msg = mallok(sizeof(struct CtdlMessage));
2324         memset(msg, 0, sizeof(struct CtdlMessage));
2325         msg->cm_magic = CTDLMESSAGE_MAGIC;
2326         msg->cm_anon_type = type;
2327         msg->cm_format_type = format_type;
2328
2329         /* Don't confuse the poor folks if it's not routed mail. */
2330         strcpy(dest_node, "");
2331
2332         striplt(recipient);
2333
2334         snprintf(buf, sizeof buf, "cit%ld", author->usernum);   /* Path */
2335         msg->cm_fields['P'] = strdoop(buf);
2336
2337         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
2338         msg->cm_fields['T'] = strdoop(buf);
2339
2340         if (fake_name[0])                                       /* author */
2341                 msg->cm_fields['A'] = strdoop(fake_name);
2342         else
2343                 msg->cm_fields['A'] = strdoop(author->fullname);
2344
2345         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
2346                 msg->cm_fields['O'] = strdoop(&CC->room.QRname[11]);
2347         }
2348         else {
2349                 msg->cm_fields['O'] = strdoop(CC->room.QRname);
2350         }
2351
2352         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
2353         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
2354
2355         if (recipient[0] != 0) {
2356                 msg->cm_fields['R'] = strdoop(recipient);
2357         }
2358         if (dest_node[0] != 0) {
2359                 msg->cm_fields['D'] = strdoop(dest_node);
2360         }
2361
2362         if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2363                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2364         }
2365
2366         if (subject != NULL) {
2367                 striplt(subject);
2368                 if (strlen(subject) > 0) {
2369                         msg->cm_fields['U'] = strdoop(subject);
2370                 }
2371         }
2372
2373         if (preformatted_text != NULL) {
2374                 msg->cm_fields['M'] = preformatted_text;
2375         }
2376         else {
2377                 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2378                                         config.c_maxmsglen, NULL, 0);
2379         }
2380
2381         return(msg);
2382 }
2383
2384
2385 /*
2386  * Check to see whether we have permission to post a message in the current
2387  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2388  * returns 0 on success.
2389  */
2390 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2391
2392         if (!(CC->logged_in)) {
2393                 snprintf(errmsgbuf, n, "Not logged in.");
2394                 return (ERROR + NOT_LOGGED_IN);
2395         }
2396
2397         if ((CC->user.axlevel < 2)
2398             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2399                 snprintf(errmsgbuf, n, "Need to be validated to enter "
2400                                 "(except in %s> to sysop)", MAILROOM);
2401                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2402         }
2403
2404         if ((CC->user.axlevel < 4)
2405            && (CC->room.QRflags & QR_NETWORK)) {
2406                 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2407                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2408         }
2409
2410         if ((CC->user.axlevel < 6)
2411            && (CC->room.QRflags & QR_READONLY)) {
2412                 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2413                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2414         }
2415
2416         strcpy(errmsgbuf, "Ok");
2417         return(0);
2418 }
2419
2420
2421 /*
2422  * Check to see if the specified user has Internet mail permission
2423  * (returns nonzero if permission is granted)
2424  */
2425 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2426
2427         /* Globally enabled? */
2428         if (config.c_restrict == 0) return(1);
2429
2430         /* User flagged ok? */
2431         if (who->flags & US_INTERNET) return(2);
2432
2433         /* Aide level access? */
2434         if (who->axlevel >= 6) return(3);
2435
2436         /* No mail for you! */
2437         return(0);
2438 }
2439
2440
2441
2442 /*
2443  * Validate recipients, count delivery types and errors, and handle aliasing
2444  * FIXME check for dupes!!!!!
2445  */
2446 struct recptypes *validate_recipients(char *recipients) {
2447         struct recptypes *ret;
2448         char this_recp[SIZ];
2449         char this_recp_cooked[SIZ];
2450         char append[SIZ];
2451         int num_recps;
2452         int i, j;
2453         int mailtype;
2454         int invalid;
2455         struct ctdluser tempUS;
2456         struct ctdlroom tempQR;
2457
2458         /* Initialize */
2459         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2460         if (ret == NULL) return(NULL);
2461         memset(ret, 0, sizeof(struct recptypes));
2462
2463         ret->num_local = 0;
2464         ret->num_internet = 0;
2465         ret->num_ignet = 0;
2466         ret->num_error = 0;
2467         ret->num_room = 0;
2468
2469         if (recipients == NULL) {
2470                 num_recps = 0;
2471         }
2472         else if (strlen(recipients) == 0) {
2473                 num_recps = 0;
2474         }
2475         else {
2476                 /* Change all valid separator characters to commas */
2477                 for (i=0; i<strlen(recipients); ++i) {
2478                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2479                                 recipients[i] = ',';
2480                         }
2481                 }
2482
2483                 /* Count 'em up */
2484                 num_recps = num_tokens(recipients, ',');
2485         }
2486
2487         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2488                 extract_token(this_recp, recipients, i, ',');
2489                 striplt(this_recp);
2490                 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2491                 mailtype = alias(this_recp);
2492                 mailtype = alias(this_recp);
2493                 mailtype = alias(this_recp);
2494                 for (j=0; j<=strlen(this_recp); ++j) {
2495                         if (this_recp[j]=='_') {
2496                                 this_recp_cooked[j] = ' ';
2497                         }
2498                         else {
2499                                 this_recp_cooked[j] = this_recp[j];
2500                         }
2501                 }
2502                 invalid = 0;
2503                 switch(mailtype) {
2504                         case MES_LOCAL:
2505                                 if (!strcasecmp(this_recp, "sysop")) {
2506                                         ++ret->num_room;
2507                                         strcpy(this_recp, config.c_aideroom);
2508                                         if (strlen(ret->recp_room) > 0) {
2509                                                 strcat(ret->recp_room, "|");
2510                                         }
2511                                         strcat(ret->recp_room, this_recp);
2512                                 }
2513                                 else if (getuser(&tempUS, this_recp) == 0) {
2514                                         ++ret->num_local;
2515                                         strcpy(this_recp, tempUS.fullname);
2516                                         if (strlen(ret->recp_local) > 0) {
2517                                                 strcat(ret->recp_local, "|");
2518                                         }
2519                                         strcat(ret->recp_local, this_recp);
2520                                 }
2521                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2522                                         ++ret->num_local;
2523                                         strcpy(this_recp, tempUS.fullname);
2524                                         if (strlen(ret->recp_local) > 0) {
2525                                                 strcat(ret->recp_local, "|");
2526                                         }
2527                                         strcat(ret->recp_local, this_recp);
2528                                 }
2529                                 else if ( (!strncasecmp(this_recp, "room_", 5))
2530                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2531                                         ++ret->num_room;
2532                                         if (strlen(ret->recp_room) > 0) {
2533                                                 strcat(ret->recp_room, "|");
2534                                         }
2535                                         strcat(ret->recp_room, &this_recp_cooked[5]);
2536                                 }
2537                                 else {
2538                                         ++ret->num_error;
2539                                         invalid = 1;
2540                                 }
2541                                 break;
2542                         case MES_INTERNET:
2543                                 /* Yes, you're reading this correctly: if the target
2544                                  * domain points back to the local system or an attached
2545                                  * Citadel directory, the address is invalid.  That's
2546                                  * because if the address were valid, we would have
2547                                  * already translated it to a local address by now.
2548                                  */
2549                                 if (IsDirectory(this_recp)) {
2550                                         ++ret->num_error;
2551                                         invalid = 1;
2552                                 }
2553                                 else {
2554                                         ++ret->num_internet;
2555                                         if (strlen(ret->recp_internet) > 0) {
2556                                                 strcat(ret->recp_internet, "|");
2557                                         }
2558                                         strcat(ret->recp_internet, this_recp);
2559                                 }
2560                                 break;
2561                         case MES_IGNET:
2562                                 ++ret->num_ignet;
2563                                 if (strlen(ret->recp_ignet) > 0) {
2564                                         strcat(ret->recp_ignet, "|");
2565                                 }
2566                                 strcat(ret->recp_ignet, this_recp);
2567                                 break;
2568                         case MES_ERROR:
2569                                 ++ret->num_error;
2570                                 invalid = 1;
2571                                 break;
2572                 }
2573                 if (invalid) {
2574                         if (strlen(ret->errormsg) == 0) {
2575                                 snprintf(append, sizeof append,
2576                                          "Invalid recipient: %s",
2577                                          this_recp);
2578                         }
2579                         else {
2580                                 snprintf(append, sizeof append,
2581                                          ", %s", this_recp);
2582                         }
2583                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2584                                 strcat(ret->errormsg, append);
2585                         }
2586                 }
2587                 else {
2588                         if (strlen(ret->display_recp) == 0) {
2589                                 strcpy(append, this_recp);
2590                         }
2591                         else {
2592                                 snprintf(append, sizeof append, ", %s",
2593                                          this_recp);
2594                         }
2595                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2596                                 strcat(ret->display_recp, append);
2597                         }
2598                 }
2599         }
2600
2601         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2602            ret->num_room + ret->num_error) == 0) {
2603                 ++ret->num_error;
2604                 strcpy(ret->errormsg, "No recipients specified.");
2605         }
2606
2607         lprintf(CTDL_DEBUG, "validate_recipients()\n");
2608         lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2609         lprintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2610         lprintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2611         lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2612         lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2613
2614         return(ret);
2615 }
2616
2617
2618
2619 /*
2620  * message entry  -  mode 0 (normal)
2621  */
2622 void cmd_ent0(char *entargs)
2623 {
2624         int post = 0;
2625         char recp[SIZ];
2626         char masquerade_as[SIZ];
2627         int anon_flag = 0;
2628         int format_type = 0;
2629         char newusername[SIZ];
2630         struct CtdlMessage *msg;
2631         int anonymous = 0;
2632         char errmsg[SIZ];
2633         int err = 0;
2634         struct recptypes *valid = NULL;
2635         char subject[SIZ];
2636
2637         post = extract_int(entargs, 0);
2638         extract(recp, entargs, 1);
2639         anon_flag = extract_int(entargs, 2);
2640         format_type = extract_int(entargs, 3);
2641         extract(subject, entargs, 4);
2642
2643         /* first check to make sure the request is valid. */
2644
2645         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2646         if (err) {
2647                 cprintf("%d %s\n", err, errmsg);
2648                 return;
2649         }
2650
2651         /* Check some other permission type things. */
2652
2653         if (post == 2) {
2654                 if (CC->user.axlevel < 6) {
2655                         cprintf("%d You don't have permission to masquerade.\n",
2656                                 ERROR + HIGHER_ACCESS_REQUIRED);
2657                         return;
2658                 }
2659                 extract(newusername, entargs, 5);
2660                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2661                 safestrncpy(CC->fake_postname, newusername,
2662                         sizeof(CC->fake_postname) );
2663                 cprintf("%d ok\n", CIT_OK);
2664                 return;
2665         }
2666         CC->cs_flags |= CS_POSTING;
2667
2668         /* In the Mail> room we have to behave a little differently --
2669          * make sure the user has specified at least one recipient.  Then
2670          * validate the recipient(s).
2671          */
2672         if ( (CC->room.QRflags & QR_MAILBOX)
2673            && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2674
2675                 if (CC->user.axlevel < 2) {
2676                         strcpy(recp, "sysop");
2677                 }
2678
2679                 valid = validate_recipients(recp);
2680                 if (valid->num_error > 0) {
2681                         cprintf("%d %s\n",
2682                                 ERROR + NO_SUCH_USER, valid->errormsg);
2683                         phree(valid);
2684                         return;
2685                 }
2686                 if (valid->num_internet > 0) {
2687                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2688                                 cprintf("%d You do not have permission "
2689                                         "to send Internet mail.\n",
2690                                         ERROR + HIGHER_ACCESS_REQUIRED);
2691                                 phree(valid);
2692                                 return;
2693                         }
2694                 }
2695
2696                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2697                    && (CC->user.axlevel < 4) ) {
2698                         cprintf("%d Higher access required for network mail.\n",
2699                                 ERROR + HIGHER_ACCESS_REQUIRED);
2700                         phree(valid);
2701                         return;
2702                 }
2703         
2704                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2705                     && ((CC->user.flags & US_INTERNET) == 0)
2706                     && (!CC->internal_pgm)) {
2707                         cprintf("%d You don't have access to Internet mail.\n",
2708                                 ERROR + HIGHER_ACCESS_REQUIRED);
2709                         phree(valid);
2710                         return;
2711                 }
2712
2713         }
2714
2715         /* Is this a room which has anonymous-only or anonymous-option? */
2716         anonymous = MES_NORMAL;
2717         if (CC->room.QRflags & QR_ANONONLY) {
2718                 anonymous = MES_ANONONLY;
2719         }
2720         if (CC->room.QRflags & QR_ANONOPT) {
2721                 if (anon_flag == 1) {   /* only if the user requested it */
2722                         anonymous = MES_ANONOPT;
2723                 }
2724         }
2725
2726         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2727                 recp[0] = 0;
2728         }
2729
2730         /* If we're only checking the validity of the request, return
2731          * success without creating the message.
2732          */
2733         if (post == 0) {
2734                 cprintf("%d %s\n", CIT_OK,
2735                         ((valid != NULL) ? valid->display_recp : "") );
2736                 phree(valid);
2737                 return;
2738         }
2739
2740         /* Handle author masquerading */
2741         if (CC->fake_postname[0]) {
2742                 strcpy(masquerade_as, CC->fake_postname);
2743         }
2744         else if (CC->fake_username[0]) {
2745                 strcpy(masquerade_as, CC->fake_username);
2746         }
2747         else {
2748                 strcpy(masquerade_as, "");
2749         }
2750
2751         /* Read in the message from the client. */
2752         cprintf("%d send message\n", SEND_LISTING);
2753         msg = CtdlMakeMessage(&CC->user, recp,
2754                 CC->room.QRname, anonymous, format_type,
2755                 masquerade_as, subject, NULL);
2756
2757         if (msg != NULL) {
2758                 CtdlSubmitMsg(msg, valid, "");
2759                 CtdlFreeMessage(msg);
2760         }
2761         CC->fake_postname[0] = '\0';
2762         phree(valid);
2763         return;
2764 }
2765
2766
2767
2768 /*
2769  * API function to delete messages which match a set of criteria
2770  * (returns the actual number of messages deleted)
2771  */
2772 int CtdlDeleteMessages(char *room_name,         /* which room */
2773                        long dmsgnum,            /* or "0" for any */
2774                        char *content_type       /* or "" for any */
2775 )
2776 {
2777
2778         struct ctdlroom qrbuf;
2779         struct cdbdata *cdbfr;
2780         long *msglist = NULL;
2781         long *dellist = NULL;
2782         int num_msgs = 0;
2783         int i;
2784         int num_deleted = 0;
2785         int delete_this;
2786         struct MetaData smi;
2787
2788         lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2789                 room_name, dmsgnum, content_type);
2790
2791         /* get room record, obtaining a lock... */
2792         if (lgetroom(&qrbuf, room_name) != 0) {
2793                 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2794                         room_name);
2795                 return (0);     /* room not found */
2796         }
2797         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2798
2799         if (cdbfr != NULL) {
2800                 msglist = mallok(cdbfr->len);
2801                 dellist = mallok(cdbfr->len);
2802                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2803                 num_msgs = cdbfr->len / sizeof(long);
2804                 cdb_free(cdbfr);
2805         }
2806         if (num_msgs > 0) {
2807                 for (i = 0; i < num_msgs; ++i) {
2808                         delete_this = 0x00;
2809
2810                         /* Set/clear a bit for each criterion */
2811
2812                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2813                                 delete_this |= 0x01;
2814                         }
2815                         if (strlen(content_type) == 0) {
2816                                 delete_this |= 0x02;
2817                         } else {
2818                                 GetMetaData(&smi, msglist[i]);
2819                                 if (!strcasecmp(smi.meta_content_type,
2820                                                 content_type)) {
2821                                         delete_this |= 0x02;
2822                                 }
2823                         }
2824
2825                         /* Delete message only if all bits are set */
2826                         if (delete_this == 0x03) {
2827                                 dellist[num_deleted++] = msglist[i];
2828                                 msglist[i] = 0L;
2829                         }
2830                 }
2831
2832                 num_msgs = sort_msglist(msglist, num_msgs);
2833                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2834                           msglist, (num_msgs * sizeof(long)));
2835
2836                 qrbuf.QRhighest = msglist[num_msgs - 1];
2837         }
2838         lputroom(&qrbuf);
2839
2840         /* Go through the messages we pulled out of the index, and decrement
2841          * their reference counts by 1.  If this is the only room the message
2842          * was in, the reference count will reach zero and the message will
2843          * automatically be deleted from the database.  We do this in a
2844          * separate pass because there might be plug-in hooks getting called,
2845          * and we don't want that happening during an S_ROOMS critical
2846          * section.
2847          */
2848         if (num_deleted) for (i=0; i<num_deleted; ++i) {
2849                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2850                 AdjRefCount(dellist[i], -1);
2851         }
2852
2853         /* Now free the memory we used, and go away. */
2854         if (msglist != NULL) phree(msglist);
2855         if (dellist != NULL) phree(dellist);
2856         lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2857         return (num_deleted);
2858 }
2859
2860
2861
2862 /*
2863  * Check whether the current user has permission to delete messages from
2864  * the current room (returns 1 for yes, 0 for no)
2865  */
2866 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2867         getuser(&CC->user, CC->curr_user);
2868         if ((CC->user.axlevel < 6)
2869             && (CC->user.usernum != CC->room.QRroomaide)
2870             && ((CC->room.QRflags & QR_MAILBOX) == 0)
2871             && (!(CC->internal_pgm))) {
2872                 return(0);
2873         }
2874         return(1);
2875 }
2876
2877
2878
2879 /*
2880  * Delete message from current room
2881  */
2882 void cmd_dele(char *delstr)
2883 {
2884         long delnum;
2885         int num_deleted;
2886
2887         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2888                 cprintf("%d Higher access required.\n",
2889                         ERROR + HIGHER_ACCESS_REQUIRED);
2890                 return;
2891         }
2892         delnum = extract_long(delstr, 0);
2893
2894         num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2895
2896         if (num_deleted) {
2897                 cprintf("%d %d message%s deleted.\n", CIT_OK,
2898                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2899         } else {
2900                 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2901         }
2902 }
2903
2904
2905 /*
2906  * Back end API function for moves and deletes
2907  */
2908 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2909         int err;
2910
2911         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2912                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2913         if (err != 0) return(err);
2914
2915         return(0);
2916 }
2917
2918
2919
2920 /*
2921  * move or copy a message to another room
2922  */
2923 void cmd_move(char *args)
2924 {
2925         long num;
2926         char targ[SIZ];
2927         struct ctdlroom qtemp;
2928         int err;
2929         int is_copy = 0;
2930         int ra;
2931
2932         num = extract_long(args, 0);
2933         extract(targ, args, 1);
2934         targ[ROOMNAMELEN - 1] = 0;
2935         is_copy = extract_int(args, 2);
2936
2937         if (getroom(&qtemp, targ) != 0) {
2938                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
2939                 return;
2940         }
2941
2942         getuser(&CC->user, CC->curr_user);
2943         ra = CtdlRoomAccess(&qtemp, &CC->user);
2944         /* Aides can move/copy */
2945         if ((CC->user.axlevel < 6)
2946             /* Roomaides can move/copy */
2947             && (CC->user.usernum != CC->room.QRroomaide)
2948             /* Permit move/copy from personal rooms */
2949             && (!((CC->room.QRflags & QR_MAILBOX)
2950                             && (qtemp.QRflags & QR_MAILBOX)))
2951             /* Permit only copy from public to personal room */
2952             && (!(is_copy && (CC->room.QRflags & QR_MAILBOX)
2953                             || (qtemp.QRflags & QR_MAILBOX)))
2954             /* User must have access to target room */
2955             && !((ra & UA_KNOWN))) {
2956                 cprintf("%d Higher access required.\n",
2957                         ERROR + HIGHER_ACCESS_REQUIRED);
2958                 return;
2959         }
2960
2961         err = CtdlCopyMsgToRoom(num, targ);
2962         if (err != 0) {
2963                 cprintf("%d Cannot store message in %s: error %d\n",
2964                         err, targ, err);
2965                 return;
2966         }
2967
2968         /* Now delete the message from the source room,
2969          * if this is a 'move' rather than a 'copy' operation.
2970          */
2971         if (is_copy == 0) {
2972                 CtdlDeleteMessages(CC->room.QRname, num, "");
2973         }
2974
2975         cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2976 }
2977
2978
2979
2980 /*
2981  * GetMetaData()  -  Get the supplementary record for a message
2982  */
2983 void GetMetaData(struct MetaData *smibuf, long msgnum)
2984 {
2985
2986         struct cdbdata *cdbsmi;
2987         long TheIndex;
2988
2989         memset(smibuf, 0, sizeof(struct MetaData));
2990         smibuf->meta_msgnum = msgnum;
2991         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2992
2993         /* Use the negative of the message number for its supp record index */
2994         TheIndex = (0L - msgnum);
2995
2996         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2997         if (cdbsmi == NULL) {
2998                 return;         /* record not found; go with defaults */
2999         }
3000         memcpy(smibuf, cdbsmi->ptr,
3001                ((cdbsmi->len > sizeof(struct MetaData)) ?
3002                 sizeof(struct MetaData) : cdbsmi->len));
3003         cdb_free(cdbsmi);
3004         return;
3005 }
3006
3007
3008 /*
3009  * PutMetaData()  -  (re)write supplementary record for a message
3010  */
3011 void PutMetaData(struct MetaData *smibuf)
3012 {
3013         long TheIndex;
3014
3015         /* Use the negative of the message number for the metadata db index */
3016         TheIndex = (0L - smibuf->meta_msgnum);
3017
3018         lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3019                 smibuf->meta_msgnum, smibuf->meta_refcount);
3020
3021         cdb_store(CDB_MSGMAIN,
3022                   &TheIndex, sizeof(long),
3023                   smibuf, sizeof(struct MetaData));
3024
3025 }
3026
3027 /*
3028  * AdjRefCount  -  change the reference count for a message;
3029  *                 delete the message if it reaches zero
3030  */
3031 void AdjRefCount(long msgnum, int incr)
3032 {
3033
3034         struct MetaData smi;
3035         long delnum;
3036
3037         /* This is a *tight* critical section; please keep it that way, as
3038          * it may get called while nested in other critical sections.  
3039          * Complicating this any further will surely cause deadlock!
3040          */
3041         begin_critical_section(S_SUPPMSGMAIN);
3042         GetMetaData(&smi, msgnum);
3043         lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3044                 msgnum, smi.meta_refcount);
3045         smi.meta_refcount += incr;
3046         PutMetaData(&smi);
3047         end_critical_section(S_SUPPMSGMAIN);
3048         lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3049                 msgnum, smi.meta_refcount);
3050
3051         /* If the reference count is now zero, delete the message
3052          * (and its supplementary record as well).
3053          */
3054         if (smi.meta_refcount == 0) {
3055                 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3056                 delnum = msgnum;
3057                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3058
3059                 /* We have to delete the metadata record too! */
3060                 delnum = (0L - msgnum);
3061                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3062         }
3063 }
3064
3065 /*
3066  * Write a generic object to this room
3067  *
3068  * Note: this could be much more efficient.  Right now we use two temporary
3069  * files, and still pull the message into memory as with all others.
3070  */
3071 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
3072                         char *content_type,     /* MIME type of this object */
3073                         char *tempfilename,     /* Where to fetch it from */
3074                         struct ctdluser *is_mailbox,    /* Mailbox room? */
3075                         int is_binary,          /* Is encoding necessary? */
3076                         int is_unique,          /* Del others of this type? */
3077                         unsigned int flags      /* Internal save flags */
3078                         )
3079 {
3080
3081         FILE *fp;
3082         struct ctdlroom qrbuf;
3083         char roomname[ROOMNAMELEN];
3084         struct CtdlMessage *msg;
3085
3086         char *raw_message = NULL;
3087         char *encoded_message = NULL;
3088         off_t raw_length = 0;
3089
3090         if (is_mailbox != NULL)
3091                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3092         else
3093                 safestrncpy(roomname, req_room, sizeof(roomname));
3094         lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3095
3096
3097         fp = fopen(tempfilename, "rb");
3098         if (fp == NULL) {
3099                 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3100                         tempfilename, strerror(errno));
3101                 return;
3102         }
3103         fseek(fp, 0L, SEEK_END);
3104         raw_length = ftell(fp);
3105         rewind(fp);
3106         lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3107
3108         raw_message = mallok((size_t)raw_length + 2);
3109         fread(raw_message, (size_t)raw_length, 1, fp);
3110         fclose(fp);
3111
3112         if (is_binary) {
3113                 encoded_message = mallok((size_t)
3114                         (((raw_length * 134) / 100) + 4096 ) );
3115         }
3116         else {
3117                 encoded_message = mallok((size_t)(raw_length + 4096));
3118         }
3119
3120         sprintf(encoded_message, "Content-type: %s\n", content_type);
3121
3122         if (is_binary) {
3123                 sprintf(&encoded_message[strlen(encoded_message)],
3124                         "Content-transfer-encoding: base64\n\n"
3125                 );
3126         }
3127         else {
3128                 sprintf(&encoded_message[strlen(encoded_message)],
3129                         "Content-transfer-encoding: 7bit\n\n"
3130                 );
3131         }
3132
3133         if (is_binary) {
3134                 CtdlEncodeBase64(
3135                         &encoded_message[strlen(encoded_message)],
3136                         raw_message,
3137                         (int)raw_length
3138                 );
3139         }
3140         else {
3141                 raw_message[raw_length] = 0;
3142                 memcpy(
3143                         &encoded_message[strlen(encoded_message)],
3144                         raw_message,
3145                         (int)(raw_length+1)
3146                 );
3147         }
3148
3149         phree(raw_message);
3150
3151         lprintf(CTDL_DEBUG, "Allocating\n");
3152         msg = mallok(sizeof(struct CtdlMessage));
3153         memset(msg, 0, sizeof(struct CtdlMessage));
3154         msg->cm_magic = CTDLMESSAGE_MAGIC;
3155         msg->cm_anon_type = MES_NORMAL;
3156         msg->cm_format_type = 4;
3157         msg->cm_fields['A'] = strdoop(CC->user.fullname);
3158         msg->cm_fields['O'] = strdoop(req_room);
3159         msg->cm_fields['N'] = strdoop(config.c_nodename);
3160         msg->cm_fields['H'] = strdoop(config.c_humannode);
3161         msg->cm_flags = flags;
3162         
3163         msg->cm_fields['M'] = encoded_message;
3164
3165         /* Create the requested room if we have to. */
3166         if (getroom(&qrbuf, roomname) != 0) {
3167                 create_room(roomname, 
3168                         ( (is_mailbox != NULL) ? 5 : 3 ),
3169                         "", 0, 1, 0);
3170         }
3171         /* If the caller specified this object as unique, delete all
3172          * other objects of this type that are currently in the room.
3173          */
3174         if (is_unique) {
3175                 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3176                         CtdlDeleteMessages(roomname, 0L, content_type));
3177         }
3178         /* Now write the data */
3179         CtdlSubmitMsg(msg, NULL, roomname);
3180         CtdlFreeMessage(msg);
3181 }
3182
3183
3184
3185
3186
3187
3188 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3189         config_msgnum = msgnum;
3190 }
3191
3192
3193 char *CtdlGetSysConfig(char *sysconfname) {
3194         char hold_rm[ROOMNAMELEN];
3195         long msgnum;
3196         char *conf;
3197         struct CtdlMessage *msg;
3198         char buf[SIZ];
3199         
3200         strcpy(hold_rm, CC->room.QRname);
3201         if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3202                 getroom(&CC->room, hold_rm);
3203                 return NULL;
3204         }
3205
3206
3207         /* We want the last (and probably only) config in this room */
3208         begin_critical_section(S_CONFIG);
3209         config_msgnum = (-1L);
3210         CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3211                 CtdlGetSysConfigBackend, NULL);
3212         msgnum = config_msgnum;
3213         end_critical_section(S_CONFIG);
3214
3215         if (msgnum < 0L) {
3216                 conf = NULL;
3217         }
3218         else {
3219                 msg = CtdlFetchMessage(msgnum);
3220                 if (msg != NULL) {
3221                         conf = strdoop(msg->cm_fields['M']);
3222                         CtdlFreeMessage(msg);
3223                 }
3224                 else {
3225                         conf = NULL;
3226                 }
3227         }
3228
3229         getroom(&CC->room, hold_rm);
3230
3231         if (conf != NULL) do {
3232                 extract_token(buf, conf, 0, '\n');
3233                 strcpy(conf, &conf[strlen(buf)+1]);
3234         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3235
3236         return(conf);
3237 }
3238
3239 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3240         char temp[PATH_MAX];
3241         FILE *fp;
3242
3243         strcpy(temp, tmpnam(NULL));
3244
3245         fp = fopen(temp, "w");
3246         if (fp == NULL) return;
3247         fprintf(fp, "%s", sysconfdata);
3248         fclose(fp);
3249
3250         /* this handy API function does all the work for us */
3251         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3252         unlink(temp);
3253 }
3254
3255
3256 /*
3257  * Determine whether a given Internet address belongs to the current user
3258  */
3259 int CtdlIsMe(char *addr) {
3260         struct recptypes *recp;
3261         int i;
3262
3263         recp = validate_recipients(addr);
3264         if (recp == NULL) return(0);
3265
3266         if (recp->num_local == 0) {
3267                 phree(recp);
3268                 return(0);
3269         }
3270
3271         for (i=0; i<recp->num_local; ++i) {
3272                 extract(addr, recp->recp_local, i);
3273                 if (!strcasecmp(addr, CC->user.fullname)) {
3274                         phree(recp);
3275                         return(1);
3276                 }
3277         }
3278
3279         phree(recp);
3280         return(0);
3281 }
3282
3283
3284 /*
3285  * Citadel protocol command to do the same
3286  */
3287 void cmd_isme(char *argbuf) {
3288         char addr[SIZ];
3289
3290         if (CtdlAccessCheck(ac_logged_in)) return;
3291         extract(addr, argbuf, 0);
3292
3293         if (CtdlIsMe(addr)) {
3294                 cprintf("%d %s\n", CIT_OK, addr);
3295         }
3296         else {
3297                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
3298         }
3299
3300 }