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