]> code.citadel.org Git - citadel.git/blob - citadel/msgbase.c
* msgbase.c: CtdlCheckInternetMailPermission(): Do not allow twits to send
[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         /* Do not allow twits to send Internet mail */
2474         if (who->axlevel <= 2) return(0);
2475
2476         /* Globally enabled? */
2477         if (config.c_restrict == 0) return(1);
2478
2479         /* User flagged ok? */
2480         if (who->flags & US_INTERNET) return(2);
2481
2482         /* Aide level access? */
2483         if (who->axlevel >= 6) return(3);
2484
2485         /* No mail for you! */
2486         return(0);
2487 }
2488
2489
2490
2491 /*
2492  * Validate recipients, count delivery types and errors, and handle aliasing
2493  * FIXME check for dupes!!!!!
2494  */
2495 struct recptypes *validate_recipients(char *recipients) {
2496         struct recptypes *ret;
2497         char this_recp[SIZ];
2498         char this_recp_cooked[SIZ];
2499         char append[SIZ];
2500         int num_recps;
2501         int i, j;
2502         int mailtype;
2503         int invalid;
2504         struct ctdluser tempUS;
2505         struct ctdlroom tempQR;
2506
2507         /* Initialize */
2508         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2509         if (ret == NULL) return(NULL);
2510         memset(ret, 0, sizeof(struct recptypes));
2511
2512         ret->num_local = 0;
2513         ret->num_internet = 0;
2514         ret->num_ignet = 0;
2515         ret->num_error = 0;
2516         ret->num_room = 0;
2517
2518         if (recipients == NULL) {
2519                 num_recps = 0;
2520         }
2521         else if (strlen(recipients) == 0) {
2522                 num_recps = 0;
2523         }
2524         else {
2525                 /* Change all valid separator characters to commas */
2526                 for (i=0; i<strlen(recipients); ++i) {
2527                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2528                                 recipients[i] = ',';
2529                         }
2530                 }
2531
2532                 /* Count 'em up */
2533                 num_recps = num_tokens(recipients, ',');
2534         }
2535
2536         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2537                 extract_token(this_recp, recipients, i, ',');
2538                 striplt(this_recp);
2539                 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2540                 mailtype = alias(this_recp);
2541                 mailtype = alias(this_recp);
2542                 mailtype = alias(this_recp);
2543                 for (j=0; j<=strlen(this_recp); ++j) {
2544                         if (this_recp[j]=='_') {
2545                                 this_recp_cooked[j] = ' ';
2546                         }
2547                         else {
2548                                 this_recp_cooked[j] = this_recp[j];
2549                         }
2550                 }
2551                 invalid = 0;
2552                 switch(mailtype) {
2553                         case MES_LOCAL:
2554                                 if (!strcasecmp(this_recp, "sysop")) {
2555                                         ++ret->num_room;
2556                                         strcpy(this_recp, config.c_aideroom);
2557                                         if (strlen(ret->recp_room) > 0) {
2558                                                 strcat(ret->recp_room, "|");
2559                                         }
2560                                         strcat(ret->recp_room, this_recp);
2561                                 }
2562                                 else if (getuser(&tempUS, this_recp) == 0) {
2563                                         ++ret->num_local;
2564                                         strcpy(this_recp, tempUS.fullname);
2565                                         if (strlen(ret->recp_local) > 0) {
2566                                                 strcat(ret->recp_local, "|");
2567                                         }
2568                                         strcat(ret->recp_local, this_recp);
2569                                 }
2570                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2571                                         ++ret->num_local;
2572                                         strcpy(this_recp, tempUS.fullname);
2573                                         if (strlen(ret->recp_local) > 0) {
2574                                                 strcat(ret->recp_local, "|");
2575                                         }
2576                                         strcat(ret->recp_local, this_recp);
2577                                 }
2578                                 else if ( (!strncasecmp(this_recp, "room_", 5))
2579                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2580                                         ++ret->num_room;
2581                                         if (strlen(ret->recp_room) > 0) {
2582                                                 strcat(ret->recp_room, "|");
2583                                         }
2584                                         strcat(ret->recp_room, &this_recp_cooked[5]);
2585                                 }
2586                                 else {
2587                                         ++ret->num_error;
2588                                         invalid = 1;
2589                                 }
2590                                 break;
2591                         case MES_INTERNET:
2592                                 /* Yes, you're reading this correctly: if the target
2593                                  * domain points back to the local system or an attached
2594                                  * Citadel directory, the address is invalid.  That's
2595                                  * because if the address were valid, we would have
2596                                  * already translated it to a local address by now.
2597                                  */
2598                                 if (IsDirectory(this_recp)) {
2599                                         ++ret->num_error;
2600                                         invalid = 1;
2601                                 }
2602                                 else {
2603                                         ++ret->num_internet;
2604                                         if (strlen(ret->recp_internet) > 0) {
2605                                                 strcat(ret->recp_internet, "|");
2606                                         }
2607                                         strcat(ret->recp_internet, this_recp);
2608                                 }
2609                                 break;
2610                         case MES_IGNET:
2611                                 ++ret->num_ignet;
2612                                 if (strlen(ret->recp_ignet) > 0) {
2613                                         strcat(ret->recp_ignet, "|");
2614                                 }
2615                                 strcat(ret->recp_ignet, this_recp);
2616                                 break;
2617                         case MES_ERROR:
2618                                 ++ret->num_error;
2619                                 invalid = 1;
2620                                 break;
2621                 }
2622                 if (invalid) {
2623                         if (strlen(ret->errormsg) == 0) {
2624                                 snprintf(append, sizeof append,
2625                                          "Invalid recipient: %s",
2626                                          this_recp);
2627                         }
2628                         else {
2629                                 snprintf(append, sizeof append,
2630                                          ", %s", this_recp);
2631                         }
2632                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2633                                 strcat(ret->errormsg, append);
2634                         }
2635                 }
2636                 else {
2637                         if (strlen(ret->display_recp) == 0) {
2638                                 strcpy(append, this_recp);
2639                         }
2640                         else {
2641                                 snprintf(append, sizeof append, ", %s",
2642                                          this_recp);
2643                         }
2644                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2645                                 strcat(ret->display_recp, append);
2646                         }
2647                 }
2648         }
2649
2650         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2651            ret->num_room + ret->num_error) == 0) {
2652                 ++ret->num_error;
2653                 strcpy(ret->errormsg, "No recipients specified.");
2654         }
2655
2656         lprintf(CTDL_DEBUG, "validate_recipients()\n");
2657         lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2658         lprintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2659         lprintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2660         lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2661         lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2662
2663         return(ret);
2664 }
2665
2666
2667
2668 /*
2669  * message entry  -  mode 0 (normal)
2670  */
2671 void cmd_ent0(char *entargs)
2672 {
2673         int post = 0;
2674         char recp[SIZ];
2675         char masquerade_as[SIZ];
2676         int anon_flag = 0;
2677         int format_type = 0;
2678         char newusername[SIZ];
2679         struct CtdlMessage *msg;
2680         int anonymous = 0;
2681         char errmsg[SIZ];
2682         int err = 0;
2683         struct recptypes *valid = NULL;
2684         char subject[SIZ];
2685
2686         post = extract_int(entargs, 0);
2687         extract(recp, entargs, 1);
2688         anon_flag = extract_int(entargs, 2);
2689         format_type = extract_int(entargs, 3);
2690         extract(subject, entargs, 4);
2691
2692         /* first check to make sure the request is valid. */
2693
2694         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2695         if (err) {
2696                 cprintf("%d %s\n", err, errmsg);
2697                 return;
2698         }
2699
2700         /* Check some other permission type things. */
2701
2702         if (post == 2) {
2703                 if (CC->user.axlevel < 6) {
2704                         cprintf("%d You don't have permission to masquerade.\n",
2705                                 ERROR + HIGHER_ACCESS_REQUIRED);
2706                         return;
2707                 }
2708                 extract(newusername, entargs, 5);
2709                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2710                 safestrncpy(CC->fake_postname, newusername,
2711                         sizeof(CC->fake_postname) );
2712                 cprintf("%d ok\n", CIT_OK);
2713                 return;
2714         }
2715         CC->cs_flags |= CS_POSTING;
2716
2717         /* In the Mail> room we have to behave a little differently --
2718          * make sure the user has specified at least one recipient.  Then
2719          * validate the recipient(s).
2720          */
2721         if ( (CC->room.QRflags & QR_MAILBOX)
2722            && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2723
2724                 if (CC->user.axlevel < 2) {
2725                         strcpy(recp, "sysop");
2726                 }
2727
2728                 valid = validate_recipients(recp);
2729                 if (valid->num_error > 0) {
2730                         cprintf("%d %s\n",
2731                                 ERROR + NO_SUCH_USER, valid->errormsg);
2732                         free(valid);
2733                         return;
2734                 }
2735                 if (valid->num_internet > 0) {
2736                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2737                                 cprintf("%d You do not have permission "
2738                                         "to send Internet mail.\n",
2739                                         ERROR + HIGHER_ACCESS_REQUIRED);
2740                                 free(valid);
2741                                 return;
2742                         }
2743                 }
2744
2745                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2746                    && (CC->user.axlevel < 4) ) {
2747                         cprintf("%d Higher access required for network mail.\n",
2748                                 ERROR + HIGHER_ACCESS_REQUIRED);
2749                         free(valid);
2750                         return;
2751                 }
2752         
2753                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2754                     && ((CC->user.flags & US_INTERNET) == 0)
2755                     && (!CC->internal_pgm)) {
2756                         cprintf("%d You don't have access to Internet mail.\n",
2757                                 ERROR + HIGHER_ACCESS_REQUIRED);
2758                         free(valid);
2759                         return;
2760                 }
2761
2762         }
2763
2764         /* Is this a room which has anonymous-only or anonymous-option? */
2765         anonymous = MES_NORMAL;
2766         if (CC->room.QRflags & QR_ANONONLY) {
2767                 anonymous = MES_ANONONLY;
2768         }
2769         if (CC->room.QRflags & QR_ANONOPT) {
2770                 if (anon_flag == 1) {   /* only if the user requested it */
2771                         anonymous = MES_ANONOPT;
2772                 }
2773         }
2774
2775         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2776                 recp[0] = 0;
2777         }
2778
2779         /* If we're only checking the validity of the request, return
2780          * success without creating the message.
2781          */
2782         if (post == 0) {
2783                 cprintf("%d %s\n", CIT_OK,
2784                         ((valid != NULL) ? valid->display_recp : "") );
2785                 free(valid);
2786                 return;
2787         }
2788
2789         /* Handle author masquerading */
2790         if (CC->fake_postname[0]) {
2791                 strcpy(masquerade_as, CC->fake_postname);
2792         }
2793         else if (CC->fake_username[0]) {
2794                 strcpy(masquerade_as, CC->fake_username);
2795         }
2796         else {
2797                 strcpy(masquerade_as, "");
2798         }
2799
2800         /* Read in the message from the client. */
2801         cprintf("%d send message\n", SEND_LISTING);
2802         msg = CtdlMakeMessage(&CC->user, recp,
2803                 CC->room.QRname, anonymous, format_type,
2804                 masquerade_as, subject, NULL);
2805
2806         if (msg != NULL) {
2807                 CtdlSubmitMsg(msg, valid, "");
2808                 CtdlFreeMessage(msg);
2809         }
2810         CC->fake_postname[0] = '\0';
2811         free(valid);
2812         return;
2813 }
2814
2815
2816
2817 /*
2818  * API function to delete messages which match a set of criteria
2819  * (returns the actual number of messages deleted)
2820  */
2821 int CtdlDeleteMessages(char *room_name,         /* which room */
2822                        long dmsgnum,            /* or "0" for any */
2823                        char *content_type       /* or "" for any */
2824 )
2825 {
2826
2827         struct ctdlroom qrbuf;
2828         struct cdbdata *cdbfr;
2829         long *msglist = NULL;
2830         long *dellist = NULL;
2831         int num_msgs = 0;
2832         int i;
2833         int num_deleted = 0;
2834         int delete_this;
2835         struct MetaData smi;
2836
2837         lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2838                 room_name, dmsgnum, content_type);
2839
2840         /* get room record, obtaining a lock... */
2841         if (lgetroom(&qrbuf, room_name) != 0) {
2842                 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2843                         room_name);
2844                 return (0);     /* room not found */
2845         }
2846         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2847
2848         if (cdbfr != NULL) {
2849                 msglist = malloc(cdbfr->len);
2850                 dellist = malloc(cdbfr->len);
2851                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2852                 num_msgs = cdbfr->len / sizeof(long);
2853                 cdb_free(cdbfr);
2854         }
2855         if (num_msgs > 0) {
2856                 for (i = 0; i < num_msgs; ++i) {
2857                         delete_this = 0x00;
2858
2859                         /* Set/clear a bit for each criterion */
2860
2861                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2862                                 delete_this |= 0x01;
2863                         }
2864                         if (strlen(content_type) == 0) {
2865                                 delete_this |= 0x02;
2866                         } else {
2867                                 GetMetaData(&smi, msglist[i]);
2868                                 if (!strcasecmp(smi.meta_content_type,
2869                                                 content_type)) {
2870                                         delete_this |= 0x02;
2871                                 }
2872                         }
2873
2874                         /* Delete message only if all bits are set */
2875                         if (delete_this == 0x03) {
2876                                 dellist[num_deleted++] = msglist[i];
2877                                 msglist[i] = 0L;
2878                         }
2879                 }
2880
2881                 num_msgs = sort_msglist(msglist, num_msgs);
2882                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2883                           msglist, (num_msgs * sizeof(long)));
2884
2885                 qrbuf.QRhighest = msglist[num_msgs - 1];
2886         }
2887         lputroom(&qrbuf);
2888
2889         /* Go through the messages we pulled out of the index, and decrement
2890          * their reference counts by 1.  If this is the only room the message
2891          * was in, the reference count will reach zero and the message will
2892          * automatically be deleted from the database.  We do this in a
2893          * separate pass because there might be plug-in hooks getting called,
2894          * and we don't want that happening during an S_ROOMS critical
2895          * section.
2896          */
2897         if (num_deleted) for (i=0; i<num_deleted; ++i) {
2898                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2899                 AdjRefCount(dellist[i], -1);
2900         }
2901
2902         /* Now free the memory we used, and go away. */
2903         if (msglist != NULL) free(msglist);
2904         if (dellist != NULL) free(dellist);
2905         lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2906         return (num_deleted);
2907 }
2908
2909
2910
2911 /*
2912  * Check whether the current user has permission to delete messages from
2913  * the current room (returns 1 for yes, 0 for no)
2914  */
2915 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2916         getuser(&CC->user, CC->curr_user);
2917         if ((CC->user.axlevel < 6)
2918             && (CC->user.usernum != CC->room.QRroomaide)
2919             && ((CC->room.QRflags & QR_MAILBOX) == 0)
2920             && (!(CC->internal_pgm))) {
2921                 return(0);
2922         }
2923         return(1);
2924 }
2925
2926
2927
2928 /*
2929  * Delete message from current room
2930  */
2931 void cmd_dele(char *delstr)
2932 {
2933         long delnum;
2934         int num_deleted;
2935
2936         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2937                 cprintf("%d Higher access required.\n",
2938                         ERROR + HIGHER_ACCESS_REQUIRED);
2939                 return;
2940         }
2941         delnum = extract_long(delstr, 0);
2942
2943         num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2944
2945         if (num_deleted) {
2946                 cprintf("%d %d message%s deleted.\n", CIT_OK,
2947                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2948         } else {
2949                 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2950         }
2951 }
2952
2953
2954 /*
2955  * Back end API function for moves and deletes
2956  */
2957 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2958         int err;
2959
2960         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2961                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2962         if (err != 0) return(err);
2963
2964         return(0);
2965 }
2966
2967
2968
2969 /*
2970  * move or copy a message to another room
2971  */
2972 void cmd_move(char *args)
2973 {
2974         long num;
2975         char targ[SIZ];
2976         struct ctdlroom qtemp;
2977         int err;
2978         int is_copy = 0;
2979         int ra;
2980         int permit = 0;
2981
2982         num = extract_long(args, 0);
2983         extract(targ, args, 1);
2984         targ[ROOMNAMELEN - 1] = 0;
2985         is_copy = extract_int(args, 2);
2986
2987         if (getroom(&qtemp, targ) != 0) {
2988                 cprintf("%d '%s' does not exist.\n",
2989                         ERROR + ROOM_NOT_FOUND, targ);
2990                 return;
2991         }
2992
2993         getuser(&CC->user, CC->curr_user);
2994         ra = CtdlRoomAccess(&qtemp, &CC->user);
2995
2996         /* Check for permission to perform this operation.
2997          * Remember: "CC->room" is source, "qtemp" is target.
2998          */
2999         permit = 0;
3000
3001         /* Aides can move/copy */
3002         if (CC->user.axlevel >= 6) permit = 1;
3003
3004         /* Room aides can move/copy */
3005         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3006
3007         /* Permit move/copy from personal rooms */
3008         if ((CC->room.QRflags & QR_MAILBOX)
3009            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3010
3011         /* Permit only copy from public to personal room */
3012         if ( (is_copy)
3013            && (!(CC->room.QRflags & QR_MAILBOX))
3014            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3015
3016         /* User must have access to target room */
3017         if (!(ra & UA_KNOWN))  permit = 0;
3018
3019         if (!permit) {
3020                 cprintf("%d Higher access required.\n",
3021                         ERROR + HIGHER_ACCESS_REQUIRED);
3022                 return;
3023         }
3024
3025         err = CtdlCopyMsgToRoom(num, targ);
3026         if (err != 0) {
3027                 cprintf("%d Cannot store message in %s: error %d\n",
3028                         err, targ, err);
3029                 return;
3030         }
3031
3032         /* Now delete the message from the source room,
3033          * if this is a 'move' rather than a 'copy' operation.
3034          */
3035         if (is_copy == 0) {
3036                 CtdlDeleteMessages(CC->room.QRname, num, "");
3037         }
3038
3039         cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3040 }
3041
3042
3043
3044 /*
3045  * GetMetaData()  -  Get the supplementary record for a message
3046  */
3047 void GetMetaData(struct MetaData *smibuf, long msgnum)
3048 {
3049
3050         struct cdbdata *cdbsmi;
3051         long TheIndex;
3052
3053         memset(smibuf, 0, sizeof(struct MetaData));
3054         smibuf->meta_msgnum = msgnum;
3055         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
3056
3057         /* Use the negative of the message number for its supp record index */
3058         TheIndex = (0L - msgnum);
3059
3060         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3061         if (cdbsmi == NULL) {
3062                 return;         /* record not found; go with defaults */
3063         }
3064         memcpy(smibuf, cdbsmi->ptr,
3065                ((cdbsmi->len > sizeof(struct MetaData)) ?
3066                 sizeof(struct MetaData) : cdbsmi->len));
3067         cdb_free(cdbsmi);
3068         return;
3069 }
3070
3071
3072 /*
3073  * PutMetaData()  -  (re)write supplementary record for a message
3074  */
3075 void PutMetaData(struct MetaData *smibuf)
3076 {
3077         long TheIndex;
3078
3079         /* Use the negative of the message number for the metadata db index */
3080         TheIndex = (0L - smibuf->meta_msgnum);
3081
3082         lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3083                 smibuf->meta_msgnum, smibuf->meta_refcount);
3084
3085         cdb_store(CDB_MSGMAIN,
3086                   &TheIndex, sizeof(long),
3087                   smibuf, sizeof(struct MetaData));
3088
3089 }
3090
3091 /*
3092  * AdjRefCount  -  change the reference count for a message;
3093  *                 delete the message if it reaches zero
3094  */
3095 void AdjRefCount(long msgnum, int incr)
3096 {
3097
3098         struct MetaData smi;
3099         long delnum;
3100
3101         /* This is a *tight* critical section; please keep it that way, as
3102          * it may get called while nested in other critical sections.  
3103          * Complicating this any further will surely cause deadlock!
3104          */
3105         begin_critical_section(S_SUPPMSGMAIN);
3106         GetMetaData(&smi, msgnum);
3107         lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3108                 msgnum, smi.meta_refcount);
3109         smi.meta_refcount += incr;
3110         PutMetaData(&smi);
3111         end_critical_section(S_SUPPMSGMAIN);
3112         lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3113                 msgnum, smi.meta_refcount);
3114
3115         /* If the reference count is now zero, delete the message
3116          * (and its supplementary record as well).
3117          */
3118         if (smi.meta_refcount == 0) {
3119                 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3120                 delnum = msgnum;
3121                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3122                 cdb_delete(CDB_BIGMSGS, &delnum, sizeof(long));
3123
3124                 /* We have to delete the metadata record too! */
3125                 delnum = (0L - msgnum);
3126                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3127         }
3128 }
3129
3130 /*
3131  * Write a generic object to this room
3132  *
3133  * Note: this could be much more efficient.  Right now we use two temporary
3134  * files, and still pull the message into memory as with all others.
3135  */
3136 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
3137                         char *content_type,     /* MIME type of this object */
3138                         char *tempfilename,     /* Where to fetch it from */
3139                         struct ctdluser *is_mailbox,    /* Mailbox room? */
3140                         int is_binary,          /* Is encoding necessary? */
3141                         int is_unique,          /* Del others of this type? */
3142                         unsigned int flags      /* Internal save flags */
3143                         )
3144 {
3145
3146         FILE *fp;
3147         struct ctdlroom qrbuf;
3148         char roomname[ROOMNAMELEN];
3149         struct CtdlMessage *msg;
3150
3151         char *raw_message = NULL;
3152         char *encoded_message = NULL;
3153         off_t raw_length = 0;
3154
3155         if (is_mailbox != NULL)
3156                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3157         else
3158                 safestrncpy(roomname, req_room, sizeof(roomname));
3159         lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3160
3161
3162         fp = fopen(tempfilename, "rb");
3163         if (fp == NULL) {
3164                 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3165                         tempfilename, strerror(errno));
3166                 return;
3167         }
3168         fseek(fp, 0L, SEEK_END);
3169         raw_length = ftell(fp);
3170         rewind(fp);
3171         lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3172
3173         raw_message = malloc((size_t)raw_length + 2);
3174         fread(raw_message, (size_t)raw_length, 1, fp);
3175         fclose(fp);
3176
3177         if (is_binary) {
3178                 encoded_message = malloc((size_t)
3179                         (((raw_length * 134) / 100) + 4096 ) );
3180         }
3181         else {
3182                 encoded_message = malloc((size_t)(raw_length + 4096));
3183         }
3184
3185         sprintf(encoded_message, "Content-type: %s\n", content_type);
3186
3187         if (is_binary) {
3188                 sprintf(&encoded_message[strlen(encoded_message)],
3189                         "Content-transfer-encoding: base64\n\n"
3190                 );
3191         }
3192         else {
3193                 sprintf(&encoded_message[strlen(encoded_message)],
3194                         "Content-transfer-encoding: 7bit\n\n"
3195                 );
3196         }
3197
3198         if (is_binary) {
3199                 CtdlEncodeBase64(
3200                         &encoded_message[strlen(encoded_message)],
3201                         raw_message,
3202                         (int)raw_length
3203                 );
3204         }
3205         else {
3206                 raw_message[raw_length] = 0;
3207                 memcpy(
3208                         &encoded_message[strlen(encoded_message)],
3209                         raw_message,
3210                         (int)(raw_length+1)
3211                 );
3212         }
3213
3214         free(raw_message);
3215
3216         lprintf(CTDL_DEBUG, "Allocating\n");
3217         msg = malloc(sizeof(struct CtdlMessage));
3218         memset(msg, 0, sizeof(struct CtdlMessage));
3219         msg->cm_magic = CTDLMESSAGE_MAGIC;
3220         msg->cm_anon_type = MES_NORMAL;
3221         msg->cm_format_type = 4;
3222         msg->cm_fields['A'] = strdup(CC->user.fullname);
3223         msg->cm_fields['O'] = strdup(req_room);
3224         msg->cm_fields['N'] = strdup(config.c_nodename);
3225         msg->cm_fields['H'] = strdup(config.c_humannode);
3226         msg->cm_flags = flags;
3227         
3228         msg->cm_fields['M'] = encoded_message;
3229
3230         /* Create the requested room if we have to. */
3231         if (getroom(&qrbuf, roomname) != 0) {
3232                 create_room(roomname, 
3233                         ( (is_mailbox != NULL) ? 5 : 3 ),
3234                         "", 0, 1, 0);
3235         }
3236         /* If the caller specified this object as unique, delete all
3237          * other objects of this type that are currently in the room.
3238          */
3239         if (is_unique) {
3240                 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3241                         CtdlDeleteMessages(roomname, 0L, content_type));
3242         }
3243         /* Now write the data */
3244         CtdlSubmitMsg(msg, NULL, roomname);
3245         CtdlFreeMessage(msg);
3246 }
3247
3248
3249
3250
3251
3252
3253 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3254         config_msgnum = msgnum;
3255 }
3256
3257
3258 char *CtdlGetSysConfig(char *sysconfname) {
3259         char hold_rm[ROOMNAMELEN];
3260         long msgnum;
3261         char *conf;
3262         struct CtdlMessage *msg;
3263         char buf[SIZ];
3264         
3265         strcpy(hold_rm, CC->room.QRname);
3266         if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3267                 getroom(&CC->room, hold_rm);
3268                 return NULL;
3269         }
3270
3271
3272         /* We want the last (and probably only) config in this room */
3273         begin_critical_section(S_CONFIG);
3274         config_msgnum = (-1L);
3275         CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3276                 CtdlGetSysConfigBackend, NULL);
3277         msgnum = config_msgnum;
3278         end_critical_section(S_CONFIG);
3279
3280         if (msgnum < 0L) {
3281                 conf = NULL;
3282         }
3283         else {
3284                 msg = CtdlFetchMessage(msgnum, 1);
3285                 if (msg != NULL) {
3286                         conf = strdup(msg->cm_fields['M']);
3287                         CtdlFreeMessage(msg);
3288                 }
3289                 else {
3290                         conf = NULL;
3291                 }
3292         }
3293
3294         getroom(&CC->room, hold_rm);
3295
3296         if (conf != NULL) do {
3297                 extract_token(buf, conf, 0, '\n');
3298                 strcpy(conf, &conf[strlen(buf)+1]);
3299         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3300
3301         return(conf);
3302 }
3303
3304 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3305         char temp[PATH_MAX];
3306         FILE *fp;
3307
3308         strcpy(temp, tmpnam(NULL));
3309
3310         fp = fopen(temp, "w");
3311         if (fp == NULL) return;
3312         fprintf(fp, "%s", sysconfdata);
3313         fclose(fp);
3314
3315         /* this handy API function does all the work for us */
3316         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3317         unlink(temp);
3318 }
3319
3320
3321 /*
3322  * Determine whether a given Internet address belongs to the current user
3323  */
3324 int CtdlIsMe(char *addr) {
3325         struct recptypes *recp;
3326         int i;
3327
3328         recp = validate_recipients(addr);
3329         if (recp == NULL) return(0);
3330
3331         if (recp->num_local == 0) {
3332                 free(recp);
3333                 return(0);
3334         }
3335
3336         for (i=0; i<recp->num_local; ++i) {
3337                 extract(addr, recp->recp_local, i);
3338                 if (!strcasecmp(addr, CC->user.fullname)) {
3339                         free(recp);
3340                         return(1);
3341                 }
3342         }
3343
3344         free(recp);
3345         return(0);
3346 }
3347
3348
3349 /*
3350  * Citadel protocol command to do the same
3351  */
3352 void cmd_isme(char *argbuf) {
3353         char addr[SIZ];
3354
3355         if (CtdlAccessCheck(ac_logged_in)) return;
3356         extract(addr, argbuf, 0);
3357
3358         if (CtdlIsMe(addr)) {
3359                 cprintf("%d %s\n", CIT_OK, addr);
3360         }
3361         else {
3362                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
3363         }
3364
3365 }