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