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