]> code.citadel.org Git - citadel.git/blob - citadel/msgbase.c
* Tweaks to msgbase.c and imap_fetch.c to fix slightly incorrect byte counts
[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         int subject_found = 0;
1100
1101         /* buffers needed for RFC822 translation */
1102         char suser[SIZ];
1103         char luser[SIZ];
1104         char fuser[SIZ];
1105         char snode[SIZ];
1106         char lnode[SIZ];
1107         char mid[SIZ];
1108         char datestamp[SIZ];
1109         /*                                       */
1110
1111         snprintf(mid, sizeof mid, "%ld", msg_num);
1112         nl = (crlf ? "\r\n" : "\n");
1113
1114         if (!is_valid_message(TheMessage)) {
1115                 lprintf(1, "ERROR: invalid preloaded message for output\n");
1116                 return(om_no_such_msg);
1117         }
1118
1119         /* Are we downloading a MIME component? */
1120         if (mode == MT_DOWNLOAD) {
1121                 if (TheMessage->cm_format_type != FMT_RFC822) {
1122                         if (do_proto)
1123                                 cprintf("%d This is not a MIME message.\n",
1124                                 ERROR);
1125                 } else if (CC->download_fp != NULL) {
1126                         if (do_proto) cprintf(
1127                                 "%d You already have a download open.\n",
1128                                 ERROR);
1129                 } else {
1130                         /* Parse the message text component */
1131                         mptr = TheMessage->cm_fields['M'];
1132                         mime_parser(mptr, NULL,
1133                                 *mime_download, NULL, NULL,
1134                                 NULL, 0);
1135                         /* If there's no file open by this time, the requested
1136                          * section wasn't found, so print an error
1137                          */
1138                         if (CC->download_fp == NULL) {
1139                                 if (do_proto) cprintf(
1140                                         "%d Section %s not found.\n",
1141                                         ERROR + FILE_NOT_FOUND,
1142                                         desired_section);
1143                         }
1144                 }
1145                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1146         }
1147
1148         /* now for the user-mode message reading loops */
1149         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1150
1151         /* Does the caller want to skip the headers? */
1152         if (headers_only == HEADERS_NONE) goto START_TEXT;
1153
1154         /* Tell the client which format type we're using. */
1155         if ( (mode == MT_CITADEL) && (do_proto) ) {
1156                 cprintf("type=%d\n", TheMessage->cm_format_type);
1157         }
1158
1159         /* nhdr=yes means that we're only displaying headers, no body */
1160         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1161            && (mode == MT_CITADEL)
1162            && (do_proto)
1163            ) {
1164                 cprintf("nhdr=yes\n");
1165         }
1166
1167         /* begin header processing loop for Citadel message format */
1168
1169         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1170
1171                 strcpy(display_name, "<unknown>");
1172                 if (TheMessage->cm_fields['A']) {
1173                         strcpy(buf, TheMessage->cm_fields['A']);
1174                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1175                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1176                                 strcpy(display_name, "****");
1177                         }
1178                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1179                                 strcpy(display_name, "anonymous");
1180                         }
1181                         else {
1182                                 strcpy(display_name, buf);
1183                         }
1184                         if ((is_room_aide())
1185                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1186                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1187                                 size_t tmp = strlen(display_name);
1188                                 snprintf(&display_name[tmp],
1189                                          sizeof display_name - tmp,
1190                                          " [%s]", buf);
1191                         }
1192                 }
1193
1194                 /* Don't show Internet address for users on the
1195                  * local Citadel network.
1196                  */
1197                 suppress_f = 0;
1198                 if (TheMessage->cm_fields['N'] != NULL)
1199                    if (strlen(TheMessage->cm_fields['N']) > 0)
1200                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1201                         suppress_f = 1;
1202                 }
1203                 
1204                 /* Now spew the header fields in the order we like them. */
1205                 strcpy(allkeys, FORDER);
1206                 for (i=0; i<strlen(allkeys); ++i) {
1207                         k = (int) allkeys[i];
1208                         if (k != 'M') {
1209                                 if ( (TheMessage->cm_fields[k] != NULL)
1210                                    && (msgkeys[k] != NULL) ) {
1211                                         if (k == 'A') {
1212                                                 if (do_proto) cprintf("%s=%s\n",
1213                                                         msgkeys[k],
1214                                                         display_name);
1215                                         }
1216                                         else if ((k == 'F') && (suppress_f)) {
1217                                                 /* do nothing */
1218                                         }
1219                                         /* Masquerade display name if needed */
1220                                         else {
1221                                                 if (do_proto) cprintf("%s=%s\n",
1222                                                         msgkeys[k],
1223                                                         TheMessage->cm_fields[k]
1224                                         );
1225                                         }
1226                                 }
1227                         }
1228                 }
1229
1230         }
1231
1232         /* begin header processing loop for RFC822 transfer format */
1233
1234         strcpy(suser, "");
1235         strcpy(luser, "");
1236         strcpy(fuser, "");
1237         strcpy(snode, NODENAME);
1238         strcpy(lnode, HUMANNODE);
1239         if (mode == MT_RFC822) {
1240                 cprintf("X-UIDL: %ld%s", msg_num, nl);
1241                 for (i = 0; i < 256; ++i) {
1242                         if (TheMessage->cm_fields[i]) {
1243                                 mptr = TheMessage->cm_fields[i];
1244
1245                                 if (i == 'A') {
1246                                         strcpy(luser, mptr);
1247                                         strcpy(suser, mptr);
1248                                 }
1249 /****
1250  "Path:" removed for now because it confuses brain-dead Microsoft shitware
1251  into thinking that mail messages are newsgroup messages instead.  When we
1252  add NNTP support back into Citadel we'll have to add code to only output
1253  this field when appropriate.
1254                                 else if (i == 'P') {
1255                                         cprintf("Path: %s%s", mptr, nl);
1256                                 }
1257  ****/
1258                                 else if (i == 'U') {
1259                                         cprintf("Subject: %s%s", mptr, nl);
1260                                         subject_found = 1;
1261                                 }
1262                                 else if (i == 'I')
1263                                         safestrncpy(mid, mptr, sizeof mid);
1264                                 else if (i == 'H')
1265                                         safestrncpy(lnode, mptr, sizeof lnode);
1266                                 else if (i == 'F')
1267                                         safestrncpy(fuser, mptr, sizeof fuser);
1268                                 else if (i == 'O')
1269                                         cprintf("X-Citadel-Room: %s%s",
1270                                                 mptr, nl);
1271                                 else if (i == 'N')
1272                                         safestrncpy(snode, mptr, sizeof snode);
1273                                 else if (i == 'R')
1274                                         cprintf("To: %s%s", mptr, nl);
1275                                 else if (i == 'T') {
1276                                         datestring(datestamp, sizeof datestamp,
1277                                                 atol(mptr), DATESTRING_RFC822);
1278                                         cprintf("Date: %s%s", datestamp, nl);
1279                                 }
1280                         }
1281                 }
1282                 if (subject_found == 0) {
1283                         cprintf("Subject: (no subject)%s", nl);
1284                 }
1285         }
1286
1287         for (i=0; i<strlen(suser); ++i) {
1288                 suser[i] = tolower(suser[i]);
1289                 if (!isalnum(suser[i])) suser[i]='_';
1290         }
1291
1292         if (mode == MT_RFC822) {
1293                 if (!strcasecmp(snode, NODENAME)) {
1294                         strcpy(snode, FQDN);
1295                 }
1296
1297                 /* Construct a fun message id */
1298                 cprintf("Message-ID: <%s", mid);
1299                 if (strchr(mid, '@')==NULL) {
1300                         cprintf("@%s", snode);
1301                 }
1302                 cprintf(">%s", nl);
1303
1304                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1305
1306                 if (strlen(fuser) > 0) {
1307                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1308                 }
1309                 else {
1310                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1311                 }
1312
1313                 cprintf("Organization: %s%s", lnode, nl);
1314
1315                 /* Blank line signifying RFC822 end-of-headers */
1316                 cprintf("%s", nl);
1317         }
1318
1319         /* end header processing loop ... at this point, we're in the text */
1320 START_TEXT:
1321         mptr = TheMessage->cm_fields['M'];
1322
1323         /* Tell the client about the MIME parts in this message */
1324         if (TheMessage->cm_format_type == FMT_RFC822) {
1325                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1326                         mime_parser(mptr, NULL,
1327                                 *list_this_part,
1328                                 *list_this_pref,
1329                                 *list_this_suff,
1330                                 NULL, 0);
1331                 }
1332                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1333                         /* FIXME ... we have to put some code in here to avoid
1334                          * printing duplicate header information when both
1335                          * Citadel and RFC822 headers exist.  Preference should
1336                          * probably be given to the RFC822 headers.
1337                          */
1338                         while (ch=*(mptr++), ch!=0) {
1339                                 if (ch==13) ;
1340                                 else if (ch==10) cprintf("%s", nl);
1341                                 else cprintf("%c", ch);
1342                         }
1343                         if (do_proto) cprintf("000\n");
1344                         return(om_ok);
1345                 }
1346         }
1347
1348         if (headers_only == HEADERS_ONLY) {
1349                 if (do_proto) cprintf("000\n");
1350                 return(om_ok);
1351         }
1352
1353         /* signify start of msg text */
1354         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1355                 if (do_proto) cprintf("text\n");
1356         }
1357
1358         /* If the format type on disk is 1 (fixed-format), then we want
1359          * everything to be output completely literally ... regardless of
1360          * what message transfer format is in use.
1361          */
1362         if (TheMessage->cm_format_type == FMT_FIXED) {
1363                 if (mode == MT_MIME) {
1364                         cprintf("Content-type: text/plain\n\n");
1365                 }
1366                 strcpy(buf, "");
1367                 while (ch = *mptr++, ch > 0) {
1368                         if (ch == 13)
1369                                 ch = 10;
1370                         if ((ch == 10) || (strlen(buf) > 250)) {
1371                                 cprintf("%s%s", buf, nl);
1372                                 strcpy(buf, "");
1373                         } else {
1374                                 buf[strlen(buf) + 1] = 0;
1375                                 buf[strlen(buf)] = ch;
1376                         }
1377                 }
1378                 if (strlen(buf) > 0)
1379                         cprintf("%s%s", buf, nl);
1380         }
1381
1382         /* If the message on disk is format 0 (Citadel vari-format), we
1383          * output using the formatter at 80 columns.  This is the final output
1384          * form if the transfer format is RFC822, but if the transfer format
1385          * is Citadel proprietary, it'll still work, because the indentation
1386          * for new paragraphs is correct and the client will reformat the
1387          * message to the reader's screen width.
1388          */
1389         if (TheMessage->cm_format_type == FMT_CITADEL) {
1390                 if (mode == MT_MIME) {
1391                         cprintf("Content-type: text/x-citadel-variformat\n\n");
1392                 }
1393                 memfmout(80, mptr, 0, nl);
1394         }
1395
1396         /* If the message on disk is format 4 (MIME), we've gotta hand it
1397          * off to the MIME parser.  The client has already been told that
1398          * this message is format 1 (fixed format), so the callback function
1399          * we use will display those parts as-is.
1400          */
1401         if (TheMessage->cm_format_type == FMT_RFC822) {
1402                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1403                 memset(ma, 0, sizeof(struct ma_info));
1404
1405                 if (mode == MT_MIME) {
1406                         strcpy(ma->chosen_part, "1");
1407                         mime_parser(mptr, NULL,
1408                                 *choose_preferred, *fixed_output_pre,
1409                                 *fixed_output_post, NULL, 0);
1410                         mime_parser(mptr, NULL,
1411                                 *output_preferred, NULL, NULL, NULL, 0);
1412                 }
1413                 else {
1414                         mime_parser(mptr, NULL,
1415                                 *fixed_output, *fixed_output_pre,
1416                                 *fixed_output_post, NULL, 0);
1417                 }
1418         }
1419
1420         /* now we're done */
1421         if (do_proto) cprintf("000\n");
1422         return(om_ok);
1423 }
1424
1425
1426
1427 /*
1428  * display a message (mode 0 - Citadel proprietary)
1429  */
1430 void cmd_msg0(char *cmdbuf)
1431 {
1432         long msgid;
1433         int headers_only = HEADERS_ALL;
1434
1435         msgid = extract_long(cmdbuf, 0);
1436         headers_only = extract_int(cmdbuf, 1);
1437
1438         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1439         return;
1440 }
1441
1442
1443 /*
1444  * display a message (mode 2 - RFC822)
1445  */
1446 void cmd_msg2(char *cmdbuf)
1447 {
1448         long msgid;
1449         int headers_only = HEADERS_ALL;
1450
1451         msgid = extract_long(cmdbuf, 0);
1452         headers_only = extract_int(cmdbuf, 1);
1453
1454         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1455 }
1456
1457
1458
1459 /* 
1460  * display a message (mode 3 - IGnet raw format - internal programs only)
1461  */
1462 void cmd_msg3(char *cmdbuf)
1463 {
1464         long msgnum;
1465         struct CtdlMessage *msg;
1466         struct ser_ret smr;
1467
1468         if (CC->internal_pgm == 0) {
1469                 cprintf("%d This command is for internal programs only.\n",
1470                         ERROR);
1471                 return;
1472         }
1473
1474         msgnum = extract_long(cmdbuf, 0);
1475         msg = CtdlFetchMessage(msgnum);
1476         if (msg == NULL) {
1477                 cprintf("%d Message %ld not found.\n", 
1478                         ERROR, msgnum);
1479                 return;
1480         }
1481
1482         serialize_message(&smr, msg);
1483         CtdlFreeMessage(msg);
1484
1485         if (smr.len == 0) {
1486                 cprintf("%d Unable to serialize message\n",
1487                         ERROR+INTERNAL_ERROR);
1488                 return;
1489         }
1490
1491         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1492         client_write(smr.ser, smr.len);
1493         phree(smr.ser);
1494 }
1495
1496
1497
1498 /* 
1499  * Display a message using MIME content types
1500  */
1501 void cmd_msg4(char *cmdbuf)
1502 {
1503         long msgid;
1504
1505         msgid = extract_long(cmdbuf, 0);
1506         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1507 }
1508
1509
1510
1511 /* 
1512  * Client tells us its preferred message format(s)
1513  */
1514 void cmd_msgp(char *cmdbuf)
1515 {
1516         safestrncpy(CC->preferred_formats, cmdbuf,
1517                         sizeof(CC->preferred_formats));
1518         cprintf("%d ok\n", CIT_OK);
1519 }
1520
1521
1522 /*
1523  * Open a component of a MIME message as a download file 
1524  */
1525 void cmd_opna(char *cmdbuf)
1526 {
1527         long msgid;
1528
1529         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1530
1531         msgid = extract_long(cmdbuf, 0);
1532         extract(desired_section, cmdbuf, 1);
1533
1534         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1535 }                       
1536
1537
1538 /*
1539  * Save a message pointer into a specified room
1540  * (Returns 0 for success, nonzero for failure)
1541  * roomname may be NULL to use the current room
1542  */
1543 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1544         int i;
1545         char hold_rm[ROOMNAMELEN];
1546         struct cdbdata *cdbfr;
1547         int num_msgs;
1548         long *msglist;
1549         long highest_msg = 0L;
1550         struct CtdlMessage *msg = NULL;
1551
1552         lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1553                 roomname, msgid, flags);
1554
1555         strcpy(hold_rm, CC->quickroom.QRname);
1556
1557         /* We may need to check to see if this message is real */
1558         if (  (flags & SM_VERIFY_GOODNESS)
1559            || (flags & SM_DO_REPL_CHECK)
1560            ) {
1561                 msg = CtdlFetchMessage(msgid);
1562                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1563         }
1564
1565         /* Perform replication checks if necessary */
1566         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1567
1568                 if (getroom(&CC->quickroom,
1569                    ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1570                    != 0) {
1571                         lprintf(9, "No such room <%s>\n", roomname);
1572                         if (msg != NULL) CtdlFreeMessage(msg);
1573                         return(ERROR + ROOM_NOT_FOUND);
1574                 }
1575
1576                 if (ReplicationChecks(msg) != 0) {
1577                         getroom(&CC->quickroom, hold_rm);
1578                         if (msg != NULL) CtdlFreeMessage(msg);
1579                         lprintf(9, "Did replication, and newer exists\n");
1580                         return(0);
1581                 }
1582         }
1583
1584         /* Now the regular stuff */
1585         if (lgetroom(&CC->quickroom,
1586            ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1587            != 0) {
1588                 lprintf(9, "No such room <%s>\n", roomname);
1589                 if (msg != NULL) CtdlFreeMessage(msg);
1590                 return(ERROR + ROOM_NOT_FOUND);
1591         }
1592
1593         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1594         if (cdbfr == NULL) {
1595                 msglist = NULL;
1596                 num_msgs = 0;
1597         } else {
1598                 msglist = mallok(cdbfr->len);
1599                 if (msglist == NULL)
1600                         lprintf(3, "ERROR malloc msglist!\n");
1601                 num_msgs = cdbfr->len / sizeof(long);
1602                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1603                 cdb_free(cdbfr);
1604         }
1605
1606
1607         /* Make sure the message doesn't already exist in this room.  It
1608          * is absolutely taboo to have more than one reference to the same
1609          * message in a room.
1610          */
1611         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1612                 if (msglist[i] == msgid) {
1613                         lputroom(&CC->quickroom);       /* unlock the room */
1614                         getroom(&CC->quickroom, hold_rm);
1615                         if (msg != NULL) CtdlFreeMessage(msg);
1616                         return(ERROR + ALREADY_EXISTS);
1617                 }
1618         }
1619
1620         /* Now add the new message */
1621         ++num_msgs;
1622         msglist = reallok(msglist,
1623                           (num_msgs * sizeof(long)));
1624
1625         if (msglist == NULL) {
1626                 lprintf(3, "ERROR: can't realloc message list!\n");
1627         }
1628         msglist[num_msgs - 1] = msgid;
1629
1630         /* Sort the message list, so all the msgid's are in order */
1631         num_msgs = sort_msglist(msglist, num_msgs);
1632
1633         /* Determine the highest message number */
1634         highest_msg = msglist[num_msgs - 1];
1635
1636         /* Write it back to disk. */
1637         cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1638                   msglist, num_msgs * sizeof(long));
1639
1640         /* Free up the memory we used. */
1641         phree(msglist);
1642
1643         /* Update the highest-message pointer and unlock the room. */
1644         CC->quickroom.QRhighest = highest_msg;
1645         lputroom(&CC->quickroom);
1646         getroom(&CC->quickroom, hold_rm);
1647
1648         /* Bump the reference count for this message. */
1649         if ((flags & SM_DONT_BUMP_REF)==0) {
1650                 AdjRefCount(msgid, +1);
1651         }
1652
1653         /* Return success. */
1654         if (msg != NULL) CtdlFreeMessage(msg);
1655         return (0);
1656 }
1657
1658
1659
1660 /*
1661  * Message base operation to send a message to the master file
1662  * (returns new message number)
1663  *
1664  * This is the back end for CtdlSubmitMsg() and should not be directly
1665  * called by server-side modules.
1666  *
1667  */
1668 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1669                 FILE *save_a_copy)              /* save a copy to disk? */
1670 {
1671         long newmsgid;
1672         long retval;
1673         char msgidbuf[SIZ];
1674         struct ser_ret smr;
1675
1676         /* Get a new message number */
1677         newmsgid = get_new_message_number();
1678         snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1679
1680         /* Generate an ID if we don't have one already */
1681         if (msg->cm_fields['I']==NULL) {
1682                 msg->cm_fields['I'] = strdoop(msgidbuf);
1683         }
1684         
1685         serialize_message(&smr, msg);
1686
1687         if (smr.len == 0) {
1688                 cprintf("%d Unable to serialize message\n",
1689                         ERROR+INTERNAL_ERROR);
1690                 return (-1L);
1691         }
1692
1693         /* Write our little bundle of joy into the message base */
1694         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1695                       smr.ser, smr.len) < 0) {
1696                 lprintf(2, "Can't store message\n");
1697                 retval = 0L;
1698         } else {
1699                 retval = newmsgid;
1700         }
1701
1702         /* If the caller specified that a copy should be saved to a particular
1703          * file handle, do that now too.
1704          */
1705         if (save_a_copy != NULL) {
1706                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1707         }
1708
1709         /* Free the memory we used for the serialized message */
1710         phree(smr.ser);
1711
1712         /* Return the *local* message ID to the caller
1713          * (even if we're storing an incoming network message)
1714          */
1715         return(retval);
1716 }
1717
1718
1719
1720 /*
1721  * Serialize a struct CtdlMessage into the format used on disk and network.
1722  * 
1723  * This function loads up a "struct ser_ret" (defined in server.h) which
1724  * contains the length of the serialized message and a pointer to the
1725  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1726  */
1727 void serialize_message(struct ser_ret *ret,             /* return values */
1728                         struct CtdlMessage *msg)        /* unserialized msg */
1729 {
1730         size_t wlen;
1731         int i;
1732         static char *forder = FORDER;
1733
1734         if (is_valid_message(msg) == 0) return;         /* self check */
1735
1736         ret->len = 3;
1737         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1738                 ret->len = ret->len +
1739                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1740
1741         lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1742         ret->ser = mallok(ret->len);
1743         if (ret->ser == NULL) {
1744                 ret->len = 0;
1745                 return;
1746         }
1747
1748         ret->ser[0] = 0xFF;
1749         ret->ser[1] = msg->cm_anon_type;
1750         ret->ser[2] = msg->cm_format_type;
1751         wlen = 3;
1752
1753         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1754                 ret->ser[wlen++] = (char)forder[i];
1755                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1756                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1757         }
1758         if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1759                 (long)ret->len, (long)wlen);
1760
1761         return;
1762 }
1763
1764
1765
1766 /*
1767  * Back end for the ReplicationChecks() function
1768  */
1769 void check_repl(long msgnum, void *userdata) {
1770         struct CtdlMessage *msg;
1771         time_t timestamp = (-1L);
1772
1773         lprintf(9, "check_repl() found message %ld\n", msgnum);
1774         msg = CtdlFetchMessage(msgnum);
1775         if (msg == NULL) return;
1776         if (msg->cm_fields['T'] != NULL) {
1777                 timestamp = atol(msg->cm_fields['T']);
1778         }
1779         CtdlFreeMessage(msg);
1780
1781         if (timestamp > msg_repl->highest) {
1782                 msg_repl->highest = timestamp;  /* newer! */
1783                 lprintf(9, "newer!\n");
1784                 return;
1785         }
1786         lprintf(9, "older!\n");
1787
1788         /* Existing isn't newer?  Then delete the old one(s). */
1789         CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1790 }
1791
1792
1793 /*
1794  * Check to see if any messages already exist which carry the same Extended ID
1795  * as this one.  
1796  *
1797  * If any are found:
1798  * -> With older timestamps: delete them and return 0.  Message will be saved.
1799  * -> With newer timestamps: return 1.  Message save will be aborted.
1800  */
1801 int ReplicationChecks(struct CtdlMessage *msg) {
1802         struct CtdlMessage *template;
1803         int abort_this = 0;
1804
1805         lprintf(9, "ReplicationChecks() started\n");
1806         /* No extended id?  Don't do anything. */
1807         if (msg->cm_fields['E'] == NULL) return 0;
1808         if (strlen(msg->cm_fields['E']) == 0) return 0;
1809         lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1810
1811         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1812         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1813         msg_repl->highest = atol(msg->cm_fields['T']);
1814
1815         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1816         memset(template, 0, sizeof(struct CtdlMessage));
1817         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1818
1819         CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1820
1821         /* If a newer message exists with the same Extended ID, abort
1822          * this save.
1823          */
1824         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1825                 abort_this = 1;
1826                 }
1827
1828         CtdlFreeMessage(template);
1829         lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1830         return(abort_this);
1831 }
1832
1833
1834
1835
1836 /*
1837  * Save a message to disk and submit it into the delivery system.
1838  */
1839 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1840                 struct recptypes *recps,        /* recipients (if mail) */
1841                 char *force                     /* force a particular room? */
1842 ) {
1843         char aaa[SIZ];
1844         char hold_rm[ROOMNAMELEN];
1845         char actual_rm[ROOMNAMELEN];
1846         char force_room[ROOMNAMELEN];
1847         char content_type[SIZ];                 /* We have to learn this */
1848         char recipient[SIZ];
1849         long newmsgid;
1850         char *mptr = NULL;
1851         struct usersupp userbuf;
1852         int a, i;
1853         struct MetaData smi;
1854         FILE *network_fp = NULL;
1855         static int seqnum = 1;
1856         struct CtdlMessage *imsg = NULL;
1857         char *instr;
1858         struct ser_ret smr;
1859         char *hold_R, *hold_D;
1860
1861         lprintf(9, "CtdlSubmitMsg() called\n");
1862         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1863
1864         /* If this message has no timestamp, we take the liberty of
1865          * giving it one, right now.
1866          */
1867         if (msg->cm_fields['T'] == NULL) {
1868                 lprintf(9, "Generating timestamp\n");
1869                 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1870                 msg->cm_fields['T'] = strdoop(aaa);
1871         }
1872
1873         /* If this message has no path, we generate one.
1874          */
1875         if (msg->cm_fields['P'] == NULL) {
1876                 lprintf(9, "Generating path\n");
1877                 if (msg->cm_fields['A'] != NULL) {
1878                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1879                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1880                                 if (isspace(msg->cm_fields['P'][a])) {
1881                                         msg->cm_fields['P'][a] = ' ';
1882                                 }
1883                         }
1884                 }
1885                 else {
1886                         msg->cm_fields['P'] = strdoop("unknown");
1887                 }
1888         }
1889
1890         if (force == NULL) {
1891                 strcpy(force_room, "");
1892         }
1893         else {
1894                 strcpy(force_room, force);
1895         }
1896
1897         /* Learn about what's inside, because it's what's inside that counts */
1898         lprintf(9, "Learning what's inside\n");
1899         if (msg->cm_fields['M'] == NULL) {
1900                 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1901         }
1902
1903         switch (msg->cm_format_type) {
1904         case 0:
1905                 strcpy(content_type, "text/x-citadel-variformat");
1906                 break;
1907         case 1:
1908                 strcpy(content_type, "text/plain");
1909                 break;
1910         case 4:
1911                 strcpy(content_type, "text/plain");
1912                 /* advance past header fields */
1913                 mptr = msg->cm_fields['M'];
1914                 a = strlen(mptr);
1915                 while ((--a) > 0) {
1916                         if (!strncasecmp(mptr, "Content-type: ", 14)) {
1917                                 safestrncpy(content_type, mptr,
1918                                             sizeof(content_type));
1919                                 strcpy(content_type, &content_type[14]);
1920                                 for (a = 0; a < strlen(content_type); ++a)
1921                                         if ((content_type[a] == ';')
1922                                             || (content_type[a] == ' ')
1923                                             || (content_type[a] == 13)
1924                                             || (content_type[a] == 10))
1925                                                 content_type[a] = 0;
1926                                 break;
1927                         }
1928                         ++mptr;
1929                 }
1930         }
1931
1932         /* Goto the correct room */
1933         lprintf(9, "Switching rooms\n");
1934         strcpy(hold_rm, CC->quickroom.QRname);
1935         strcpy(actual_rm, CC->quickroom.QRname);
1936         if (recps != NULL) {
1937                 strcpy(actual_rm, SENTITEMS);
1938         }
1939
1940         /* If the user is a twit, move to the twit room for posting */
1941         lprintf(9, "Handling twit stuff\n");
1942         if (TWITDETECT) {
1943                 if (CC->usersupp.axlevel == 2) {
1944                         strcpy(hold_rm, actual_rm);
1945                         strcpy(actual_rm, config.c_twitroom);
1946                 }
1947         }
1948
1949         /* ...or if this message is destined for Aide> then go there. */
1950         if (strlen(force_room) > 0) {
1951                 strcpy(actual_rm, force_room);
1952         }
1953
1954         lprintf(9, "Possibly relocating\n");
1955         if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1956                 getroom(&CC->quickroom, actual_rm);
1957         }
1958
1959         /*
1960          * If this message has no O (room) field, generate one.
1961          */
1962         if (msg->cm_fields['O'] == NULL) {
1963                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1964         }
1965
1966         /* Perform "before save" hooks (aborting if any return nonzero) */
1967         lprintf(9, "Performing before-save hooks\n");
1968         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1969
1970         /* If this message has an Extended ID, perform replication checks */
1971         lprintf(9, "Performing replication checks\n");
1972         if (ReplicationChecks(msg) > 0) return(-1);
1973
1974         /* Save it to disk */
1975         lprintf(9, "Saving to disk\n");
1976         newmsgid = send_message(msg, NULL);
1977         if (newmsgid <= 0L) return(-1);
1978
1979         /* Write a supplemental message info record.  This doesn't have to
1980          * be a critical section because nobody else knows about this message
1981          * yet.
1982          */
1983         lprintf(9, "Creating MetaData record\n");
1984         memset(&smi, 0, sizeof(struct MetaData));
1985         smi.meta_msgnum = newmsgid;
1986         smi.meta_refcount = 0;
1987         safestrncpy(smi.meta_content_type, content_type, 64);
1988         PutMetaData(&smi);
1989
1990         /* Now figure out where to store the pointers */
1991         lprintf(9, "Storing pointers\n");
1992
1993         /* If this is being done by the networker delivering a private
1994          * message, we want to BYPASS saving the sender's copy (because there
1995          * is no local sender; it would otherwise go to the Trashcan).
1996          */
1997         if ((!CC->internal_pgm) || (recps == NULL)) {
1998                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1999                         lprintf(3, "ERROR saving message pointer!\n");
2000                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2001                 }
2002         }
2003
2004         /* For internet mail, drop a copy in the outbound queue room */
2005         if (recps != NULL)
2006          if (recps->num_internet > 0) {
2007                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2008         }
2009
2010         /* If other rooms are specified, drop them there too. */
2011         if (recps != NULL)
2012          if (recps->num_room > 0)
2013           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2014                 extract(recipient, recps->recp_room, i);
2015                 lprintf(9, "Delivering to local room <%s>\n", recipient);
2016                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2017         }
2018
2019         /* Bump this user's messages posted counter. */
2020         lprintf(9, "Updating user\n");
2021         lgetuser(&CC->usersupp, CC->curr_user);
2022         CC->usersupp.posted = CC->usersupp.posted + 1;
2023         lputuser(&CC->usersupp);
2024
2025         /* If this is private, local mail, make a copy in the
2026          * recipient's mailbox and bump the reference count.
2027          */
2028         if (recps != NULL)
2029          if (recps->num_local > 0)
2030           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2031                 extract(recipient, recps->recp_local, i);
2032                 lprintf(9, "Delivering private local mail to <%s>\n",
2033                         recipient);
2034                 if (getuser(&userbuf, recipient) == 0) {
2035                         MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2036                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2037                         BumpNewMailCounter(userbuf.usernum);
2038                 }
2039                 else {
2040                         lprintf(9, "No user <%s>\n", recipient);
2041                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2042                 }
2043         }
2044
2045         /* Perform "after save" hooks */
2046         lprintf(9, "Performing after-save hooks\n");
2047         PerformMessageHooks(msg, EVT_AFTERSAVE);
2048
2049         /* For IGnet mail, we have to save a new copy into the spooler for
2050          * each recipient, with the R and D fields set to the recipient and
2051          * destination-node.  This has two ugly side effects: all other
2052          * recipients end up being unlisted in this recipient's copy of the
2053          * message, and it has to deliver multiple messages to the same
2054          * node.  We'll revisit this again in a year or so when everyone has
2055          * a network spool receiver that can handle the new style messages.
2056          */
2057         if (recps != NULL)
2058          if (recps->num_ignet > 0)
2059           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2060                 extract(recipient, recps->recp_ignet, i);
2061
2062                 hold_R = msg->cm_fields['R'];
2063                 hold_D = msg->cm_fields['D'];
2064                 msg->cm_fields['R'] = mallok(SIZ);
2065                 msg->cm_fields['D'] = mallok(SIZ);
2066                 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2067                 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2068                 
2069                 serialize_message(&smr, msg);
2070                 if (smr.len > 0) {
2071                         snprintf(aaa, sizeof aaa,
2072                                 "./network/spoolin/netmail.%04lx.%04x.%04x",
2073                                 (long) getpid(), CC->cs_pid, ++seqnum);
2074                         network_fp = fopen(aaa, "wb+");
2075                         if (network_fp != NULL) {
2076                                 fwrite(smr.ser, smr.len, 1, network_fp);
2077                                 fclose(network_fp);
2078                         }
2079                         phree(smr.ser);
2080                 }
2081
2082                 phree(msg->cm_fields['R']);
2083                 phree(msg->cm_fields['D']);
2084                 msg->cm_fields['R'] = hold_R;
2085                 msg->cm_fields['D'] = hold_D;
2086         }
2087
2088         /* Go back to the room we started from */
2089         lprintf(9, "Returning to original room\n");
2090         if (strcasecmp(hold_rm, CC->quickroom.QRname))
2091                 getroom(&CC->quickroom, hold_rm);
2092
2093         /* For internet mail, generate delivery instructions.
2094          * Yes, this is recursive.  Deal with it.  Infinite recursion does
2095          * not happen because the delivery instructions message does not
2096          * contain a recipient.
2097          */
2098         if (recps != NULL)
2099          if (recps->num_internet > 0) {
2100                 lprintf(9, "Generating delivery instructions\n");
2101                 instr = mallok(SIZ * 2);
2102                 snprintf(instr, SIZ * 2,
2103                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2104                         "bounceto|%s@%s\n",
2105                         SPOOLMIME, newmsgid, (long)time(NULL),
2106                         msg->cm_fields['A'], msg->cm_fields['N']
2107                 );
2108
2109                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2110                         size_t tmp = strlen(instr);
2111                         extract(recipient, recps->recp_internet, i);
2112                         snprintf(&instr[tmp], SIZ * 2 - tmp,
2113                                  "remote|%s|0||\n", recipient);
2114                 }
2115
2116                 imsg = mallok(sizeof(struct CtdlMessage));
2117                 memset(imsg, 0, sizeof(struct CtdlMessage));
2118                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2119                 imsg->cm_anon_type = MES_NORMAL;
2120                 imsg->cm_format_type = FMT_RFC822;
2121                 imsg->cm_fields['A'] = strdoop("Citadel");
2122                 imsg->cm_fields['M'] = instr;
2123                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2124                 CtdlFreeMessage(imsg);
2125         }
2126
2127         return(newmsgid);
2128 }
2129
2130
2131
2132 /*
2133  * Convenience function for generating small administrative messages.
2134  */
2135 void quickie_message(char *from, char *to, char *room, char *text, 
2136                         int format_type, char *subject)
2137 {
2138         struct CtdlMessage *msg;
2139         struct recptypes *recp = NULL;
2140
2141         msg = mallok(sizeof(struct CtdlMessage));
2142         memset(msg, 0, sizeof(struct CtdlMessage));
2143         msg->cm_magic = CTDLMESSAGE_MAGIC;
2144         msg->cm_anon_type = MES_NORMAL;
2145         msg->cm_format_type = format_type;
2146         msg->cm_fields['A'] = strdoop(from);
2147         if (room != NULL) msg->cm_fields['O'] = strdoop(room);
2148         msg->cm_fields['N'] = strdoop(NODENAME);
2149         if (to != NULL) {
2150                 msg->cm_fields['R'] = strdoop(to);
2151                 recp = validate_recipients(to);
2152         }
2153         if (subject != NULL) {
2154                 msg->cm_fields['U'] = strdoop(subject);
2155         }
2156         msg->cm_fields['M'] = strdoop(text);
2157
2158         CtdlSubmitMsg(msg, recp, room);
2159         CtdlFreeMessage(msg);
2160         if (recp != NULL) phree(recp);
2161 }
2162
2163
2164
2165 /*
2166  * Back end function used by CtdlMakeMessage() and similar functions
2167  */
2168 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
2169                         size_t maxlen,          /* maximum message length */
2170                         char *exist             /* if non-null, append to it;
2171                                                    exist is ALWAYS freed  */
2172                         ) {
2173         char buf[SIZ];
2174         int linelen;
2175         size_t message_len = 0;
2176         size_t buffer_len = 0;
2177         char *ptr;
2178         char *m;
2179
2180         if (exist == NULL) {
2181                 m = mallok(4096);
2182                 m[0] = 0;
2183                 buffer_len = 4096;
2184                 message_len = 0;
2185         }
2186         else {
2187                 message_len = strlen(exist);
2188                 buffer_len = message_len + 4096;
2189                 m = reallok(exist, buffer_len);
2190                 if (m == NULL) {
2191                         phree(exist);
2192                         return m;
2193                 }
2194         }
2195
2196         /* flush the input if we have nowhere to store it */
2197         if (m == NULL) {
2198                 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2199                 return(NULL);
2200         }
2201
2202         /* read in the lines of message text one by one */
2203         while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2204
2205                 /* Measure the line and strip trailing newline characters */
2206                 linelen = strlen(buf);
2207                 if (linelen > 0) if (buf[linelen-1]==13) buf[linelen--]=0;
2208                 if (linelen > 0) if (buf[linelen-1]==10) buf[linelen--]=0;
2209
2210                 /* augment the buffer if we have to */
2211                 if ((message_len + linelen + 2) > buffer_len) {
2212                         lprintf(9, "realloking\n");
2213                         ptr = reallok(m, (buffer_len * 2) );
2214                         if (ptr == NULL) {      /* flush if can't allocate */
2215                                 while ( (client_gets(buf)>0) &&
2216                                         strcmp(buf, terminator)) ;;
2217                                 return(m);
2218                         } else {
2219                                 buffer_len = (buffer_len * 2);
2220                                 m = ptr;
2221                                 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2222                         }
2223                 }
2224
2225                 /* Add the new line to the buffer.  NOTE: this loop must avoid
2226                  * using functions like strcat() and strlen() because they
2227                  * traverse the entire buffer upon every call, and doing that
2228                  * for a multi-megabyte message slows it down beyond usability.
2229                  */
2230                 strcpy(&m[message_len], buf);
2231                 m[message_len + linelen] = '\n';
2232                 m[message_len + linelen + 1] = 0;
2233                 message_len = message_len + linelen + 1;
2234
2235                 /* if we've hit the max msg length, flush the rest */
2236                 if (message_len >= maxlen) {
2237                         while ( (client_gets(buf)>0)
2238                                 && strcmp(buf, terminator)) ;;
2239                         return(m);
2240                 }
2241         }
2242         return(m);
2243 }
2244
2245
2246
2247
2248 /*
2249  * Build a binary message to be saved on disk.
2250  * (NOTE: if you supply 'preformatted_text', the buffer you give it
2251  * will become part of the message.  This means you are no longer
2252  * responsible for managing that memory -- it will be freed along with
2253  * the rest of the fields when CtdlFreeMessage() is called.)
2254  */
2255
2256 struct CtdlMessage *CtdlMakeMessage(
2257         struct usersupp *author,        /* author's usersupp structure */
2258         char *recipient,                /* NULL if it's not mail */
2259         char *room,                     /* room where it's going */
2260         int type,                       /* see MES_ types in header file */
2261         int format_type,                /* variformat, plain text, MIME... */
2262         char *fake_name,                /* who we're masquerading as */
2263         char *subject,                  /* Subject (optional) */
2264         char *preformatted_text         /* ...or NULL to read text from client */
2265 ) {
2266         char dest_node[SIZ];
2267         char buf[SIZ];
2268         struct CtdlMessage *msg;
2269
2270         msg = mallok(sizeof(struct CtdlMessage));
2271         memset(msg, 0, sizeof(struct CtdlMessage));
2272         msg->cm_magic = CTDLMESSAGE_MAGIC;
2273         msg->cm_anon_type = type;
2274         msg->cm_format_type = format_type;
2275
2276         /* Don't confuse the poor folks if it's not routed mail. */
2277         strcpy(dest_node, "");
2278
2279         striplt(recipient);
2280
2281         snprintf(buf, sizeof buf, "cit%ld", author->usernum);   /* Path */
2282         msg->cm_fields['P'] = strdoop(buf);
2283
2284         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
2285         msg->cm_fields['T'] = strdoop(buf);
2286
2287         if (fake_name[0])                                       /* author */
2288                 msg->cm_fields['A'] = strdoop(fake_name);
2289         else
2290                 msg->cm_fields['A'] = strdoop(author->fullname);
2291
2292         if (CC->quickroom.QRflags & QR_MAILBOX) {               /* room */
2293                 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2294         }
2295         else {
2296                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2297         }
2298
2299         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
2300         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
2301
2302         if (recipient[0] != 0) {
2303                 msg->cm_fields['R'] = strdoop(recipient);
2304         }
2305         if (dest_node[0] != 0) {
2306                 msg->cm_fields['D'] = strdoop(dest_node);
2307         }
2308
2309         if ( (author == &CC->usersupp) && (strlen(CC->cs_inet_email) > 0) ) {
2310                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2311         }
2312
2313         if (subject != NULL) {
2314                 striplt(subject);
2315                 if (strlen(subject) > 0) {
2316                         msg->cm_fields['U'] = strdoop(subject);
2317                 }
2318         }
2319
2320         if (preformatted_text != NULL) {
2321                 msg->cm_fields['M'] = preformatted_text;
2322         }
2323         else {
2324                 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2325                                                 config.c_maxmsglen, NULL);
2326         }
2327
2328         return(msg);
2329 }
2330
2331
2332 /*
2333  * Check to see whether we have permission to post a message in the current
2334  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2335  * returns 0 on success.
2336  */
2337 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2338
2339         if (!(CC->logged_in)) {
2340                 snprintf(errmsgbuf, n, "Not logged in.");
2341                 return (ERROR + NOT_LOGGED_IN);
2342         }
2343
2344         if ((CC->usersupp.axlevel < 2)
2345             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2346                 snprintf(errmsgbuf, n, "Need to be validated to enter "
2347                                 "(except in %s> to sysop)", MAILROOM);
2348                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2349         }
2350
2351         if ((CC->usersupp.axlevel < 4)
2352            && (CC->quickroom.QRflags & QR_NETWORK)) {
2353                 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2354                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2355         }
2356
2357         if ((CC->usersupp.axlevel < 6)
2358            && (CC->quickroom.QRflags & QR_READONLY)) {
2359                 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2360                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2361         }
2362
2363         strcpy(errmsgbuf, "Ok");
2364         return(0);
2365 }
2366
2367
2368 /*
2369  * Check to see if the specified user has Internet mail permission
2370  * (returns nonzero if permission is granted)
2371  */
2372 int CtdlCheckInternetMailPermission(struct usersupp *who) {
2373
2374         /* Globally enabled? */
2375         if (config.c_restrict == 0) return(1);
2376
2377         /* User flagged ok? */
2378         if (who->flags & US_INTERNET) return(2);
2379
2380         /* Aide level access? */
2381         if (who->axlevel >= 6) return(3);
2382
2383         /* No mail for you! */
2384         return(0);
2385 }
2386
2387
2388
2389 /*
2390  * Validate recipients, count delivery types and errors, and handle aliasing
2391  * FIXME check for dupes!!!!!
2392  */
2393 struct recptypes *validate_recipients(char *recipients) {
2394         struct recptypes *ret;
2395         char this_recp[SIZ];
2396         char this_recp_cooked[SIZ];
2397         char append[SIZ];
2398         int num_recps;
2399         int i, j;
2400         int mailtype;
2401         int invalid;
2402         struct usersupp tempUS;
2403         struct quickroom tempQR;
2404
2405         /* Initialize */
2406         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2407         if (ret == NULL) return(NULL);
2408         memset(ret, 0, sizeof(struct recptypes));
2409
2410         ret->num_local = 0;
2411         ret->num_internet = 0;
2412         ret->num_ignet = 0;
2413         ret->num_error = 0;
2414         ret->num_room = 0;
2415
2416         if (recipients == NULL) {
2417                 num_recps = 0;
2418         }
2419         else if (strlen(recipients) == 0) {
2420                 num_recps = 0;
2421         }
2422         else {
2423                 /* Change all valid separator characters to commas */
2424                 for (i=0; i<strlen(recipients); ++i) {
2425                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2426                                 recipients[i] = ',';
2427                         }
2428                 }
2429
2430                 /* Count 'em up */
2431                 num_recps = num_tokens(recipients, ',');
2432         }
2433
2434         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2435                 extract_token(this_recp, recipients, i, ',');
2436                 striplt(this_recp);
2437                 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2438                 mailtype = alias(this_recp);
2439                 mailtype = alias(this_recp);
2440                 mailtype = alias(this_recp);
2441                 for (j=0; j<=strlen(this_recp); ++j) {
2442                         if (this_recp[j]=='_') {
2443                                 this_recp_cooked[j] = ' ';
2444                         }
2445                         else {
2446                                 this_recp_cooked[j] = this_recp[j];
2447                         }
2448                 }
2449                 invalid = 0;
2450                 switch(mailtype) {
2451                         case MES_LOCAL:
2452                                 if (!strcasecmp(this_recp, "sysop")) {
2453                                         ++ret->num_room;
2454                                         strcpy(this_recp, config.c_aideroom);
2455                                         if (strlen(ret->recp_room) > 0) {
2456                                                 strcat(ret->recp_room, "|");
2457                                         }
2458                                         strcat(ret->recp_room, this_recp);
2459                                 }
2460                                 else if (getuser(&tempUS, this_recp) == 0) {
2461                                         ++ret->num_local;
2462                                         strcpy(this_recp, tempUS.fullname);
2463                                         if (strlen(ret->recp_local) > 0) {
2464                                                 strcat(ret->recp_local, "|");
2465                                         }
2466                                         strcat(ret->recp_local, this_recp);
2467                                 }
2468                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2469                                         ++ret->num_local;
2470                                         strcpy(this_recp, tempUS.fullname);
2471                                         if (strlen(ret->recp_local) > 0) {
2472                                                 strcat(ret->recp_local, "|");
2473                                         }
2474                                         strcat(ret->recp_local, this_recp);
2475                                 }
2476                                 else if ( (!strncasecmp(this_recp, "room_", 5))
2477                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2478                                         ++ret->num_room;
2479                                         if (strlen(ret->recp_room) > 0) {
2480                                                 strcat(ret->recp_room, "|");
2481                                         }
2482                                         strcat(ret->recp_room, &this_recp_cooked[5]);
2483                                 }
2484                                 else {
2485                                         ++ret->num_error;
2486                                         invalid = 1;
2487                                 }
2488                                 break;
2489                         case MES_INTERNET:
2490                                 ++ret->num_internet;
2491                                 if (strlen(ret->recp_internet) > 0) {
2492                                         strcat(ret->recp_internet, "|");
2493                                 }
2494                                 strcat(ret->recp_internet, this_recp);
2495                                 break;
2496                         case MES_IGNET:
2497                                 ++ret->num_ignet;
2498                                 if (strlen(ret->recp_ignet) > 0) {
2499                                         strcat(ret->recp_ignet, "|");
2500                                 }
2501                                 strcat(ret->recp_ignet, this_recp);
2502                                 break;
2503                         case MES_ERROR:
2504                                 ++ret->num_error;
2505                                 invalid = 1;
2506                                 break;
2507                 }
2508                 if (invalid) {
2509                         if (strlen(ret->errormsg) == 0) {
2510                                 snprintf(append, sizeof append,
2511                                          "Invalid recipient: %s",
2512                                          this_recp);
2513                         }
2514                         else {
2515                                 snprintf(append, sizeof append,
2516                                          ", %s", this_recp);
2517                         }
2518                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2519                                 strcat(ret->errormsg, append);
2520                         }
2521                 }
2522                 else {
2523                         if (strlen(ret->display_recp) == 0) {
2524                                 strcpy(append, this_recp);
2525                         }
2526                         else {
2527                                 snprintf(append, sizeof append, ", %s",
2528                                          this_recp);
2529                         }
2530                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2531                                 strcat(ret->display_recp, append);
2532                         }
2533                 }
2534         }
2535
2536         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2537            ret->num_room + ret->num_error) == 0) {
2538                 ++ret->num_error;
2539                 strcpy(ret->errormsg, "No recipients specified.");
2540         }
2541
2542         lprintf(9, "validate_recipients()\n");
2543         lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2544         lprintf(9, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2545         lprintf(9, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2546         lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2547         lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2548
2549         return(ret);
2550 }
2551
2552
2553
2554 /*
2555  * message entry  -  mode 0 (normal)
2556  */
2557 void cmd_ent0(char *entargs)
2558 {
2559         int post = 0;
2560         char recp[SIZ];
2561         char masquerade_as[SIZ];
2562         int anon_flag = 0;
2563         int format_type = 0;
2564         char newusername[SIZ];
2565         struct CtdlMessage *msg;
2566         int anonymous = 0;
2567         char errmsg[SIZ];
2568         int err = 0;
2569         struct recptypes *valid = NULL;
2570         char subject[SIZ];
2571
2572         post = extract_int(entargs, 0);
2573         extract(recp, entargs, 1);
2574         anon_flag = extract_int(entargs, 2);
2575         format_type = extract_int(entargs, 3);
2576         extract(subject, entargs, 4);
2577
2578         /* first check to make sure the request is valid. */
2579
2580         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2581         if (err) {
2582                 cprintf("%d %s\n", err, errmsg);
2583                 return;
2584         }
2585
2586         /* Check some other permission type things. */
2587
2588         if (post == 2) {
2589                 if (CC->usersupp.axlevel < 6) {
2590                         cprintf("%d You don't have permission to masquerade.\n",
2591                                 ERROR + HIGHER_ACCESS_REQUIRED);
2592                         return;
2593                 }
2594                 extract(newusername, entargs, 5);
2595                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2596                 safestrncpy(CC->fake_postname, newusername,
2597                         sizeof(CC->fake_postname) );
2598                 cprintf("%d ok\n", CIT_OK);
2599                 return;
2600         }
2601         CC->cs_flags |= CS_POSTING;
2602
2603         /* In the Mail> room we have to behave a little differently --
2604          * make sure the user has specified at least one recipient.  Then
2605          * validate the recipient(s).
2606          */
2607         if ( (CC->quickroom.QRflags & QR_MAILBOX)
2608            && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2609
2610                 if (CC->usersupp.axlevel < 2) {
2611                         strcpy(recp, "sysop");
2612                 }
2613
2614                 valid = validate_recipients(recp);
2615                 if (valid->num_error > 0) {
2616                         cprintf("%d %s\n",
2617                                 ERROR + NO_SUCH_USER, valid->errormsg);
2618                         phree(valid);
2619                         return;
2620                 }
2621                 if (valid->num_internet > 0) {
2622                         if (CtdlCheckInternetMailPermission(&CC->usersupp)==0) {
2623                                 cprintf("%d You do not have permission "
2624                                         "to send Internet mail.\n",
2625                                         ERROR + HIGHER_ACCESS_REQUIRED);
2626                                 phree(valid);
2627                                 return;
2628                         }
2629                 }
2630
2631                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2632                    && (CC->usersupp.axlevel < 4) ) {
2633                         cprintf("%d Higher access required for network mail.\n",
2634                                 ERROR + HIGHER_ACCESS_REQUIRED);
2635                         phree(valid);
2636                         return;
2637                 }
2638         
2639                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2640                     && ((CC->usersupp.flags & US_INTERNET) == 0)
2641                     && (!CC->internal_pgm)) {
2642                         cprintf("%d You don't have access to Internet mail.\n",
2643                                 ERROR + HIGHER_ACCESS_REQUIRED);
2644                         phree(valid);
2645                         return;
2646                 }
2647
2648         }
2649
2650         /* Is this a room which has anonymous-only or anonymous-option? */
2651         anonymous = MES_NORMAL;
2652         if (CC->quickroom.QRflags & QR_ANONONLY) {
2653                 anonymous = MES_ANONONLY;
2654         }
2655         if (CC->quickroom.QRflags & QR_ANONOPT) {
2656                 if (anon_flag == 1) {   /* only if the user requested it */
2657                         anonymous = MES_ANONOPT;
2658                 }
2659         }
2660
2661         if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2662                 recp[0] = 0;
2663         }
2664
2665         /* If we're only checking the validity of the request, return
2666          * success without creating the message.
2667          */
2668         if (post == 0) {
2669                 cprintf("%d %s\n", CIT_OK,
2670                         ((valid != NULL) ? valid->display_recp : "") );
2671                 phree(valid);
2672                 return;
2673         }
2674
2675         /* Handle author masquerading */
2676         if (CC->fake_postname[0]) {
2677                 strcpy(masquerade_as, CC->fake_postname);
2678         }
2679         else if (CC->fake_username[0]) {
2680                 strcpy(masquerade_as, CC->fake_username);
2681         }
2682         else {
2683                 strcpy(masquerade_as, "");
2684         }
2685
2686         /* Read in the message from the client. */
2687         cprintf("%d send message\n", SEND_LISTING);
2688         msg = CtdlMakeMessage(&CC->usersupp, recp,
2689                 CC->quickroom.QRname, anonymous, format_type,
2690                 masquerade_as, subject, NULL);
2691
2692         if (msg != NULL) {
2693                 CtdlSubmitMsg(msg, valid, "");
2694                 CtdlFreeMessage(msg);
2695         }
2696         CC->fake_postname[0] = '\0';
2697         phree(valid);
2698         return;
2699 }
2700
2701
2702
2703 /*
2704  * API function to delete messages which match a set of criteria
2705  * (returns the actual number of messages deleted)
2706  */
2707 int CtdlDeleteMessages(char *room_name,         /* which room */
2708                        long dmsgnum,            /* or "0" for any */
2709                        char *content_type       /* or "" for any */
2710 )
2711 {
2712
2713         struct quickroom qrbuf;
2714         struct cdbdata *cdbfr;
2715         long *msglist = NULL;
2716         long *dellist = NULL;
2717         int num_msgs = 0;
2718         int i;
2719         int num_deleted = 0;
2720         int delete_this;
2721         struct MetaData smi;
2722
2723         lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2724                 room_name, dmsgnum, content_type);
2725
2726         /* get room record, obtaining a lock... */
2727         if (lgetroom(&qrbuf, room_name) != 0) {
2728                 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2729                         room_name);
2730                 return (0);     /* room not found */
2731         }
2732         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2733
2734         if (cdbfr != NULL) {
2735                 msglist = mallok(cdbfr->len);
2736                 dellist = mallok(cdbfr->len);
2737                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2738                 num_msgs = cdbfr->len / sizeof(long);
2739                 cdb_free(cdbfr);
2740         }
2741         if (num_msgs > 0) {
2742                 for (i = 0; i < num_msgs; ++i) {
2743                         delete_this = 0x00;
2744
2745                         /* Set/clear a bit for each criterion */
2746
2747                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2748                                 delete_this |= 0x01;
2749                         }
2750                         if (strlen(content_type) == 0) {
2751                                 delete_this |= 0x02;
2752                         } else {
2753                                 GetMetaData(&smi, msglist[i]);
2754                                 if (!strcasecmp(smi.meta_content_type,
2755                                                 content_type)) {
2756                                         delete_this |= 0x02;
2757                                 }
2758                         }
2759
2760                         /* Delete message only if all bits are set */
2761                         if (delete_this == 0x03) {
2762                                 dellist[num_deleted++] = msglist[i];
2763                                 msglist[i] = 0L;
2764                         }
2765                 }
2766
2767                 num_msgs = sort_msglist(msglist, num_msgs);
2768                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2769                           msglist, (num_msgs * sizeof(long)));
2770
2771                 qrbuf.QRhighest = msglist[num_msgs - 1];
2772         }
2773         lputroom(&qrbuf);
2774
2775         /* Go through the messages we pulled out of the index, and decrement
2776          * their reference counts by 1.  If this is the only room the message
2777          * was in, the reference count will reach zero and the message will
2778          * automatically be deleted from the database.  We do this in a
2779          * separate pass because there might be plug-in hooks getting called,
2780          * and we don't want that happening during an S_QUICKROOM critical
2781          * section.
2782          */
2783         if (num_deleted) for (i=0; i<num_deleted; ++i) {
2784                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2785                 AdjRefCount(dellist[i], -1);
2786         }
2787
2788         /* Now free the memory we used, and go away. */
2789         if (msglist != NULL) phree(msglist);
2790         if (dellist != NULL) phree(dellist);
2791         lprintf(9, "%d message(s) deleted.\n", num_deleted);
2792         return (num_deleted);
2793 }
2794
2795
2796
2797 /*
2798  * Check whether the current user has permission to delete messages from
2799  * the current room (returns 1 for yes, 0 for no)
2800  */
2801 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2802         getuser(&CC->usersupp, CC->curr_user);
2803         if ((CC->usersupp.axlevel < 6)
2804             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2805             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2806             && (!(CC->internal_pgm))) {
2807                 return(0);
2808         }
2809         return(1);
2810 }
2811
2812
2813
2814 /*
2815  * Delete message from current room
2816  */
2817 void cmd_dele(char *delstr)
2818 {
2819         long delnum;
2820         int num_deleted;
2821
2822         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2823                 cprintf("%d Higher access required.\n",
2824                         ERROR + HIGHER_ACCESS_REQUIRED);
2825                 return;
2826         }
2827         delnum = extract_long(delstr, 0);
2828
2829         num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2830
2831         if (num_deleted) {
2832                 cprintf("%d %d message%s deleted.\n", CIT_OK,
2833                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2834         } else {
2835                 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2836         }
2837 }
2838
2839
2840 /*
2841  * Back end API function for moves and deletes
2842  */
2843 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2844         int err;
2845
2846         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2847                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2848         if (err != 0) return(err);
2849
2850         return(0);
2851 }
2852
2853
2854
2855 /*
2856  * move or copy a message to another room
2857  */
2858 void cmd_move(char *args)
2859 {
2860         long num;
2861         char targ[SIZ];
2862         struct quickroom qtemp;
2863         int err;
2864         int is_copy = 0;
2865
2866         num = extract_long(args, 0);
2867         extract(targ, args, 1);
2868         targ[ROOMNAMELEN - 1] = 0;
2869         is_copy = extract_int(args, 2);
2870
2871         if (getroom(&qtemp, targ) != 0) {
2872                 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2873                 return;
2874         }
2875
2876         getuser(&CC->usersupp, CC->curr_user);
2877         /* Aides can move/copy */
2878         if ((CC->usersupp.axlevel < 6)
2879             /* Roomaides can move/copy */
2880             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2881             /* Permit move/copy to/from personal rooms */
2882             && (!((CC->quickroom.QRflags & QR_MAILBOX)
2883                             && (qtemp.QRflags & QR_MAILBOX)))
2884             /* Permit only copy from public to personal room */
2885             && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2886                             && (qtemp.QRflags & QR_MAILBOX)))) {
2887                 cprintf("%d Higher access required.\n",
2888                         ERROR + HIGHER_ACCESS_REQUIRED);
2889                 return;
2890         }
2891
2892         err = CtdlCopyMsgToRoom(num, targ);
2893         if (err != 0) {
2894                 cprintf("%d Cannot store message in %s: error %d\n",
2895                         err, targ, err);
2896                 return;
2897         }
2898
2899         /* Now delete the message from the source room,
2900          * if this is a 'move' rather than a 'copy' operation.
2901          */
2902         if (is_copy == 0) {
2903                 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2904         }
2905
2906         cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2907 }
2908
2909
2910
2911 /*
2912  * GetMetaData()  -  Get the supplementary record for a message
2913  */
2914 void GetMetaData(struct MetaData *smibuf, long msgnum)
2915 {
2916
2917         struct cdbdata *cdbsmi;
2918         long TheIndex;
2919
2920         memset(smibuf, 0, sizeof(struct MetaData));
2921         smibuf->meta_msgnum = msgnum;
2922         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2923
2924         /* Use the negative of the message number for its supp record index */
2925         TheIndex = (0L - msgnum);
2926
2927         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2928         if (cdbsmi == NULL) {
2929                 return;         /* record not found; go with defaults */
2930         }
2931         memcpy(smibuf, cdbsmi->ptr,
2932                ((cdbsmi->len > sizeof(struct MetaData)) ?
2933                 sizeof(struct MetaData) : cdbsmi->len));
2934         cdb_free(cdbsmi);
2935         return;
2936 }
2937
2938
2939 /*
2940  * PutMetaData()  -  (re)write supplementary record for a message
2941  */
2942 void PutMetaData(struct MetaData *smibuf)
2943 {
2944         long TheIndex;
2945
2946         /* Use the negative of the message number for the metadata db index */
2947         TheIndex = (0L - smibuf->meta_msgnum);
2948
2949         lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2950                 smibuf->meta_msgnum, smibuf->meta_refcount);
2951
2952         cdb_store(CDB_MSGMAIN,
2953                   &TheIndex, sizeof(long),
2954                   smibuf, sizeof(struct MetaData));
2955
2956 }
2957
2958 /*
2959  * AdjRefCount  -  change the reference count for a message;
2960  *                 delete the message if it reaches zero
2961  */
2962 void AdjRefCount(long msgnum, int incr)
2963 {
2964
2965         struct MetaData smi;
2966         long delnum;
2967
2968         /* This is a *tight* critical section; please keep it that way, as
2969          * it may get called while nested in other critical sections.  
2970          * Complicating this any further will surely cause deadlock!
2971          */
2972         begin_critical_section(S_SUPPMSGMAIN);
2973         GetMetaData(&smi, msgnum);
2974         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2975                 msgnum, smi.meta_refcount);
2976         smi.meta_refcount += incr;
2977         PutMetaData(&smi);
2978         end_critical_section(S_SUPPMSGMAIN);
2979         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2980                 msgnum, smi.meta_refcount);
2981
2982         /* If the reference count is now zero, delete the message
2983          * (and its supplementary record as well).
2984          */
2985         if (smi.meta_refcount == 0) {
2986                 lprintf(9, "Deleting message <%ld>\n", msgnum);
2987                 delnum = msgnum;
2988                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2989
2990                 /* We have to delete the metadata record too! */
2991                 delnum = (0L - msgnum);
2992                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2993         }
2994 }
2995
2996 /*
2997  * Write a generic object to this room
2998  *
2999  * Note: this could be much more efficient.  Right now we use two temporary
3000  * files, and still pull the message into memory as with all others.
3001  */
3002 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
3003                         char *content_type,     /* MIME type of this object */
3004                         char *tempfilename,     /* Where to fetch it from */
3005                         struct usersupp *is_mailbox,    /* Mailbox room? */
3006                         int is_binary,          /* Is encoding necessary? */
3007                         int is_unique,          /* Del others of this type? */
3008                         unsigned int flags      /* Internal save flags */
3009                         )
3010 {
3011
3012         FILE *fp;
3013         struct quickroom qrbuf;
3014         char roomname[ROOMNAMELEN];
3015         struct CtdlMessage *msg;
3016
3017         char *raw_message = NULL;
3018         char *encoded_message = NULL;
3019         off_t raw_length = 0;
3020
3021         if (is_mailbox != NULL)
3022                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3023         else
3024                 safestrncpy(roomname, req_room, sizeof(roomname));
3025         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3026
3027
3028         fp = fopen(tempfilename, "rb");
3029         if (fp == NULL) {
3030                 lprintf(5, "Cannot open %s: %s\n",
3031                         tempfilename, strerror(errno));
3032                 return;
3033         }
3034         fseek(fp, 0L, SEEK_END);
3035         raw_length = ftell(fp);
3036         rewind(fp);
3037         lprintf(9, "Raw length is %ld\n", (long)raw_length);
3038
3039         raw_message = mallok((size_t)raw_length);
3040         fread(raw_message, (size_t)raw_length, 1, fp);
3041         fclose(fp);
3042
3043         if (is_binary) {
3044                 encoded_message = mallok((size_t)
3045                         (((raw_length * 134) / 100) + 4096 ) );
3046         }
3047         else {
3048                 encoded_message = mallok((size_t)(raw_length + 4096));
3049         }
3050
3051         sprintf(encoded_message, "Content-type: %s\n", content_type);
3052
3053         if (is_binary) {
3054                 sprintf(&encoded_message[strlen(encoded_message)],
3055                         "Content-transfer-encoding: base64\n\n"
3056                 );
3057         }
3058         else {
3059                 sprintf(&encoded_message[strlen(encoded_message)],
3060                         "Content-transfer-encoding: 7bit\n\n"
3061                 );
3062         }
3063
3064         if (is_binary) {
3065                 CtdlEncodeBase64(
3066                         &encoded_message[strlen(encoded_message)],
3067                         raw_message,
3068                         (int)raw_length
3069                 );
3070         }
3071         else {
3072                 raw_message[raw_length] = 0;
3073                 memcpy(
3074                         &encoded_message[strlen(encoded_message)],
3075                         raw_message,
3076                         (int)(raw_length+1)
3077                 );
3078         }
3079
3080         phree(raw_message);
3081
3082         lprintf(9, "Allocating\n");
3083         msg = mallok(sizeof(struct CtdlMessage));
3084         memset(msg, 0, sizeof(struct CtdlMessage));
3085         msg->cm_magic = CTDLMESSAGE_MAGIC;
3086         msg->cm_anon_type = MES_NORMAL;
3087         msg->cm_format_type = 4;
3088         msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
3089         msg->cm_fields['O'] = strdoop(req_room);
3090         msg->cm_fields['N'] = strdoop(config.c_nodename);
3091         msg->cm_fields['H'] = strdoop(config.c_humannode);
3092         msg->cm_flags = flags;
3093         
3094         msg->cm_fields['M'] = encoded_message;
3095
3096         /* Create the requested room if we have to. */
3097         if (getroom(&qrbuf, roomname) != 0) {
3098                 create_room(roomname, 
3099                         ( (is_mailbox != NULL) ? 5 : 3 ),
3100                         "", 0, 1, 0);
3101         }
3102         /* If the caller specified this object as unique, delete all
3103          * other objects of this type that are currently in the room.
3104          */
3105         if (is_unique) {
3106                 lprintf(9, "Deleted %d other msgs of this type\n",
3107                         CtdlDeleteMessages(roomname, 0L, content_type));
3108         }
3109         /* Now write the data */
3110         CtdlSubmitMsg(msg, NULL, roomname);
3111         CtdlFreeMessage(msg);
3112 }
3113
3114
3115
3116
3117
3118
3119 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3120         config_msgnum = msgnum;
3121 }
3122
3123
3124 char *CtdlGetSysConfig(char *sysconfname) {
3125         char hold_rm[ROOMNAMELEN];
3126         long msgnum;
3127         char *conf;
3128         struct CtdlMessage *msg;
3129         char buf[SIZ];
3130         
3131         strcpy(hold_rm, CC->quickroom.QRname);
3132         if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
3133                 getroom(&CC->quickroom, hold_rm);
3134                 return NULL;
3135         }
3136
3137
3138         /* We want the last (and probably only) config in this room */
3139         begin_critical_section(S_CONFIG);
3140         config_msgnum = (-1L);
3141         CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3142                 CtdlGetSysConfigBackend, NULL);
3143         msgnum = config_msgnum;
3144         end_critical_section(S_CONFIG);
3145
3146         if (msgnum < 0L) {
3147                 conf = NULL;
3148         }
3149         else {
3150                 msg = CtdlFetchMessage(msgnum);
3151                 if (msg != NULL) {
3152                         conf = strdoop(msg->cm_fields['M']);
3153                         CtdlFreeMessage(msg);
3154                 }
3155                 else {
3156                         conf = NULL;
3157                 }
3158         }
3159
3160         getroom(&CC->quickroom, hold_rm);
3161
3162         if (conf != NULL) do {
3163                 extract_token(buf, conf, 0, '\n');
3164                 strcpy(conf, &conf[strlen(buf)+1]);
3165         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3166
3167         return(conf);
3168 }
3169
3170 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3171         char temp[PATH_MAX];
3172         FILE *fp;
3173
3174         strcpy(temp, tmpnam(NULL));
3175
3176         fp = fopen(temp, "w");
3177         if (fp == NULL) return;
3178         fprintf(fp, "%s", sysconfdata);
3179         fclose(fp);
3180
3181         /* this handy API function does all the work for us */
3182         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3183         unlink(temp);
3184 }
3185
3186
3187 /*
3188  * Determine whether a given Internet address belongs to the current user
3189  */
3190 int CtdlIsMe(char *addr) {
3191         struct recptypes *recp;
3192         int i;
3193
3194         recp = validate_recipients(addr);
3195         if (recp == NULL) return(0);
3196
3197         if (recp->num_local == 0) {
3198                 phree(recp);
3199                 return(0);
3200         }
3201
3202         for (i=0; i<recp->num_local; ++i) {
3203                 extract(addr, recp->recp_local, i);
3204                 if (!strcasecmp(addr, CC->usersupp.fullname)) {
3205                         phree(recp);
3206                         return(1);
3207                 }
3208         }
3209
3210         phree(recp);
3211         return(0);
3212 }
3213
3214
3215 /*
3216  * Citadel protocol command to do the same
3217  */
3218 void cmd_isme(char *argbuf) {
3219         char addr[SIZ];
3220
3221         if (CtdlAccessCheck(ac_logged_in)) return;
3222         extract(addr, argbuf, 0);
3223
3224         if (CtdlIsMe(addr)) {
3225                 cprintf("%d %s\n", CIT_OK, addr);
3226         }
3227         else {
3228                 cprintf("%d Not you.\n", ERROR);
3229         }
3230
3231 }