citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

msgbase.c
Go to the documentation of this file.
1// Implements the message store.
2//
3// Copyright (c) 1987-2021 by the citadel.org team
4//
5// This program is open source software; you can redistribute it and/or modify
6// it under the terms of the GNU General Public License version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12
13
14#include <stdlib.h>
15#include <unistd.h>
16#include <stdio.h>
17#include <regex.h>
18#include <sys/stat.h>
19#include <libcitadel.h>
20#include "ctdl_module.h"
21#include "citserver.h"
22#include "control.h"
23#include "config.h"
24#include "clientsocket.h"
25#include "genstamp.h"
26#include "room_ops.h"
27#include "user_ops.h"
28#include "internet_addressing.h"
29#include "euidindex.h"
30#include "msgbase.h"
31#include "journaling.h"
32
34
35// These are the four-character field headers we use when outputting
36// messages in Citadel format (as opposed to RFC822 format).
37char *msgkeys[] = {
38 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
39 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
40 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
41 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
42 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
43 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
44 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
45 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
46 NULL,
47 "from", // A -> eAuthor
48 NULL, // B -> eBig_message
49 NULL, // C (formerly used as eRemoteRoom)
50 NULL, // D (formerly used as eDestination)
51 "exti", // E -> eXclusivID
52 "rfca", // F -> erFc822Addr
53 NULL, // G
54 "hnod", // H (formerly used as eHumanNode)
55 "msgn", // I -> emessageId
56 "jrnl", // J -> eJournal
57 "rep2", // K -> eReplyTo
58 "list", // L -> eListID
59 "text", // M -> eMesageText
60 NULL, // N (formerly used as eNodename)
61 "room", // O -> eOriginalRoom
62 "path", // P -> eMessagePath
63 NULL, // Q
64 "rcpt", // R -> eRecipient
65 NULL, // S (formerly used as eSpecialField)
66 "time", // T -> eTimestamp
67 "subj", // U -> eMsgSubject
68 "nvto", // V -> eenVelopeTo
69 "wefw", // W -> eWeferences
70 NULL, // X
71 "cccc", // Y -> eCarbonCopY
72 NULL // Z
73};
74
75
76HashList *msgKeyLookup = NULL;
77
78int GetFieldFromMnemonic(eMsgField *f, const char* c) {
79 void *v = NULL;
80 if (GetHash(msgKeyLookup, c, 4, &v)) {
81 *f = (eMsgField) v;
82 return 1;
83 }
84 return 0;
85}
86
88 long i;
89
90 msgKeyLookup = NewHash (1, FourHash);
91
92 for (i=0; i < 91; i++) {
93 if (msgkeys[i] != NULL) {
94 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
95 }
96 }
97}
98
99
101/* Important fields */
102 emessageId ,
104 eTimestamp ,
105 eAuthor ,
108 eRecipient ,
109/* Semi-important fields */
113 eJournal ,
114/* G is not used yet */
115 eReplyTo ,
116 eListID ,
117/* Q is not used yet */
119/* X is not used yet */
120/* Z is not used yet */
123/* internal only */
124 eErrorMsg ,
126 eExtnotify ,
127/* Message text (MUST be last) */
129/* Not saved to disk:
130 eVltMsgNum
131*/
132};
133
134static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
135
136
137int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
138 return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
139}
140
141
142void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
143 if (Msg->cm_fields[which] != NULL) {
144 free (Msg->cm_fields[which]);
145 }
146 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
147 length = strlen(buf);
148 }
149 Msg->cm_fields[which] = malloc(length + 1);
150 memcpy(Msg->cm_fields[which], buf, length);
151 Msg->cm_fields[which][length] = '\0';
152 Msg->cm_lengths[which] = length;
153}
154
155
156void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
157 char buf[128];
158 long len;
159 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
160 CM_SetField(Msg, which, buf, len);
161}
162
163
164void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
165 if (Msg->cm_fields[WhichToCut] == NULL)
166 return;
167
168 if (Msg->cm_lengths[WhichToCut] > maxlen)
169 {
170 Msg->cm_fields[WhichToCut][maxlen] = '\0';
171 Msg->cm_lengths[WhichToCut] = maxlen;
172 }
173}
174
175
176void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
177 if (Msg->cm_fields[which] != NULL)
178 free (Msg->cm_fields[which]);
179 Msg->cm_fields[which] = NULL;
180 Msg->cm_lengths[which] = 0;
181}
182
183
184void CM_Flush(struct CtdlMessage *Msg) {
185 int i;
186
187 if (CM_IsValidMsg(Msg) == 0) {
188 return;
189 }
190
191 for (i = 0; i < 256; ++i) {
192 CM_FlushField(Msg, i);
193 }
194}
195
196
197void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
198 long len;
199 if (Msg->cm_fields[WhichToPutTo] != NULL) {
200 free (Msg->cm_fields[WhichToPutTo]);
201 }
202
203 if (Msg->cm_fields[WhichtToCopy] != NULL) {
204 len = Msg->cm_lengths[WhichtToCopy];
205 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
206 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
207 Msg->cm_fields[WhichToPutTo][len] = '\0';
208 Msg->cm_lengths[WhichToPutTo] = len;
209 }
210 else {
211 Msg->cm_fields[WhichToPutTo] = NULL;
212 Msg->cm_lengths[WhichToPutTo] = 0;
213 }
214}
215
216
217void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
218 if (Msg->cm_fields[which] != NULL) {
219 long oldmsgsize;
220 long newmsgsize;
221 char *new;
222
223 oldmsgsize = Msg->cm_lengths[which] + 1;
224 newmsgsize = length + oldmsgsize;
225
226 new = malloc(newmsgsize);
227 memcpy(new, buf, length);
228 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
229 free(Msg->cm_fields[which]);
230 Msg->cm_fields[which] = new;
231 Msg->cm_lengths[which] = newmsgsize - 1;
232 }
233 else {
234 Msg->cm_fields[which] = malloc(length + 1);
235 memcpy(Msg->cm_fields[which], buf, length);
236 Msg->cm_fields[which][length] = '\0';
237 Msg->cm_lengths[which] = length;
238 }
239}
240
241
242void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
243 if (Msg->cm_fields[which] != NULL) {
244 free (Msg->cm_fields[which]);
245 }
246
247 Msg->cm_fields[which] = *buf;
248 *buf = NULL;
249 if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
250 Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
251 }
252 else {
253 Msg->cm_lengths[which] = length;
254 }
255}
256
257
258void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
259 if (Msg->cm_fields[which] != NULL) {
260 free (Msg->cm_fields[which]);
261 }
262
263 Msg->cm_lengths[which] = StrLength(*buf);
264 Msg->cm_fields[which] = SmashStrBuf(buf);
265}
266
267
268void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
269 if (Msg->cm_fields[which] != NULL) {
270 *retlen = Msg->cm_lengths[which];
271 *ret = Msg->cm_fields[which];
272 Msg->cm_fields[which] = NULL;
273 Msg->cm_lengths[which] = 0;
274 }
275 else {
276 *ret = NULL;
277 *retlen = 0;
278 }
279}
280
281
282// Returns 1 if the supplied pointer points to a valid Citadel message.
283// If the pointer is NULL or the magic number check fails, returns 0.
284int CM_IsValidMsg(struct CtdlMessage *msg) {
285 if (msg == NULL) {
286 return 0;
287 }
288 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
289 syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
290 return 0;
291 }
292 return 1;
293}
294
295
296void CM_FreeContents(struct CtdlMessage *msg) {
297 int i;
298
299 for (i = 0; i < 256; ++i)
300 if (msg->cm_fields[i] != NULL) {
301 free(msg->cm_fields[i]);
302 msg->cm_lengths[i] = 0;
303 }
304
305 msg->cm_magic = 0; // just in case
306}
307
308
309// 'Destructor' for struct CtdlMessage
310void CM_Free(struct CtdlMessage *msg) {
311 if (CM_IsValidMsg(msg) == 0) {
312 if (msg != NULL) free (msg);
313 return;
314 }
315 CM_FreeContents(msg);
316 free(msg);
317}
318
319
320int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
321 long len;
322 len = OrgMsg->cm_lengths[i];
323 NewMsg->cm_fields[i] = malloc(len + 1);
324 if (NewMsg->cm_fields[i] == NULL) {
325 return 0;
326 }
327 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
328 NewMsg->cm_fields[i][len] = '\0';
329 NewMsg->cm_lengths[i] = len;
330 return 1;
331}
332
333
334struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
335 int i;
336 struct CtdlMessage *NewMsg;
337
338 if (CM_IsValidMsg(OrgMsg) == 0) {
339 return NULL;
340 }
341 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
342 if (NewMsg == NULL) {
343 return NULL;
344 }
345
346 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
347
348 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
349
350 for (i = 0; i < 256; ++i) {
351 if (OrgMsg->cm_fields[i] != NULL) {
352 if (!CM_DupField(i, OrgMsg, NewMsg)) {
353 CM_Free(NewMsg);
354 return NULL;
355 }
356 }
357 }
358
359 return NewMsg;
360}
361
362
363// Determine if a given message matches the fields in a message template.
364// Return 0 for a successful match.
365int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
366 int i;
367
368 // If there aren't any fields in the template, all messages will match.
369 if (template == NULL) return(0);
370
371 // Null messages are bogus.
372 if (msg == NULL) return(1);
373
374 for (i='A'; i<='Z'; ++i) {
375 if (template->cm_fields[i] != NULL) {
376 if (msg->cm_fields[i] == NULL) {
377 // Considered equal if temmplate is empty string
378 if (IsEmptyStr(template->cm_fields[i])) continue;
379 return 1;
380 }
381 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
382 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
383 return 1;
384 }
385 }
386
387 /* All compares succeeded: we have a match! */
388 return 0;
389}
390
391
392// Retrieve the "seen" message list for the current room.
393void CtdlGetSeen(char *buf, int which_set) {
394 visit vbuf;
395
396 // Learn about the user and room in question
397 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
398
399 if (which_set == ctdlsetseen_seen) {
400 safestrncpy(buf, vbuf.v_seen, SIZ);
401 }
402 if (which_set == ctdlsetseen_answered) {
403 safestrncpy(buf, vbuf.v_answered, SIZ);
404 }
405}
406
407
408// Manipulate the "seen msgs" string (or other message set strings)
409void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
410 int target_setting, int which_set,
411 struct ctdluser *which_user, struct ctdlroom *which_room) {
412 struct cdbdata *cdbfr;
413 int i, k;
414 int is_seen = 0;
415 int was_seen = 0;
416 long lo = (-1L);
417 long hi = (-1L);
418 visit vbuf;
419 long *msglist;
420 int num_msgs = 0;
421 StrBuf *vset;
422 StrBuf *setstr;
423 StrBuf *lostr;
424 StrBuf *histr;
425 const char *pvset;
426 char *is_set; // actually an array of booleans
427
428 // Don't bother doing *anything* if we were passed a list of zero messages
429 if (num_target_msgnums < 1) {
430 return;
431 }
432
433 // If no room was specified, we go with the current room.
434 if (!which_room) {
435 which_room = &CC->room;
436 }
437
438 // If no user was specified, we go with the current user.
439 if (!which_user) {
440 which_user = &CC->user;
441 }
442
443 syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
444 num_target_msgnums, target_msgnums[0],
445 (target_setting ? "SET" : "CLEAR"),
446 which_set,
447 which_room->QRname);
448
449 // Learn about the user and room in question
450 CtdlGetRelationship(&vbuf, which_user, which_room);
451
452 // Load the message list
453 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
454 if (cdbfr != NULL) {
455 msglist = (long *) cdbfr->ptr;
456 cdbfr->ptr = NULL; // CtdlSetSeen() now owns this memory
457 num_msgs = cdbfr->len / sizeof(long);
458 cdb_free(cdbfr);
459 }
460 else {
461 return; // No messages at all? No further action.
462 }
463
464 is_set = malloc(num_msgs * sizeof(char));
465 memset(is_set, 0, (num_msgs * sizeof(char)) );
466
467 /* Decide which message set we're manipulating */
468 switch(which_set) {
469 case ctdlsetseen_seen:
470 vset = NewStrBufPlain(vbuf.v_seen, -1);
471 break;
473 vset = NewStrBufPlain(vbuf.v_answered, -1);
474 break;
475 default:
476 vset = NewStrBuf();
477 }
478
479
480#if 0 // This is a special diagnostic section. Do not allow it to run during normal operation.
481 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
482 for (i=0; i<num_msgs; ++i) {
483 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
484 }
485 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
486 for (k=0; k<num_target_msgnums; ++k) {
487 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
488 }
489#endif
490
491 // Translate the existing sequence set into an array of booleans
492 setstr = NewStrBuf();
493 lostr = NewStrBuf();
494 histr = NewStrBuf();
495 pvset = NULL;
496 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
497
498 StrBufExtract_token(lostr, setstr, 0, ':');
499 if (StrBufNum_tokens(setstr, ':') >= 2) {
500 StrBufExtract_token(histr, setstr, 1, ':');
501 }
502 else {
503 FlushStrBuf(histr);
504 StrBufAppendBuf(histr, lostr, 0);
505 }
506 lo = StrTol(lostr);
507 if (!strcmp(ChrPtr(histr), "*")) {
508 hi = LONG_MAX;
509 }
510 else {
511 hi = StrTol(histr);
512 }
513
514 for (i = 0; i < num_msgs; ++i) {
515 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
516 is_set[i] = 1;
517 }
518 }
519 }
520 FreeStrBuf(&setstr);
521 FreeStrBuf(&lostr);
522 FreeStrBuf(&histr);
523
524 // Now translate the array of booleans back into a sequence set
525 FlushStrBuf(vset);
526 was_seen = 0;
527 lo = (-1);
528 hi = (-1);
529
530 for (i=0; i<num_msgs; ++i) {
531 is_seen = is_set[i];
532
533 // Apply changes
534 for (k=0; k<num_target_msgnums; ++k) {
535 if (msglist[i] == target_msgnums[k]) {
536 is_seen = target_setting;
537 }
538 }
539
540 if ((was_seen == 0) && (is_seen == 1)) {
541 lo = msglist[i];
542 }
543 else if ((was_seen == 1) && (is_seen == 0)) {
544 hi = msglist[i-1];
545
546 if (StrLength(vset) > 0) {
547 StrBufAppendBufPlain(vset, HKEY(","), 0);
548 }
549 if (lo == hi) {
550 StrBufAppendPrintf(vset, "%ld", hi);
551 }
552 else {
553 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
554 }
555 }
556
557 if ((is_seen) && (i == num_msgs - 1)) {
558 if (StrLength(vset) > 0) {
559 StrBufAppendBufPlain(vset, HKEY(","), 0);
560 }
561 if ((i==0) || (was_seen == 0)) {
562 StrBufAppendPrintf(vset, "%ld", msglist[i]);
563 }
564 else {
565 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
566 }
567 }
568
569 was_seen = is_seen;
570 }
571
572 // We will have to stuff this string back into a 4096 byte buffer, so if it's
573 // larger than that now, truncate it by removing tokens from the beginning.
574 // The limit of 100 iterations is there to prevent an infinite loop in case
575 // something unexpected happens.
576 int number_of_truncations = 0;
577 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
578 StrBufRemove_token(vset, 0, ',');
579 ++number_of_truncations;
580 }
581
582 // If we're truncating the sequence set of messages marked with the 'seen' flag,
583 // we want the earliest messages (the truncated ones) to be marked, not unmarked.
584 // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
585 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
586 StrBuf *first_tok;
587 first_tok = NewStrBuf();
588 StrBufExtract_token(first_tok, vset, 0, ',');
589 StrBufRemove_token(vset, 0, ',');
590
591 if (StrBufNum_tokens(first_tok, ':') > 1) {
592 StrBufRemove_token(first_tok, 0, ':');
593 }
594
595 StrBuf *new_set;
596 new_set = NewStrBuf();
597 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
598 StrBufAppendBuf(new_set, first_tok, 0);
599 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
600 StrBufAppendBuf(new_set, vset, 0);
601
602 FreeStrBuf(&vset);
603 FreeStrBuf(&first_tok);
604 vset = new_set;
605 }
606
607 // Decide which message set we're manipulating
608 switch (which_set) {
609 case ctdlsetseen_seen:
610 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
611 break;
613 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
614 break;
615 }
616
617 free(is_set);
618 free(msglist);
619 CtdlSetRelationship(&vbuf, which_user, which_room);
620 FreeStrBuf(&vset);
621}
622
623
624// API function to perform an operation for each qualifying message in the
625// current room. (Returns the number of messages processed.)
626int CtdlForEachMessage(int mode, long ref, char *search_string,
627 char *content_type,
628 struct CtdlMessage *compare,
629 ForEachMsgCallback CallBack,
630 void *userdata)
631{
632 int a, i, j;
633 visit vbuf;
634 struct cdbdata *cdbfr;
635 long *msglist = NULL;
636 int num_msgs = 0;
637 int num_processed = 0;
638 long thismsg;
639 struct MetaData smi;
640 struct CtdlMessage *msg = NULL;
641 int is_seen = 0;
642 long lastold = 0L;
643 int printed_lastold = 0;
644 int num_search_msgs = 0;
645 long *search_msgs = NULL;
646 regex_t re;
647 int need_to_free_re = 0;
648 regmatch_t pm;
649
650 if ((content_type) && (!IsEmptyStr(content_type))) {
651 regcomp(&re, content_type, 0);
652 need_to_free_re = 1;
653 }
654
655 /* Learn about the user and room in question */
657 if (need_to_free_re) regfree(&re);
658 return -1;
659 }
660 CtdlGetUser(&CC->user, CC->curr_user);
661
663 if (need_to_free_re) regfree(&re);
664 return -1;
665 }
666 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
667
669 if (need_to_free_re) regfree(&re);
670 return -1;
671 }
672
673 /* Load the message list */
674 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
675 if (cdbfr == NULL) {
676 if (need_to_free_re) regfree(&re);
677 return 0; /* No messages at all? No further action. */
678 }
679
680 msglist = (long *) cdbfr->ptr;
681 num_msgs = cdbfr->len / sizeof(long);
682
683 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
684 cdb_free(cdbfr); /* we own this memory now */
685
686 /*
687 * Now begin the traversal.
688 */
689 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
690
691 /* If the caller is looking for a specific MIME type, filter
692 * out all messages which are not of the type requested.
693 */
694 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
695
696 /* This call to GetMetaData() sits inside this loop
697 * so that we only do the extra database read per msg
698 * if we need to. Doing the extra read all the time
699 * really kills the server. If we ever need to use
700 * metadata for another search criterion, we need to
701 * move the read somewhere else -- but still be smart
702 * enough to only do the read if the caller has
703 * specified something that will need it.
704 */
706 if (need_to_free_re) regfree(&re);
707 free(msglist);
708 return -1;
709 }
710 GetMetaData(&smi, msglist[a]);
711
712 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
713 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
714 msglist[a] = 0L;
715 }
716 }
717 }
718
719 num_msgs = sort_msglist(msglist, num_msgs);
720
721 /* If a template was supplied, filter out the messages which
722 * don't match. (This could induce some delays!)
723 */
724 if (num_msgs > 0) {
725 if (compare != NULL) {
726 for (a = 0; a < num_msgs; ++a) {
728 if (need_to_free_re) regfree(&re);
729 free(msglist);
730 return -1;
731 }
732 msg = CtdlFetchMessage(msglist[a], 1);
733 if (msg != NULL) {
734 if (CtdlMsgCmp(msg, compare)) {
735 msglist[a] = 0L;
736 }
737 CM_Free(msg);
738 }
739 }
740 }
741 }
742
743 /* If a search string was specified, get a message list from
744 * the full text index and remove messages which aren't on both
745 * lists.
746 *
747 * How this works:
748 * Since the lists are sorted and strictly ascending, and the
749 * output list is guaranteed to be shorter than or equal to the
750 * input list, we overwrite the bottom of the input list. This
751 * eliminates the need to memmove big chunks of the list over and
752 * over again.
753 */
754 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
755
756 /* Call search module via hook mechanism.
757 * NULL means use any search function available.
758 * otherwise replace with a char * to name of search routine
759 */
760 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
761
762 if (num_search_msgs > 0) {
763
764 int orig_num_msgs;
765
766 orig_num_msgs = num_msgs;
767 num_msgs = 0;
768 for (i=0; i<orig_num_msgs; ++i) {
769 for (j=0; j<num_search_msgs; ++j) {
770 if (msglist[i] == search_msgs[j]) {
771 msglist[num_msgs++] = msglist[i];
772 }
773 }
774 }
775 }
776 else {
777 num_msgs = 0; /* No messages qualify */
778 }
779 if (search_msgs != NULL) free(search_msgs);
780
781 /* Now that we've purged messages which don't contain the search
782 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
783 * point on.
784 */
785 mode = MSGS_ALL;
786 }
787
788 /*
789 * Now iterate through the message list, according to the
790 * criteria supplied by the caller.
791 */
792 if (num_msgs > 0)
793 for (a = 0; a < num_msgs; ++a) {
795 if (need_to_free_re) regfree(&re);
796 free(msglist);
797 return num_processed;
798 }
799 thismsg = msglist[a];
800 if (mode == MSGS_ALL) {
801 is_seen = 0;
802 }
803 else {
804 is_seen = is_msg_in_sequence_set(
805 vbuf.v_seen, thismsg);
806 if (is_seen) lastold = thismsg;
807 }
808 if ((thismsg > 0L)
809 && (
810
811 (mode == MSGS_ALL)
812 || ((mode == MSGS_OLD) && (is_seen))
813 || ((mode == MSGS_NEW) && (!is_seen))
814 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
815 || ((mode == MSGS_FIRST) && (a < ref))
816 || ((mode == MSGS_GT) && (thismsg > ref))
817 || ((mode == MSGS_LT) && (thismsg < ref))
818 || ((mode == MSGS_EQ) && (thismsg == ref))
819 )
820 ) {
821 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
822 if (CallBack) {
823 CallBack(lastold, userdata);
824 }
825 printed_lastold = 1;
826 ++num_processed;
827 }
828 if (CallBack) {
829 CallBack(thismsg, userdata);
830 }
831 ++num_processed;
832 }
833 }
834 if (need_to_free_re) regfree(&re);
835
836 /*
837 * We cache the most recent msglist in order to do security checks later
838 */
839 if (CC->client_socket > 0) {
840 if (CC->cached_msglist != NULL) {
841 free(CC->cached_msglist);
842 }
843 CC->cached_msglist = msglist;
844 CC->cached_num_msgs = num_msgs;
845 }
846 else {
847 free(msglist);
848 }
849
850 return num_processed;
851}
852
853
854/*
855 * memfmout() - Citadel text formatter and paginator.
856 * Although the original purpose of this routine was to format
857 * text to the reader's screen width, all we're really using it
858 * for here is to format text out to 80 columns before sending it
859 * to the client. The client software may reformat it again.
860 */
862 char *mptr, /* where are we going to get our text from? */
863 const char *nl /* string to terminate lines with */
864) {
865 int column = 0;
866 unsigned char ch = 0;
867 char outbuf[1024];
868 int len = 0;
869 int nllen = 0;
870
871 if (!mptr) return;
872 nllen = strlen(nl);
873 while (ch=*(mptr++), ch != 0) {
874
875 if (ch == '\n') {
876 if (client_write(outbuf, len) == -1) {
877 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
878 return;
879 }
880 len = 0;
881 if (client_write(nl, nllen) == -1) {
882 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
883 return;
884 }
885 column = 0;
886 }
887 else if (ch == '\r') {
888 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
889 }
890 else if (isspace(ch)) {
891 if (column > 72) { /* Beyond 72 columns, break on the next space */
892 if (client_write(outbuf, len) == -1) {
893 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
894 return;
895 }
896 len = 0;
897 if (client_write(nl, nllen) == -1) {
898 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
899 return;
900 }
901 column = 0;
902 }
903 else {
904 outbuf[len++] = ch;
905 ++column;
906 }
907 }
908 else {
909 outbuf[len++] = ch;
910 ++column;
911 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
912 if (client_write(outbuf, len) == -1) {
913 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
914 return;
915 }
916 len = 0;
917 if (client_write(nl, nllen) == -1) {
918 syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
919 return;
920 }
921 column = 0;
922 }
923 }
924 }
925 if (len) {
926 if (client_write(outbuf, len) == -1) {
927 syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
928 return;
929 }
930 client_write(nl, nllen);
931 column = 0;
932 }
933}
934
935
936/*
937 * Callback function for mime parser that simply lists the part
938 */
939void list_this_part(char *name, char *filename, char *partnum, char *disp,
940 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
941 char *cbid, void *cbuserdata)
942{
943 struct ma_info *ma;
944
945 ma = (struct ma_info *)cbuserdata;
946 if (ma->is_ma == 0) {
947 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
948 name,
949 filename,
950 partnum,
951 disp,
952 cbtype,
953 (long)length,
954 cbid,
955 cbcharset);
956 }
957}
958
959
960/*
961 * Callback function for multipart prefix
962 */
963void list_this_pref(char *name, char *filename, char *partnum, char *disp,
964 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
965 char *cbid, void *cbuserdata)
966{
967 struct ma_info *ma;
968
969 ma = (struct ma_info *)cbuserdata;
970 if (!strcasecmp(cbtype, "multipart/alternative")) {
971 ++ma->is_ma;
972 }
973
974 if (ma->is_ma == 0) {
975 cprintf("pref=%s|%s\n", partnum, cbtype);
976 }
977}
978
979
980/*
981 * Callback function for multipart sufffix
982 */
983void list_this_suff(char *name, char *filename, char *partnum, char *disp,
984 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
985 char *cbid, void *cbuserdata)
986{
987 struct ma_info *ma;
988
989 ma = (struct ma_info *)cbuserdata;
990 if (ma->is_ma == 0) {
991 cprintf("suff=%s|%s\n", partnum, cbtype);
992 }
993 if (!strcasecmp(cbtype, "multipart/alternative")) {
994 --ma->is_ma;
995 }
996}
997
998
999/*
1000 * Callback function for mime parser that opens a section for downloading
1001 * we use serv_files function here:
1002 */
1003extern void OpenCmdResult(char *filename, const char *mime_type);
1004void mime_download(char *name, char *filename, char *partnum, char *disp,
1005 void *content, char *cbtype, char *cbcharset, size_t length,
1006 char *encoding, char *cbid, void *cbuserdata)
1007{
1008 int rv = 0;
1009
1010 /* Silently go away if there's already a download open. */
1011 if (CC->download_fp != NULL)
1012 return;
1013
1014 if (
1015 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1016 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1017 ) {
1018 CC->download_fp = tmpfile();
1019 if (CC->download_fp == NULL) {
1020 syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
1021 cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
1022 return;
1023 }
1024
1025 rv = fwrite(content, length, 1, CC->download_fp);
1026 if (rv <= 0) {
1027 syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
1028 cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
1029 fclose(CC->download_fp);
1030 CC->download_fp = NULL;
1031 return;
1032 }
1033 fflush(CC->download_fp);
1034 rewind(CC->download_fp);
1035
1036 OpenCmdResult(filename, cbtype);
1037 }
1038}
1039
1040
1041/*
1042 * Callback function for mime parser that outputs a section all at once.
1043 * We can specify the desired section by part number *or* content-id.
1044 */
1045void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1046 void *content, char *cbtype, char *cbcharset, size_t length,
1047 char *encoding, char *cbid, void *cbuserdata)
1048{
1049 int *found_it = (int *)cbuserdata;
1050
1051 if (
1052 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1053 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1054 ) {
1055 *found_it = 1;
1056 cprintf("%d %d|-1|%s|%s|%s\n",
1058 (int)length,
1059 filename,
1060 cbtype,
1061 cbcharset
1062 );
1063 client_write(content, length);
1064 }
1065}
1066
1067
1068struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
1069{
1070 struct CtdlMessage *ret = NULL;
1071 const char *mptr;
1072 const char *upper_bound;
1073 cit_uint8_t ch;
1074 cit_uint8_t field_header;
1075 eMsgField which;
1076
1077 mptr = Buffer;
1078 upper_bound = Buffer + Length;
1079 if (msgnum <= 0) {
1080 return NULL;
1081 }
1082
1083 // Parse the three bytes that begin EVERY message on disk.
1084 // The first is always 0xFF, the on-disk magic number.
1085 // The second is the anonymous/public type byte.
1086 // The third is the format type byte (vari, fixed, or MIME).
1087 //
1088 ch = *mptr++;
1089 if (ch != 255) {
1090 syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
1091 return NULL;
1092 }
1093 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1094 memset(ret, 0, sizeof(struct CtdlMessage));
1095
1097 ret->cm_anon_type = *mptr++; // Anon type byte
1098 ret->cm_format_type = *mptr++; // Format type byte
1099
1100 // The rest is zero or more arbitrary fields. Load them in.
1101 // We're done when we encounter either a zero-length field or
1102 // have just processed the 'M' (message text) field.
1103 //
1104 do {
1105 field_header = '\0';
1106 long len;
1107
1108 while (field_header == '\0') { // work around possibly buggy messages
1109 if (mptr >= upper_bound) {
1110 break;
1111 }
1112 field_header = *mptr++;
1113 }
1114 if (mptr >= upper_bound) {
1115 break;
1116 }
1117 which = field_header;
1118 len = strlen(mptr);
1119
1120 CM_SetField(ret, which, mptr, len);
1121
1122 mptr += len + 1; // advance to next field
1123
1124 } while ((mptr < upper_bound) && (field_header != 'M'));
1125 return (ret);
1126}
1127
1128
1129// Load a message from disk into memory.
1130// This is used by CtdlOutputMsg() and other fetch functions.
1131//
1132// NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1133// using the CM_Free(); function.
1134//
1135struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
1136 struct cdbdata *dmsgtext;
1137 struct CtdlMessage *ret = NULL;
1138
1139 syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
1140 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1141 if (dmsgtext == NULL) {
1142 syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
1143 return NULL;
1144 }
1145
1146 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
1147 syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
1148 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1149 }
1150
1151 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
1152
1153 cdb_free(dmsgtext);
1154
1155 if (ret == NULL) {
1156 return NULL;
1157 }
1158
1159 // Always make sure there's something in the msg text field. If
1160 // it's NULL, the message text is most likely stored separately,
1161 // so go ahead and fetch that. Failing that, just set a dummy
1162 // body so other code doesn't barf.
1163 //
1164 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1165 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1166 if (dmsgtext != NULL) {
1167 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1168 cdb_free(dmsgtext);
1169 }
1170 }
1171 if (CM_IsEmpty(ret, eMesageText)) {
1172 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1173 }
1174
1175 return (ret);
1176}
1177
1178
1179// Pre callback function for multipart/alternative
1180//
1181// NOTE: this differs from the standard behavior for a reason. Normally when
1182// displaying multipart/alternative you want to show the _last_ usable
1183// format in the message. Here we show the _first_ one, because it's
1184// usually text/plain. Since this set of functions is designed for text
1185// output to non-MIME-aware clients, this is the desired behavior.
1186//
1187void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1188 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1189 char *cbid, void *cbuserdata)
1190{
1191 struct ma_info *ma;
1192
1193 ma = (struct ma_info *)cbuserdata;
1194 syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
1195 if (!strcasecmp(cbtype, "multipart/alternative")) {
1196 ++ma->is_ma;
1197 ma->did_print = 0;
1198 }
1199 if (!strcasecmp(cbtype, "message/rfc822")) {
1200 ++ma->freeze;
1201 }
1202}
1203
1204
1205//
1206// Post callback function for multipart/alternative
1207//
1208void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1209 void *content, char *cbtype, char *cbcharset, size_t length,
1210 char *encoding, char *cbid, void *cbuserdata)
1211{
1212 struct ma_info *ma;
1213
1214 ma = (struct ma_info *)cbuserdata;
1215 syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
1216 if (!strcasecmp(cbtype, "multipart/alternative")) {
1217 --ma->is_ma;
1218 ma->did_print = 0;
1219 }
1220 if (!strcasecmp(cbtype, "message/rfc822")) {
1221 --ma->freeze;
1222 }
1223}
1224
1225
1226// Inline callback function for mime parser that wants to display text
1227//
1228void fixed_output(char *name, char *filename, char *partnum, char *disp,
1229 void *content, char *cbtype, char *cbcharset, size_t length,
1230 char *encoding, char *cbid, void *cbuserdata)
1231{
1232 char *ptr;
1233 char *wptr;
1234 size_t wlen;
1235 struct ma_info *ma;
1236
1237 ma = (struct ma_info *)cbuserdata;
1238
1239 syslog(LOG_DEBUG,
1240 "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
1241 partnum, filename, cbtype, (long)length
1242 );
1243
1244 // If we're in the middle of a multipart/alternative scope and
1245 // we've already printed another section, skip this one.
1246 //
1247 if ( (ma->is_ma) && (ma->did_print) ) {
1248 syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
1249 return;
1250 }
1251 ma->did_print = 1;
1252
1253 if ( (!strcasecmp(cbtype, "text/plain"))
1254 || (IsEmptyStr(cbtype)) ) {
1255 wptr = content;
1256 if (length > 0) {
1257 client_write(wptr, length);
1258 if (wptr[length-1] != '\n') {
1259 cprintf("\n");
1260 }
1261 }
1262 return;
1263 }
1264
1265 if (!strcasecmp(cbtype, "text/html")) {
1266 ptr = html_to_ascii(content, length, 80);
1267 wlen = strlen(ptr);
1268 client_write(ptr, wlen);
1269 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1270 cprintf("\n");
1271 }
1272 free(ptr);
1273 return;
1274 }
1275
1276 if (ma->use_fo_hooks) {
1277 if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
1278 return;
1279 }
1280 }
1281
1282 if (strncasecmp(cbtype, "multipart/", 10)) {
1283 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1284 partnum, filename, cbtype, (long)length);
1285 return;
1286 }
1287}
1288
1289
1290// The client is elegant and sophisticated and wants to be choosy about
1291// MIME content types, so figure out which multipart/alternative part
1292// we're going to send.
1293//
1294// We use a system of weights. When we find a part that matches one of the
1295// MIME types we've declared as preferential, we can store it in ma->chosen_part
1296// and then set ma->chosen_pref to that MIME type's position in our preference
1297// list. If we then hit another match, we only replace the first match if
1298// the preference value is lower.
1299//
1300void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1301 void *content, char *cbtype, char *cbcharset, size_t length,
1302 char *encoding, char *cbid, void *cbuserdata)
1303{
1304 char buf[1024];
1305 int i;
1306 struct ma_info *ma;
1307
1308 ma = (struct ma_info *)cbuserdata;
1309
1310 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1311 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1312 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1313 if (i < ma->chosen_pref) {
1314 syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
1315 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1316 ma->chosen_pref = i;
1317 }
1318 }
1319 }
1320}
1321
1322
1323// Now that we've chosen our preferred part, output it.
1324//
1325void output_preferred(char *name,
1326 char *filename,
1327 char *partnum,
1328 char *disp,
1329 void *content,
1330 char *cbtype,
1331 char *cbcharset,
1332 size_t length,
1333 char *encoding,
1334 char *cbid,
1335 void *cbuserdata)
1336{
1337 int i;
1338 char buf[128];
1339 int add_newline = 0;
1340 char *text_content;
1341 struct ma_info *ma;
1342 char *decoded = NULL;
1343 size_t bytes_decoded;
1344 int rc = 0;
1345
1346 ma = (struct ma_info *)cbuserdata;
1347
1348 // This is not the MIME part you're looking for...
1349 if (strcasecmp(partnum, ma->chosen_part)) return;
1350
1351 // If the content-type of this part is in our preferred formats
1352 // list, we can simply output it verbatim.
1353 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1354 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1355 if (!strcasecmp(buf, cbtype)) {
1356 /* Yeah! Go! W00t!! */
1357 if (ma->dont_decode == 0)
1358 rc = mime_decode_now (content,
1359 length,
1360 encoding,
1361 &decoded,
1362 &bytes_decoded);
1363 if (rc < 0)
1364 break; // Give us the chance, maybe theres another one.
1365
1366 if (rc == 0) text_content = (char *)content;
1367 else {
1368 text_content = decoded;
1369 length = bytes_decoded;
1370 }
1371
1372 if (text_content[length-1] != '\n') {
1373 ++add_newline;
1374 }
1375 cprintf("Content-type: %s", cbtype);
1376 if (!IsEmptyStr(cbcharset)) {
1377 cprintf("; charset=%s", cbcharset);
1378 }
1379 cprintf("\nContent-length: %d\n",
1380 (int)(length + add_newline) );
1381 if (!IsEmptyStr(encoding)) {
1382 cprintf("Content-transfer-encoding: %s\n", encoding);
1383 }
1384 else {
1385 cprintf("Content-transfer-encoding: 7bit\n");
1386 }
1387 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1388 cprintf("\n");
1389 if (client_write(text_content, length) == -1)
1390 {
1391 syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
1392 return;
1393 }
1394 if (add_newline) cprintf("\n");
1395 if (decoded != NULL) free(decoded);
1396 return;
1397 }
1398 }
1399
1400 // No translations required or possible: output as text/plain
1401 cprintf("Content-type: text/plain\n\n");
1402 rc = 0;
1403 if (ma->dont_decode == 0)
1404 rc = mime_decode_now (content,
1405 length,
1406 encoding,
1407 &decoded,
1408 &bytes_decoded);
1409 if (rc < 0)
1410 return; // Give us the chance, maybe theres another one.
1411
1412 if (rc == 0) text_content = (char *)content;
1413 else {
1414 text_content = decoded;
1415 length = bytes_decoded;
1416 }
1417
1418 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
1419 if (decoded != NULL) free(decoded);
1420}
1421
1422
1423struct encapmsg {
1425 char *msg;
1426 size_t msglen;
1427};
1428
1429
1430// Callback function
1431void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1432 void *content, char *cbtype, char *cbcharset, size_t length,
1433 char *encoding, char *cbid, void *cbuserdata)
1434{
1435 struct encapmsg *encap;
1436
1437 encap = (struct encapmsg *)cbuserdata;
1438
1439 // Only proceed if this is the desired section...
1440 if (!strcasecmp(encap->desired_section, partnum)) {
1441 encap->msglen = length;
1442 encap->msg = malloc(length + 2);
1443 memcpy(encap->msg, content, length);
1444 return;
1445 }
1446}
1447
1448
1449// Determine whether the specified message exists in the cached_msglist
1450// (This is a security check)
1451int check_cached_msglist(long msgnum) {
1452
1453 // cases in which we skip the check
1454 if (!CC) return om_ok; // not a session
1455 if (CC->client_socket <= 0) return om_ok; // not a client session
1456 if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
1457 if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
1458
1459 // Do a binary search within the cached_msglist for the requested msgnum
1460 int min = 0;
1461 int max = (CC->cached_num_msgs - 1);
1462
1463 while (max >= min) {
1464 int middle = min + (max-min) / 2 ;
1465 if (msgnum == CC->cached_msglist[middle]) {
1466 return om_ok;
1467 }
1468 if (msgnum > CC->cached_msglist[middle]) {
1469 min = middle + 1;
1470 }
1471 else {
1472 max = middle - 1;
1473 }
1474 }
1475
1476 return om_access_denied;
1477}
1478
1479
1480// Get a message off disk. (returns om_* values found in msgbase.h)
1481int CtdlOutputMsg(long msg_num, // message number (local) to fetch
1482 int mode, // how would you like that message?
1483 int headers_only, // eschew the message body?
1484 int do_proto, // do Citadel protocol responses?
1485 int crlf, // Use CRLF newlines instead of LF?
1486 char *section, // NULL or a message/rfc822 section
1487 int flags, // various flags; see msgbase.h
1488 char **Author,
1489 char **Address,
1490 char **MessageID
1491) {
1492 struct CtdlMessage *TheMessage = NULL;
1493 int retcode = CIT_OK;
1494 struct encapmsg encap;
1495 int r;
1496
1497 syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
1498 msg_num, mode,
1499 (section ? section : "<>")
1500 );
1501
1503 if (r != om_ok) {
1504 if (do_proto) {
1505 if (r == om_not_logged_in) {
1506 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1507 }
1508 else {
1509 cprintf("%d An unknown error has occurred.\n", ERROR);
1510 }
1511 }
1512 return(r);
1513 }
1514
1515 /*
1516 * Check to make sure the message is actually IN this room
1517 */
1518 r = check_cached_msglist(msg_num);
1519 if (r == om_access_denied) {
1520 /* Not in the cache? We get ONE shot to check it again. */
1521 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1522 r = check_cached_msglist(msg_num);
1523 }
1524 if (r != om_ok) {
1525 syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
1526 msg_num, CC->room.QRname
1527 );
1528 if (do_proto) {
1529 if (r == om_access_denied) {
1530 cprintf("%d message %ld was not found in this room\n",
1532 msg_num
1533 );
1534 }
1535 }
1536 return(r);
1537 }
1538
1539 /*
1540 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1541 * request that we don't even bother loading the body into memory.
1542 */
1543 if (headers_only == HEADERS_FAST) {
1544 TheMessage = CtdlFetchMessage(msg_num, 0);
1545 }
1546 else {
1547 TheMessage = CtdlFetchMessage(msg_num, 1);
1548 }
1549
1550 if (TheMessage == NULL) {
1551 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1552 ERROR + MESSAGE_NOT_FOUND, msg_num);
1553 return(om_no_such_msg);
1554 }
1555
1556 /* Here is the weird form of this command, to process only an
1557 * encapsulated message/rfc822 section.
1558 */
1559 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1560 memset(&encap, 0, sizeof encap);
1561 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1562 mime_parser(CM_RANGE(TheMessage, eMesageText),
1564 NULL, NULL, (void *)&encap, 0
1565 );
1566
1567 if ((Author != NULL) && (*Author == NULL))
1568 {
1569 long len;
1570 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1571 }
1572 if ((Address != NULL) && (*Address == NULL))
1573 {
1574 long len;
1575 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1576 }
1577 if ((MessageID != NULL) && (*MessageID == NULL))
1578 {
1579 long len;
1580 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1581 }
1582 CM_Free(TheMessage);
1583 TheMessage = NULL;
1584
1585 if (encap.msg) {
1586 encap.msg[encap.msglen] = 0;
1587 TheMessage = convert_internet_message(encap.msg);
1588 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1589
1590 /* Now we let it fall through to the bottom of this
1591 * function, because TheMessage now contains the
1592 * encapsulated message instead of the top-level
1593 * message. Isn't that neat?
1594 */
1595 }
1596 else {
1597 if (do_proto) {
1598 cprintf("%d msg %ld has no part %s\n",
1600 msg_num,
1601 section);
1602 }
1603 retcode = om_no_such_msg;
1604 }
1605
1606 }
1607
1608 /* Ok, output the message now */
1609 if (retcode == CIT_OK)
1610 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1611 if ((Author != NULL) && (*Author == NULL))
1612 {
1613 long len;
1614 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1615 }
1616 if ((Address != NULL) && (*Address == NULL))
1617 {
1618 long len;
1619 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1620 }
1621 if ((MessageID != NULL) && (*MessageID == NULL))
1622 {
1623 long len;
1624 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1625 }
1626
1627 CM_Free(TheMessage);
1628
1629 return(retcode);
1630}
1631
1632
1633void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
1634 int i;
1635 char buf[SIZ];
1636 char display_name[256];
1637
1638 /* begin header processing loop for Citadel message format */
1639 safestrncpy(display_name, "<unknown>", sizeof display_name);
1640 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1641 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1642 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1643 safestrncpy(display_name, "****", sizeof display_name);
1644 }
1645 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1646 safestrncpy(display_name, "anonymous", sizeof display_name);
1647 }
1648 else {
1649 safestrncpy(display_name, buf, sizeof display_name);
1650 }
1651 if ((is_room_aide())
1652 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1653 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1654 size_t tmp = strlen(display_name);
1655 snprintf(&display_name[tmp],
1656 sizeof display_name - tmp,
1657 " [%s]", buf);
1658 }
1659 }
1660
1661 /* Now spew the header fields in the order we like them. */
1662 for (i=0; i< NDiskFields; ++i) {
1663 eMsgField Field;
1664 Field = FieldOrder[i];
1665 if (Field != eMesageText) {
1666 if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
1667 if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
1668 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1669 }
1670 if (Field == eAuthor) {
1671 if (do_proto) {
1672 cprintf("%s=%s\n", msgkeys[Field], display_name);
1673 }
1674 }
1675 /* Masquerade display name if needed */
1676 else {
1677 if (do_proto) {
1678 cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
1679 }
1680 }
1681 /* Give the client a hint about whether the message originated locally */
1682 if (Field == erFc822Addr) {
1683 if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
1684 cprintf("locl=yes\n"); // message originated locally.
1685 }
1686
1687
1688
1689 }
1690 }
1691 }
1692 }
1693}
1694
1695
1697 struct CtdlMessage *TheMessage,
1698 int flags, /* should the message be exported clean */
1699 const char *nl, int nlen,
1700 char *mid, long sizeof_mid,
1701 char *suser, long sizeof_suser,
1702 char *luser, long sizeof_luser,
1703 char *fuser, long sizeof_fuser,
1704 char *snode, long sizeof_snode)
1705{
1706 char datestamp[100];
1707 int subject_found = 0;
1708 char buf[SIZ];
1709 int i, j, k;
1710 char *mptr = NULL;
1711 char *mpptr = NULL;
1712 char *hptr;
1713
1714 for (i = 0; i < NDiskFields; ++i) {
1715 if (TheMessage->cm_fields[FieldOrder[i]]) {
1716 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1717 switch (FieldOrder[i]) {
1718 case eAuthor:
1719 safestrncpy(luser, mptr, sizeof_luser);
1720 safestrncpy(suser, mptr, sizeof_suser);
1721 break;
1722 case eCarbonCopY:
1723 if ((flags & QP_EADDR) != 0) {
1724 mptr = qp_encode_email_addrs(mptr);
1725 }
1727 cprintf("CC: %s%s", mptr, nl);
1728 break;
1729 case eMessagePath:
1730 cprintf("Return-Path: %s%s", mptr, nl);
1731 break;
1732 case eListID:
1733 cprintf("List-ID: %s%s", mptr, nl);
1734 break;
1735 case eenVelopeTo:
1736 if ((flags & QP_EADDR) != 0)
1737 mptr = qp_encode_email_addrs(mptr);
1738 hptr = mptr;
1739 while ((*hptr != '\0') && isspace(*hptr))
1740 hptr ++;
1741 if (!IsEmptyStr(hptr))
1742 cprintf("Envelope-To: %s%s", hptr, nl);
1743 break;
1744 case eMsgSubject:
1745 cprintf("Subject: %s%s", mptr, nl);
1746 subject_found = 1;
1747 break;
1748 case emessageId:
1749 safestrncpy(mid, mptr, sizeof_mid);
1750 break;
1751 case erFc822Addr:
1752 safestrncpy(fuser, mptr, sizeof_fuser);
1753 break;
1754 case eRecipient:
1755 if (haschar(mptr, '@') == 0) {
1757 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1758 cprintf("%s", nl);
1759 }
1760 else {
1761 if ((flags & QP_EADDR) != 0) {
1762 mptr = qp_encode_email_addrs(mptr);
1763 }
1765 cprintf("To: %s", mptr);
1766 cprintf("%s", nl);
1767 }
1768 break;
1769 case eTimestamp:
1770 datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
1771 cprintf("Date: %s%s", datestamp, nl);
1772 break;
1773 case eWeferences:
1774 cprintf("References: ");
1775 k = num_tokens(mptr, '|');
1776 for (j=0; j<k; ++j) {
1777 extract_token(buf, mptr, j, '|', sizeof buf);
1778 cprintf("<%s>", buf);
1779 if (j == (k-1)) {
1780 cprintf("%s", nl);
1781 }
1782 else {
1783 cprintf(" ");
1784 }
1785 }
1786 break;
1787 case eReplyTo:
1788 hptr = mptr;
1789 while ((*hptr != '\0') && isspace(*hptr))
1790 hptr ++;
1791 if (!IsEmptyStr(hptr))
1792 cprintf("Reply-To: %s%s", mptr, nl);
1793 break;
1794
1795 case eExclusiveID:
1796 case eJournal:
1797 case eMesageText:
1798 case eBig_message:
1799 case eOriginalRoom:
1800 case eErrorMsg:
1801 case eSuppressIdx:
1802 case eExtnotify:
1803 case eVltMsgNum:
1804 /* these don't map to mime message headers. */
1805 break;
1806 }
1807 if (mptr != mpptr) {
1808 free (mptr);
1809 }
1810 }
1811 }
1812 if (subject_found == 0) {
1813 cprintf("Subject: (no subject)%s", nl);
1814 }
1815}
1816
1817
1819 struct CtdlMessage *TheMessage,
1820 int headers_only, /* eschew the message body? */
1821 int flags, /* should the bessage be exported clean? */
1822 const char *nl, int nlen)
1823{
1824 cit_uint8_t prev_ch;
1825 int eoh = 0;
1826 const char *StartOfText = StrBufNOTNULL;
1827 char outbuf[1024];
1828 int outlen = 0;
1829 int nllen = strlen(nl);
1830 char *mptr;
1831 int lfSent = 0;
1832
1833 mptr = TheMessage->cm_fields[eMesageText];
1834
1835 prev_ch = '\0';
1836 while (*mptr != '\0') {
1837 if (*mptr == '\r') {
1838 /* do nothing */
1839 }
1840 else {
1841 if ((!eoh) &&
1842 (*mptr == '\n'))
1843 {
1844 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1845 if (!eoh)
1846 eoh = *(mptr+1) == '\n';
1847 if (eoh)
1848 {
1849 StartOfText = mptr;
1850 StartOfText = strchr(StartOfText, '\n');
1851 StartOfText = strchr(StartOfText, '\n');
1852 }
1853 }
1854 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1855 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1856 ((headers_only != HEADERS_NONE) &&
1857 (headers_only != HEADERS_ONLY))
1858 ) {
1859 if (*mptr == '\n') {
1860 memcpy(&outbuf[outlen], nl, nllen);
1861 outlen += nllen;
1862 outbuf[outlen] = '\0';
1863 }
1864 else {
1865 outbuf[outlen++] = *mptr;
1866 }
1867 }
1868 }
1869 if (flags & ESC_DOT) {
1870 if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
1871 outbuf[outlen++] = '.';
1872 }
1873 prev_ch = *mptr;
1874 }
1875 ++mptr;
1876 if (outlen > 1000) {
1877 if (client_write(outbuf, outlen) == -1) {
1878 syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
1879 return;
1880 }
1881 lfSent = (outbuf[outlen - 1] == '\n');
1882 outlen = 0;
1883 }
1884 }
1885 if (outlen > 0) {
1886 client_write(outbuf, outlen);
1887 lfSent = (outbuf[outlen - 1] == '\n');
1888 }
1889 if (!lfSent)
1890 client_write(nl, nlen);
1891}
1892
1893
1894/* If the format type on disk is 1 (fixed-format), then we want
1895 * everything to be output completely literally ... regardless of
1896 * what message transfer format is in use.
1897 */
1899 struct CtdlMessage *TheMessage,
1900 int mode, /* how would you like that message? */
1901 const char *nl, int nllen)
1902{
1903 cit_uint8_t ch;
1904 char buf[SIZ];
1905 int buflen;
1906 int xlline = 0;
1907 char *mptr;
1908
1909 mptr = TheMessage->cm_fields[eMesageText];
1910
1911 if (mode == MT_MIME) {
1912 cprintf("Content-type: text/plain\n\n");
1913 }
1914 *buf = '\0';
1915 buflen = 0;
1916 while (ch = *mptr++, ch > 0) {
1917 if (ch == '\n')
1918 ch = '\r';
1919
1920 if ((buflen > 250) && (!xlline)){
1921 int tbuflen;
1922 tbuflen = buflen;
1923
1924 while ((buflen > 0) &&
1925 (!isspace(buf[buflen])))
1926 buflen --;
1927 if (buflen == 0) {
1928 xlline = 1;
1929 }
1930 else {
1931 mptr -= tbuflen - buflen;
1932 buf[buflen] = '\0';
1933 ch = '\r';
1934 }
1935 }
1936
1937 /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
1938 if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
1939 ch = '\r';
1940 }
1941
1942 if (ch == '\r') {
1943 memcpy (&buf[buflen], nl, nllen);
1944 buflen += nllen;
1945 buf[buflen] = '\0';
1946
1947 if (client_write(buf, buflen) == -1) {
1948 syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
1949 return;
1950 }
1951 *buf = '\0';
1952 buflen = 0;
1953 xlline = 0;
1954 } else {
1955 buf[buflen] = ch;
1956 buflen++;
1957 }
1958 }
1959 buf[buflen] = '\0';
1960 if (!IsEmptyStr(buf)) {
1961 cprintf("%s%s", buf, nl);
1962 }
1963}
1964
1965
1966/*
1967 * Get a message off disk. (returns om_* values found in msgbase.h)
1968 */
1970 struct CtdlMessage *TheMessage,
1971 int mode, /* how would you like that message? */
1972 int headers_only, /* eschew the message body? */
1973 int do_proto, /* do Citadel protocol responses? */
1974 int crlf, /* Use CRLF newlines instead of LF? */
1975 int flags /* should the bessage be exported clean? */
1976) {
1977 int i;
1978 const char *nl; /* newline string */
1979 int nlen;
1980 struct ma_info ma;
1981
1982 /* Buffers needed for RFC822 translation. These are all filled
1983 * using functions that are bounds-checked, and therefore we can
1984 * make them substantially smaller than SIZ.
1985 */
1986 char suser[1024];
1987 char luser[1024];
1988 char fuser[1024];
1989 char snode[1024];
1990 char mid[1024];
1991
1992 syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
1993 ((TheMessage == NULL) ? "NULL" : "not null"),
1994 mode, headers_only, do_proto, crlf
1995 );
1996
1997 strcpy(mid, "unknown");
1998 nl = (crlf ? "\r\n" : "\n");
1999 nlen = crlf ? 2 : 1;
2000
2001 if (!CM_IsValidMsg(TheMessage)) {
2002 syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
2003 return(om_no_such_msg);
2004 }
2005
2006 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2007 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2008 */
2009 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2010 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2011 }
2012
2013 /* Are we downloading a MIME component? */
2014 if (mode == MT_DOWNLOAD) {
2015 if (TheMessage->cm_format_type != FMT_RFC822) {
2016 if (do_proto)
2017 cprintf("%d This is not a MIME message.\n",
2019 } else if (CC->download_fp != NULL) {
2020 if (do_proto) cprintf(
2021 "%d You already have a download open.\n",
2023 } else {
2024 /* Parse the message text component */
2025 mime_parser(CM_RANGE(TheMessage, eMesageText),
2026 *mime_download, NULL, NULL, NULL, 0);
2027 /* If there's no file open by this time, the requested
2028 * section wasn't found, so print an error
2029 */
2030 if (CC->download_fp == NULL) {
2031 if (do_proto) cprintf(
2032 "%d Section %s not found.\n",
2034 CC->download_desired_section);
2035 }
2036 }
2037 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2038 }
2039
2040 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2041 * in a single server operation instead of opening a download file.
2042 */
2043 if (mode == MT_SPEW_SECTION) {
2044 if (TheMessage->cm_format_type != FMT_RFC822) {
2045 if (do_proto)
2046 cprintf("%d This is not a MIME message.\n",
2048 } else {
2049 /* Parse the message text component */
2050 int found_it = 0;
2051
2052 mime_parser(CM_RANGE(TheMessage, eMesageText),
2053 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2054 /* If section wasn't found, print an error
2055 */
2056 if (!found_it) {
2057 if (do_proto) cprintf(
2058 "%d Section %s not found.\n",
2060 CC->download_desired_section);
2061 }
2062 }
2063 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2064 }
2065
2066 /* now for the user-mode message reading loops */
2067 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2068
2069 /* Does the caller want to skip the headers? */
2070 if (headers_only == HEADERS_NONE) goto START_TEXT;
2071
2072 /* Tell the client which format type we're using. */
2073 if ( (mode == MT_CITADEL) && (do_proto) ) {
2074 cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
2075 }
2076
2077 /* nhdr=yes means that we're only displaying headers, no body */
2078 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2079 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2080 && (do_proto)
2081 ) {
2082 cprintf("nhdr=yes\n");
2083 }
2084
2085 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
2086 OutputCtdlMsgHeaders(TheMessage, do_proto);
2087 }
2088
2089 /* begin header processing loop for RFC822 transfer format */
2090 strcpy(suser, "");
2091 strcpy(luser, "");
2092 strcpy(fuser, "");
2093 strcpy(snode, "");
2094 if (mode == MT_RFC822)
2096 TheMessage,
2097 flags,
2098 nl, nlen,
2099 mid, sizeof(mid),
2100 suser, sizeof(suser),
2101 luser, sizeof(luser),
2102 fuser, sizeof(fuser),
2103 snode, sizeof(snode)
2104 );
2105
2106
2107 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2108 suser[i] = tolower(suser[i]);
2109 if (!isalnum(suser[i])) suser[i]='_';
2110 }
2111
2112 if (mode == MT_RFC822) {
2113 /* Construct a fun message id */
2114 cprintf("Message-ID: <%s", mid);
2115 if (strchr(mid, '@')==NULL) {
2116 cprintf("@%s", snode);
2117 }
2118 cprintf(">%s", nl);
2119
2120 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2121 cprintf("From: \"----\" <x@x.org>%s", nl);
2122 }
2123 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2124 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2125 }
2126 else if (!IsEmptyStr(fuser)) {
2127 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2128 }
2129 else {
2130 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2131 }
2132
2133 /* Blank line signifying RFC822 end-of-headers */
2134 if (TheMessage->cm_format_type != FMT_RFC822) {
2135 cprintf("%s", nl);
2136 }
2137 }
2138
2139 /* end header processing loop ... at this point, we're in the text */
2140START_TEXT:
2141 if (headers_only == HEADERS_FAST) goto DONE;
2142
2143 /* Tell the client about the MIME parts in this message */
2144 if (TheMessage->cm_format_type == FMT_RFC822) {
2145 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2146 memset(&ma, 0, sizeof(struct ma_info));
2147 mime_parser(CM_RANGE(TheMessage, eMesageText),
2148 (do_proto ? *list_this_part : NULL),
2149 (do_proto ? *list_this_pref : NULL),
2150 (do_proto ? *list_this_suff : NULL),
2151 (void *)&ma, 1);
2152 }
2153 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2155 TheMessage,
2156 headers_only,
2157 flags,
2158 nl, nlen);
2159 goto DONE;
2160 }
2161 }
2162
2163 if (headers_only == HEADERS_ONLY) {
2164 goto DONE;
2165 }
2166
2167 /* signify start of msg text */
2168 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2169 if (do_proto) cprintf("text\n");
2170 }
2171
2172 if (TheMessage->cm_format_type == FMT_FIXED)
2174 TheMessage,
2175 mode, /* how would you like that message? */
2176 nl, nlen);
2177
2178 /* If the message on disk is format 0 (Citadel vari-format), we
2179 * output using the formatter at 80 columns. This is the final output
2180 * form if the transfer format is RFC822, but if the transfer format
2181 * is Citadel proprietary, it'll still work, because the indentation
2182 * for new paragraphs is correct and the client will reformat the
2183 * message to the reader's screen width.
2184 */
2185 if (TheMessage->cm_format_type == FMT_CITADEL) {
2186 if (mode == MT_MIME) {
2187 cprintf("Content-type: text/x-citadel-variformat\n\n");
2188 }
2189 memfmout(TheMessage->cm_fields[eMesageText], nl);
2190 }
2191
2192 /* If the message on disk is format 4 (MIME), we've gotta hand it
2193 * off to the MIME parser. The client has already been told that
2194 * this message is format 1 (fixed format), so the callback function
2195 * we use will display those parts as-is.
2196 */
2197 if (TheMessage->cm_format_type == FMT_RFC822) {
2198 memset(&ma, 0, sizeof(struct ma_info));
2199
2200 if (mode == MT_MIME) {
2201 ma.use_fo_hooks = 0;
2202 strcpy(ma.chosen_part, "1");
2203 ma.chosen_pref = 9999;
2204 ma.dont_decode = CC->msg4_dont_decode;
2205 mime_parser(CM_RANGE(TheMessage, eMesageText),
2207 *fixed_output_post, (void *)&ma, 1);
2208 mime_parser(CM_RANGE(TheMessage, eMesageText),
2209 *output_preferred, NULL, NULL, (void *)&ma, 1);
2210 }
2211 else {
2212 ma.use_fo_hooks = 1;
2213 mime_parser(CM_RANGE(TheMessage, eMesageText),
2215 *fixed_output_post, (void *)&ma, 0);
2216 }
2217
2218 }
2219
2220DONE: /* now we're done */
2221 if (do_proto) cprintf("000\n");
2222 return(om_ok);
2223}
2224
2225/*
2226 * Save one or more message pointers into a specified room
2227 * (Returns 0 for success, nonzero for failure)
2228 * roomname may be NULL to use the current room
2229 *
2230 * Note that the 'supplied_msg' field may be set to NULL, in which case
2231 * the message will be fetched from disk, by number, if we need to perform
2232 * replication checks. This adds an additional database read, so if the
2233 * caller already has the message in memory then it should be supplied. (Obviously
2234 * this mode of operation only works if we're saving a single message.)
2235 */
2236int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2237 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2238) {
2239 int i, j, unique;
2240 char hold_rm[ROOMNAMELEN];
2241 struct cdbdata *cdbfr;
2242 int num_msgs;
2243 long *msglist;
2244 long highest_msg = 0L;
2245
2246 long msgid = 0;
2247 struct CtdlMessage *msg = NULL;
2248
2249 long *msgs_to_be_merged = NULL;
2250 int num_msgs_to_be_merged = 0;
2251
2252 syslog(LOG_DEBUG,
2253 "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
2254 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2255 );
2256
2257 strcpy(hold_rm, CC->room.QRname);
2258
2259 /* Sanity checks */
2260 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2261 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2262 if (num_newmsgs > 1) supplied_msg = NULL;
2263
2264 /* Now the regular stuff */
2265 if (CtdlGetRoomLock(&CC->room,
2266 ((roomname != NULL) ? roomname : CC->room.QRname) )
2267 != 0) {
2268 syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
2269 return(ERROR + ROOM_NOT_FOUND);
2270 }
2271
2272
2273 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2274 num_msgs_to_be_merged = 0;
2275
2276
2277 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2278 if (cdbfr == NULL) {
2279 msglist = NULL;
2280 num_msgs = 0;
2281 } else {
2282 msglist = (long *) cdbfr->ptr;
2283 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2284 num_msgs = cdbfr->len / sizeof(long);
2285 cdb_free(cdbfr);
2286 }
2287
2288
2289 /* Create a list of msgid's which were supplied by the caller, but do
2290 * not already exist in the target room. It is absolutely taboo to
2291 * have more than one reference to the same message in a room.
2292 */
2293 for (i=0; i<num_newmsgs; ++i) {
2294 unique = 1;
2295 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2296 if (msglist[j] == newmsgidlist[i]) {
2297 unique = 0;
2298 }
2299 }
2300 if (unique) {
2301 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2302 }
2303 }
2304
2305 syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
2306
2307 /*
2308 * Now merge the new messages
2309 */
2310 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2311 if (msglist == NULL) {
2312 syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
2313 free(msgs_to_be_merged);
2314 return (ERROR + INTERNAL_ERROR);
2315 }
2316 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2317 num_msgs += num_msgs_to_be_merged;
2318
2319 /* Sort the message list, so all the msgid's are in order */
2320 num_msgs = sort_msglist(msglist, num_msgs);
2321
2322 /* Determine the highest message number */
2323 highest_msg = msglist[num_msgs - 1];
2324
2325 /* Write it back to disk. */
2326 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2327 msglist, (int)(num_msgs * sizeof(long)));
2328
2329 /* Free up the memory we used. */
2330 free(msglist);
2331
2332 /* Update the highest-message pointer and unlock the room. */
2333 CC->room.QRhighest = highest_msg;
2334 CtdlPutRoomLock(&CC->room);
2335
2336 /* Perform replication checks if necessary */
2337 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2338 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
2339
2340 for (i=0; i<num_msgs_to_be_merged; ++i) {
2341 msgid = msgs_to_be_merged[i];
2342
2343 if (supplied_msg != NULL) {
2344 msg = supplied_msg;
2345 }
2346 else {
2347 msg = CtdlFetchMessage(msgid, 0);
2348 }
2349
2350 if (msg != NULL) {
2351 ReplicationChecks(msg);
2352
2353 /* If the message has an Exclusive ID, index that... */
2354 if (!CM_IsEmpty(msg, eExclusiveID)) {
2355 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2356 }
2357
2358 /* Free up the memory we may have allocated */
2359 if (msg != supplied_msg) {
2360 CM_Free(msg);
2361 }
2362 }
2363
2364 }
2365 }
2366
2367 else {
2368 syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
2369 }
2370
2371 /* Submit this room for processing by hooks */
2372 int total_roomhook_errors = PerformRoomHooks(&CC->room);
2373 if (total_roomhook_errors) {
2374 syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
2375 }
2376
2377 /* Go back to the room we were in before we wandered here... */
2378 CtdlGetRoom(&CC->room, hold_rm);
2379
2380 /* Bump the reference count for all messages which were merged */
2381 if (!suppress_refcount_adj) {
2382 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2383 }
2384
2385 /* Free up memory... */
2386 if (msgs_to_be_merged != NULL) {
2387 free(msgs_to_be_merged);
2388 }
2389
2390 /* Return success. */
2391 return (0);
2392}
2393
2394
2395/*
2396 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2397 * a single message.
2398 */
2399int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2400 int do_repl_check, struct CtdlMessage *supplied_msg)
2401{
2402 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2403}
2404
2405
2406/*
2407 * Message base operation to save a new message to the message store
2408 * (returns new message number)
2409 *
2410 * This is the back end for CtdlSubmitMsg() and should not be directly
2411 * called by server-side modules.
2412 *
2413 */
2414long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2415 long retval;
2416 struct ser_ret smr;
2417 int is_bigmsg = 0;
2418 char *holdM = NULL;
2419 long holdMLen = 0;
2420
2421 /*
2422 * If the message is big, set its body aside for storage elsewhere
2423 * and we hide the message body from the serializer
2424 */
2425 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
2426 is_bigmsg = 1;
2427 holdM = msg->cm_fields[eMesageText];
2428 msg->cm_fields[eMesageText] = NULL;
2429 holdMLen = msg->cm_lengths[eMesageText];
2430 msg->cm_lengths[eMesageText] = 0;
2431 }
2432
2433 /* Serialize our data structure for storage in the database */
2434 CtdlSerializeMessage(&smr, msg);
2435
2436 if (is_bigmsg) {
2437 /* put the message body back into the message */
2438 msg->cm_fields[eMesageText] = holdM;
2439 msg->cm_lengths[eMesageText] = holdMLen;
2440 }
2441
2442 if (smr.len == 0) {
2443 if (Reply) {
2444 cprintf("%d Unable to serialize message\n",
2446 }
2447 else {
2448 syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
2449
2450 }
2451 return (-1L);
2452 }
2453
2454 /* Write our little bundle of joy into the message base */
2455 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
2456 if (retval < 0) {
2457 syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
2458 }
2459 else {
2460 if (is_bigmsg) {
2461 retval = cdb_store(CDB_BIGMSGS,
2462 &msgid,
2463 (int)sizeof(long),
2464 holdM,
2465 (holdMLen + 1)
2466 );
2467 if (retval < 0) {
2468 syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
2469 }
2470 }
2471 }
2472
2473 /* Free the memory we used for the serialized message */
2474 free(smr.ser);
2475
2476 return(retval);
2477}
2478
2479
2480long send_message(struct CtdlMessage *msg) {
2481 long newmsgid;
2482 long retval;
2483 char msgidbuf[256];
2484 long msgidbuflen;
2485
2486 /* Get a new message number */
2487 newmsgid = get_new_message_number();
2488
2489 /* Generate an ID if we don't have one already */
2490 if (CM_IsEmpty(msg, emessageId)) {
2491 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2492 (long unsigned int) time(NULL),
2493 (long unsigned int) newmsgid,
2494 CtdlGetConfigStr("c_fqdn")
2495 );
2496
2497 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2498 }
2499
2500 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2501
2502 if (retval == 0) {
2503 retval = newmsgid;
2504 }
2505
2506 /* Return the *local* message ID to the caller
2507 * (even if we're storing an incoming network message)
2508 */
2509 return(retval);
2510}
2511
2512
2513/*
2514 * Serialize a struct CtdlMessage into the format used on disk.
2515 *
2516 * This function loads up a "struct ser_ret" (defined in server.h) which
2517 * contains the length of the serialized message and a pointer to the
2518 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2519 */
2520void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2521 struct CtdlMessage *msg) /* unserialized msg */
2522{
2523 size_t wlen;
2524 int i;
2525
2526 /*
2527 * Check for valid message format
2528 */
2529 if (CM_IsValidMsg(msg) == 0) {
2530 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
2531 ret->len = 0;
2532 ret->ser = NULL;
2533 return;
2534 }
2535
2536 ret->len = 3;
2537 for (i=0; i < NDiskFields; ++i)
2538 if (msg->cm_fields[FieldOrder[i]] != NULL)
2539 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2540
2541 ret->ser = malloc(ret->len);
2542 if (ret->ser == NULL) {
2543 syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
2544 ret->len = 0;
2545 ret->ser = NULL;
2546 return;
2547 }
2548
2549 ret->ser[0] = 0xFF;
2550 ret->ser[1] = msg->cm_anon_type;
2551 ret->ser[2] = msg->cm_format_type;
2552 wlen = 3;
2553
2554 for (i=0; i < NDiskFields; ++i) {
2555 if (msg->cm_fields[FieldOrder[i]] != NULL) {
2556 ret->ser[wlen++] = (char)FieldOrder[i];
2557
2558 memcpy(&ret->ser[wlen],
2559 msg->cm_fields[FieldOrder[i]],
2560 msg->cm_lengths[FieldOrder[i]] + 1);
2561
2562 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2563 }
2564 }
2565
2566 if (ret->len != wlen) {
2567 syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
2568 }
2569
2570 return;
2571}
2572
2573
2574/*
2575 * Check to see if any messages already exist in the current room which
2576 * carry the same Exclusive ID as this one. If any are found, delete them.
2577 */
2579 long old_msgnum = (-1L);
2580
2581 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2582
2583 syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
2584
2585 /* No exclusive id? Don't do anything. */
2586 if (msg == NULL) return;
2587 if (CM_IsEmpty(msg, eExclusiveID)) return;
2588
2589 /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
2590 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2591
2592 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2593 if (old_msgnum > 0L) {
2594 syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
2595 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2596 }
2597}
2598
2599
2600/*
2601 * Save a message to disk and submit it into the delivery system.
2602 */
2603long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2604 struct recptypes *recps, /* recipients (if mail) */
2605 const char *force /* force a particular room? */
2606) {
2607 char hold_rm[ROOMNAMELEN];
2608 char actual_rm[ROOMNAMELEN];
2609 char force_room[ROOMNAMELEN];
2610 char content_type[SIZ]; /* We have to learn this */
2611 char recipient[SIZ];
2612 char bounce_to[1024];
2613 const char *room;
2614 long newmsgid;
2615 const char *mptr = NULL;
2616 struct ctdluser userbuf;
2617 int a, i;
2618 struct MetaData smi;
2619 char *collected_addresses = NULL;
2620 struct addresses_to_be_filed *aptr = NULL;
2621 StrBuf *saved_rfc822_version = NULL;
2622 int qualified_for_journaling = 0;
2623
2624 syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
2625 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2626
2627 /* If this message has no timestamp, we take the liberty of
2628 * giving it one, right now.
2629 */
2630 if (CM_IsEmpty(msg, eTimestamp)) {
2631 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2632 }
2633
2634 /* If this message has no path, we generate one.
2635 */
2636 if (CM_IsEmpty(msg, eMessagePath)) {
2637 if (!CM_IsEmpty(msg, eAuthor)) {
2639 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2640 if (isspace(msg->cm_fields[eMessagePath][a])) {
2641 msg->cm_fields[eMessagePath][a] = ' ';
2642 }
2643 }
2644 }
2645 else {
2646 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2647 }
2648 }
2649
2650 if (force == NULL) {
2651 force_room[0] = '\0';
2652 }
2653 else {
2654 strcpy(force_room, force);
2655 }
2656
2657 /* Learn about what's inside, because it's what's inside that counts */
2658 if (CM_IsEmpty(msg, eMesageText)) {
2659 syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
2660 return(-2);
2661 }
2662
2663 switch (msg->cm_format_type) {
2664 case 0:
2665 strcpy(content_type, "text/x-citadel-variformat");
2666 break;
2667 case 1:
2668 strcpy(content_type, "text/plain");
2669 break;
2670 case 4:
2671 strcpy(content_type, "text/plain");
2672 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2673 if (mptr != NULL) {
2674 char *aptr;
2675 safestrncpy(content_type, &mptr[13], sizeof content_type);
2676 striplt(content_type);
2677 aptr = content_type;
2678 while (!IsEmptyStr(aptr)) {
2679 if ((*aptr == ';')
2680 || (*aptr == ' ')
2681 || (*aptr == 13)
2682 || (*aptr == 10)) {
2683 *aptr = 0;
2684 }
2685 else aptr++;
2686 }
2687 }
2688 }
2689
2690 /* Goto the correct room */
2691 room = (recps) ? CC->room.QRname : SENTITEMS;
2692 syslog(LOG_DEBUG, "msgbase: selected room %s", room);
2693 strcpy(hold_rm, CC->room.QRname);
2694 strcpy(actual_rm, CC->room.QRname);
2695 if (recps != NULL) {
2696 strcpy(actual_rm, SENTITEMS);
2697 }
2698
2699 /* If the user is a twit, move to the twit room for posting */
2700 if (TWITDETECT) {
2701 if (CC->user.axlevel == AxProbU) {
2702 strcpy(hold_rm, actual_rm);
2703 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2704 syslog(LOG_DEBUG, "msgbase: diverting to twit room");
2705 }
2706 }
2707
2708 /* ...or if this message is destined for Aide> then go there. */
2709 if (!IsEmptyStr(force_room)) {
2710 strcpy(actual_rm, force_room);
2711 }
2712
2713 syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
2714 if (strcasecmp(actual_rm, CC->room.QRname)) {
2715 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2716 }
2717
2718 /*
2719 * If this message has no O (room) field, generate one.
2720 */
2721 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2722 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
2723 }
2724
2725 /* Perform "before save" hooks (aborting if any return nonzero) */
2726 syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
2727 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2728
2729 /*
2730 * If this message has an Exclusive ID, and the room is replication
2731 * checking enabled, then do replication checks.
2732 */
2733 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2734 ReplicationChecks(msg);
2735 }
2736
2737 /* Save it to disk */
2738 syslog(LOG_DEBUG, "msgbase: saving to disk");
2739 newmsgid = send_message(msg);
2740 if (newmsgid <= 0L) return(-5);
2741
2742 /* Write a supplemental message info record. This doesn't have to
2743 * be a critical section because nobody else knows about this message
2744 * yet.
2745 */
2746 syslog(LOG_DEBUG, "msgbase: creating metadata record");
2747 memset(&smi, 0, sizeof(struct MetaData));
2748 smi.meta_msgnum = newmsgid;
2749 smi.meta_refcount = 0;
2750 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2751
2752 /*
2753 * Measure how big this message will be when rendered as RFC822.
2754 * We do this for two reasons:
2755 * 1. We need the RFC822 length for the new metadata record, so the
2756 * POP and IMAP services don't have to calculate message lengths
2757 * while the user is waiting (multiplied by potentially hundreds
2758 * or thousands of messages).
2759 * 2. If journaling is enabled, we will need an RFC822 version of the
2760 * message to attach to the journalized copy.
2761 */
2762 if (CC->redirect_buffer != NULL) {
2763 syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
2764 abort();
2765 }
2766 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2768 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2769 saved_rfc822_version = CC->redirect_buffer;
2770 CC->redirect_buffer = NULL;
2771
2772 PutMetaData(&smi);
2773
2774 /* Now figure out where to store the pointers */
2775 syslog(LOG_DEBUG, "msgbase: storing pointers");
2776
2777 /* If this is being done by the networker delivering a private
2778 * message, we want to BYPASS saving the sender's copy (because there
2779 * is no local sender; it would otherwise go to the Trashcan).
2780 */
2781 if ((!CC->internal_pgm) || (recps == NULL)) {
2782 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2783 syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
2784 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2785 }
2786 }
2787
2788 /* For internet mail, drop a copy in the outbound queue room */
2789 if ((recps != NULL) && (recps->num_internet > 0)) {
2791 }
2792
2793 /* If other rooms are specified, drop them there too. */
2794 if ((recps != NULL) && (recps->num_room > 0)) {
2795 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2796 extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
2797 syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
2798 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2799 }
2800 }
2801
2802 /* Bump this user's messages posted counter. */
2803 syslog(LOG_DEBUG, "msgbase: updating user");
2805 CC->user.posted = CC->user.posted + 1;
2807
2808 /* Decide where bounces need to be delivered */
2809 if ((recps != NULL) && (recps->bounce_to == NULL)) {
2810 if (CC->logged_in) {
2811 strcpy(bounce_to, CC->user.fullname);
2812 }
2813 else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
2814 strcpy(bounce_to, msg->cm_fields[eAuthor]);
2815 }
2816 recps->bounce_to = bounce_to;
2817 }
2818
2819 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2820
2821 /* If this is private, local mail, make a copy in the
2822 * recipient's mailbox and bump the reference count.
2823 */
2824 if ((recps != NULL) && (recps->num_local > 0)) {
2825 char *pch;
2826 int ntokens;
2827
2828 pch = recps->recp_local;
2829 recps->recp_local = recipient;
2830 ntokens = num_tokens(pch, '|');
2831 for (i=0; i<ntokens; ++i) {
2832 extract_token(recipient, pch, i, '|', sizeof recipient);
2833 syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
2834 if (CtdlGetUser(&userbuf, recipient) == 0) {
2835 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2836 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2839 }
2840 else {
2841 syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
2842 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2843 }
2844 }
2845 recps->recp_local = pch;
2846 }
2847
2848 /* Perform "after save" hooks */
2849 syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
2850
2853
2854 /* Go back to the room we started from */
2855 syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
2856 if (strcasecmp(hold_rm, CC->room.QRname)) {
2857 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2858 }
2859
2860 /*
2861 * Any addresses to harvest for someone's address book?
2862 */
2863 if ( (CC->logged_in) && (recps != NULL) ) {
2865 }
2866
2867 if (collected_addresses != NULL) {
2868 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2869 CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2870 aptr->roomname = strdup(actual_rm);
2873 aptr->next = atbf;
2874 atbf = aptr;
2876 }
2877
2878 /*
2879 * Determine whether this message qualifies for journaling.
2880 */
2881 if (!CM_IsEmpty(msg, eJournal)) {
2882 qualified_for_journaling = 0;
2883 }
2884 else {
2885 if (recps == NULL) {
2886 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2887 }
2888 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2889 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
2890 }
2891 else {
2892 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
2893 }
2894 }
2895
2896 /*
2897 * Do we have to perform journaling? If so, hand off the saved
2898 * RFC822 version will be handed off to the journaler for background
2899 * submit. Otherwise, we have to free the memory ourselves.
2900 */
2901 if (saved_rfc822_version != NULL) {
2902 if (qualified_for_journaling) {
2903 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2904 }
2905 else {
2906 FreeStrBuf(&saved_rfc822_version);
2907 }
2908 }
2909
2910 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2911 recps->bounce_to = NULL;
2912
2913 /* Done. */
2914 return(newmsgid);
2915}
2916
2917
2918/*
2919 * Convenience function for generating small administrative messages.
2920 */
2921long quickie_message(const char *from,
2922 const char *fromaddr,
2923 const char *to,
2924 char *room,
2925 const char *text,
2926 int format_type,
2927 const char *subject)
2928{
2929 struct CtdlMessage *msg;
2930 struct recptypes *recp = NULL;
2931
2932 msg = malloc(sizeof(struct CtdlMessage));
2933 memset(msg, 0, sizeof(struct CtdlMessage));
2935 msg->cm_anon_type = MES_NORMAL;
2936 msg->cm_format_type = format_type;
2937
2938 if (!IsEmptyStr(from)) {
2939 CM_SetField(msg, eAuthor, from, -1);
2940 }
2941 else if (!IsEmptyStr(fromaddr)) {
2942 char *pAt;
2943 CM_SetField(msg, eAuthor, fromaddr, -1);
2944 pAt = strchr(msg->cm_fields[eAuthor], '@');
2945 if (pAt != NULL) {
2946 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
2947 }
2948 }
2949 else {
2950 msg->cm_fields[eAuthor] = strdup("Citadel");
2951 }
2952
2953 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
2954 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
2955 if (!IsEmptyStr(to)) {
2956 CM_SetField(msg, eRecipient, to, -1);
2957 recp = validate_recipients(to, NULL, 0);
2958 }
2959 if (!IsEmptyStr(subject)) {
2960 CM_SetField(msg, eMsgSubject, subject, -1);
2961 }
2962 if (!IsEmptyStr(text)) {
2963 CM_SetField(msg, eMesageText, text, -1);
2964 }
2965
2966 long msgnum = CtdlSubmitMsg(msg, recp, room);
2967 CM_Free(msg);
2968 if (recp != NULL) free_recipients(recp);
2969 return msgnum;
2970}
2971
2972
2973/*
2974 * Back end function used by CtdlMakeMessage() and similar functions
2975 */
2976StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
2977 long tlen,
2978 size_t maxlen, /* maximum message length */
2979 StrBuf *exist, /* if non-null, append to it;
2980 exist is ALWAYS freed */
2981 int crlf /* CRLF newlines instead of LF */
2982) {
2983 StrBuf *Message;
2984 StrBuf *LineBuf;
2985 int flushing = 0;
2986 int finished = 0;
2987 int dotdot = 0;
2988
2989 LineBuf = NewStrBufPlain(NULL, SIZ);
2990 if (exist == NULL) {
2991 Message = NewStrBufPlain(NULL, 4 * SIZ);
2992 }
2993 else {
2994 Message = NewStrBufDup(exist);
2995 }
2996
2997 /* Do we need to change leading ".." to "." for SMTP escaping? */
2998 if ((tlen == 1) && (*terminator == '.')) {
2999 dotdot = 1;
3000 }
3001
3002 /* read in the lines of message text one by one */
3003 do {
3004 if (CtdlClientGetLine(LineBuf) < 0) {
3005 finished = 1;
3006 }
3007 if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
3008 finished = 1;
3009 }
3010 if ( (!flushing) && (!finished) ) {
3011 if (crlf) {
3012 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3013 }
3014 else {
3015 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3016 }
3017
3018 /* Unescape SMTP-style input of two dots at the beginning of the line */
3019 if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
3020 StrBufCutLeft(LineBuf, 1);
3021 }
3022 StrBufAppendBuf(Message, LineBuf, 0);
3023 }
3024
3025 /* if we've hit the max msg length, flush the rest */
3026 if (StrLength(Message) >= maxlen) flushing = 1;
3027
3028 } while (!finished);
3029 FreeStrBuf(&LineBuf);
3030 return Message;
3031}
3032
3033
3034/*
3035 * Back end function used by CtdlMakeMessage() and similar functions
3036 */
3037char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3038 long tlen,
3039 size_t maxlen, /* maximum message length */
3040 StrBuf *exist, /* if non-null, append to it;
3041 exist is ALWAYS freed */
3042 int crlf /* CRLF newlines instead of LF */
3043 )
3044{
3045 StrBuf *Message;
3046
3047 Message = CtdlReadMessageBodyBuf(terminator,
3048 tlen,
3049 maxlen,
3050 exist,
3051 crlf
3052 );
3053 if (Message == NULL)
3054 return NULL;
3055 else
3056 return SmashStrBuf(&Message);
3057}
3058
3059
3061 struct ctdluser *author, /* author's user structure */
3062 char *recipient, /* NULL if it's not mail */
3063 char *recp_cc, /* NULL if it's not mail */
3064 char *room, /* room where it's going */
3065 int type, /* see MES_ types in header file */
3066 int format_type, /* variformat, plain text, MIME... */
3067 char *fake_name, /* who we're masquerading as */
3068 char *my_email, /* which of my email addresses to use (empty is ok) */
3069 char *subject, /* Subject (optional) */
3070 char *supplied_euid, /* ...or NULL if this is irrelevant */
3071 char *preformatted_text, /* ...or NULL to read text from client */
3072 char *references /* Thread references */
3073) {
3074 return CtdlMakeMessageLen(
3075 author, /* author's user structure */
3076 recipient, /* NULL if it's not mail */
3077 (recipient)?strlen(recipient) : 0,
3078 recp_cc, /* NULL if it's not mail */
3079 (recp_cc)?strlen(recp_cc): 0,
3080 room, /* room where it's going */
3081 (room)?strlen(room): 0,
3082 type, /* see MES_ types in header file */
3083 format_type, /* variformat, plain text, MIME... */
3084 fake_name, /* who we're masquerading as */
3085 (fake_name)?strlen(fake_name): 0,
3086 my_email, /* which of my email addresses to use (empty is ok) */
3087 (my_email)?strlen(my_email): 0,
3088 subject, /* Subject (optional) */
3089 (subject)?strlen(subject): 0,
3090 supplied_euid, /* ...or NULL if this is irrelevant */
3091 (supplied_euid)?strlen(supplied_euid):0,
3092 preformatted_text, /* ...or NULL to read text from client */
3093 (preformatted_text)?strlen(preformatted_text) : 0,
3094 references, /* Thread references */
3095 (references)?strlen(references):0);
3096
3097}
3098
3099
3100/*
3101 * Build a binary message to be saved on disk.
3102 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3103 * will become part of the message. This means you are no longer
3104 * responsible for managing that memory -- it will be freed along with
3105 * the rest of the fields when CM_Free() is called.)
3106 */
3108 struct ctdluser *author, /* author's user structure */
3109 char *recipient, /* NULL if it's not mail */
3110 long rcplen,
3111 char *recp_cc, /* NULL if it's not mail */
3112 long cclen,
3113 char *room, /* room where it's going */
3114 long roomlen,
3115 int type, /* see MES_ types in header file */
3116 int format_type, /* variformat, plain text, MIME... */
3117 char *fake_name, /* who we're masquerading as */
3118 long fnlen,
3119 char *my_email, /* which of my email addresses to use (empty is ok) */
3120 long myelen,
3121 char *subject, /* Subject (optional) */
3122 long subjlen,
3123 char *supplied_euid, /* ...or NULL if this is irrelevant */
3124 long euidlen,
3125 char *preformatted_text, /* ...or NULL to read text from client */
3126 long textlen,
3127 char *references, /* Thread references */
3128 long reflen
3129) {
3130 long blen;
3131 char buf[1024];
3132 struct CtdlMessage *msg;
3133 StrBuf *FakeAuthor;
3134 StrBuf *FakeEncAuthor = NULL;
3135
3136 msg = malloc(sizeof(struct CtdlMessage));
3137 memset(msg, 0, sizeof(struct CtdlMessage));
3139 msg->cm_anon_type = type;
3140 msg->cm_format_type = format_type;
3141
3142 if (recipient != NULL) rcplen = striplt(recipient);
3143 if (recp_cc != NULL) cclen = striplt(recp_cc);
3144
3145 /* Path or Return-Path */
3146 if (myelen > 0) {
3147 CM_SetField(msg, eMessagePath, my_email, myelen);
3148 }
3149 else if (!IsEmptyStr(author->fullname)) {
3150 CM_SetField(msg, eMessagePath, author->fullname, -1);
3151 }
3152 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3153
3154 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3155 CM_SetField(msg, eTimestamp, buf, blen);
3156
3157 if (fnlen > 0) {
3158 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3159 }
3160 else {
3161 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3162 }
3163 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3164 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3165 FreeStrBuf(&FakeAuthor);
3166
3167 if (!!IsEmptyStr(CC->room.QRname)) {
3168 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3169 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
3170 }
3171 else {
3172 CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
3173 }
3174 }
3175
3176 if (rcplen > 0) {
3177 CM_SetField(msg, eRecipient, recipient, rcplen);
3178 }
3179 if (cclen > 0) {
3180 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3181 }
3182
3183 if (myelen > 0) {
3184 CM_SetField(msg, erFc822Addr, my_email, myelen);
3185 }
3186 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3187 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
3188 }
3189
3190 if (subject != NULL) {
3191 long length;
3192 length = striplt(subject);
3193 if (length > 0) {
3194 long i;
3195 long IsAscii;
3196 IsAscii = -1;
3197 i = 0;
3198 while ((subject[i] != '\0') &&
3199 (IsAscii = isascii(subject[i]) != 0 ))
3200 i++;
3201 if (IsAscii != 0)
3202 CM_SetField(msg, eMsgSubject, subject, subjlen);
3203 else /* ok, we've got utf8 in the string. */
3204 {
3205 char *rfc2047Subj;
3206 rfc2047Subj = rfc2047encode(subject, length);
3207 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3208 }
3209
3210 }
3211 }
3212
3213 if (euidlen > 0) {
3214 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3215 }
3216
3217 if (reflen > 0) {
3218 CM_SetField(msg, eWeferences, references, reflen);
3219 }
3220
3221 if (preformatted_text != NULL) {
3222 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3223 }
3224 else {
3225 StrBuf *MsgBody;
3226 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
3227 if (MsgBody != NULL) {
3228 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3229 }
3230 }
3231
3232 return(msg);
3233}
3234
3235
3236/*
3237 * API function to delete messages which match a set of criteria
3238 * (returns the actual number of messages deleted)
3239 */
3240int CtdlDeleteMessages(const char *room_name, // which room
3241 long *dmsgnums, // array of msg numbers to be deleted
3242 int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
3243 char *content_type // or "" for any. regular expressions expected.
3244) {
3245 struct ctdlroom qrbuf;
3246 struct cdbdata *cdbfr;
3247 long *msglist = NULL;
3248 long *dellist = NULL;
3249 int num_msgs = 0;
3250 int i, j;
3251 int num_deleted = 0;
3252 int delete_this;
3253 struct MetaData smi;
3254 regex_t re;
3255 regmatch_t pm;
3256 int need_to_free_re = 0;
3257
3258 if (content_type) if (!IsEmptyStr(content_type)) {
3259 regcomp(&re, content_type, 0);
3260 need_to_free_re = 1;
3261 }
3262 syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
3263
3264 /* get room record, obtaining a lock... */
3265 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3266 syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
3267 if (need_to_free_re) regfree(&re);
3268 return(0); /* room not found */
3269 }
3270 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3271
3272 if (cdbfr != NULL) {
3273 dellist = malloc(cdbfr->len);
3274 msglist = (long *) cdbfr->ptr;
3275 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3276 num_msgs = cdbfr->len / sizeof(long);
3277 cdb_free(cdbfr);
3278 }
3279 if (num_msgs > 0) {
3280 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3281 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3282 int have_more_del = 1;
3283
3284 num_msgs = sort_msglist(msglist, num_msgs);
3285 if (num_dmsgnums > 1)
3286 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3287/*
3288 {
3289 StrBuf *dbg = NewStrBuf();
3290 for (i = 0; i < num_dmsgnums; i++)
3291 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3292 syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
3293 FreeStrBuf(&dbg);
3294 }
3295*/
3296 i = 0; j = 0;
3297 while ((i < num_msgs) && (have_more_del)) {
3298 delete_this = 0x00;
3299
3300 /* Set/clear a bit for each criterion */
3301
3302 /* 0 messages in the list or a null list means that we are
3303 * interested in deleting any messages which meet the other criteria.
3304 */
3305 if (have_delmsgs) {
3306 delete_this |= 0x01;
3307 }
3308 else {
3309 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3310
3311 if (i >= num_msgs)
3312 continue;
3313
3314 if (msglist[i] == dmsgnums[j]) {
3315 delete_this |= 0x01;
3316 }
3317 j++;
3318 have_more_del = (j < num_dmsgnums);
3319 }
3320
3321 if (have_contenttype) {
3322 GetMetaData(&smi, msglist[i]);
3323 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3324 delete_this |= 0x02;
3325 }
3326 } else {
3327 delete_this |= 0x02;
3328 }
3329
3330 /* Delete message only if all bits are set */
3331 if (delete_this == 0x03) {
3332 dellist[num_deleted++] = msglist[i];
3333 msglist[i] = 0L;
3334 }
3335 i++;
3336 }
3337/*
3338 {
3339 StrBuf *dbg = NewStrBuf();
3340 for (i = 0; i < num_deleted; i++)
3341 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3342 syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
3343 FreeStrBuf(&dbg);
3344 }
3345*/
3346 num_msgs = sort_msglist(msglist, num_msgs);
3347 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3348 msglist, (int)(num_msgs * sizeof(long)));
3349
3350 if (num_msgs > 0)
3351 qrbuf.QRhighest = msglist[num_msgs - 1];
3352 else
3353 qrbuf.QRhighest = 0;
3354 }
3356
3357 /* Go through the messages we pulled out of the index, and decrement
3358 * their reference counts by 1. If this is the only room the message
3359 * was in, the reference count will reach zero and the message will
3360 * automatically be deleted from the database. We do this in a
3361 * separate pass because there might be plug-in hooks getting called,
3362 * and we don't want that happening during an S_ROOMS critical
3363 * section.
3364 */
3365 if (num_deleted) {
3366 for (i=0; i<num_deleted; ++i) {
3367 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3368 }
3369 AdjRefCountList(dellist, num_deleted, -1);
3370 }
3371 /* Now free the memory we used, and go away. */
3372 if (msglist != NULL) free(msglist);
3373 if (dellist != NULL) free(dellist);
3374 syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
3375 if (need_to_free_re) regfree(&re);
3376 return (num_deleted);
3377}
3378
3379
3380/*
3381 * GetMetaData() - Get the supplementary record for a message
3382 */
3383void GetMetaData(struct MetaData *smibuf, long msgnum)
3384{
3385 struct cdbdata *cdbsmi;
3386 long TheIndex;
3387
3388 memset(smibuf, 0, sizeof(struct MetaData));
3389 smibuf->meta_msgnum = msgnum;
3390 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3391
3392 /* Use the negative of the message number for its supp record index */
3393 TheIndex = (0L - msgnum);
3394
3395 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3396 if (cdbsmi == NULL) {
3397 return; /* record not found; leave it alone */
3398 }
3399 memcpy(smibuf, cdbsmi->ptr,
3400 ((cdbsmi->len > sizeof(struct MetaData)) ?
3401 sizeof(struct MetaData) : cdbsmi->len)
3402 );
3403 cdb_free(cdbsmi);
3404 return;
3405}
3406
3407
3408/*
3409 * PutMetaData() - (re)write supplementary record for a message
3410 */
3411void PutMetaData(struct MetaData *smibuf)
3412{
3413 long TheIndex;
3414
3415 /* Use the negative of the message number for the metadata db index */
3416 TheIndex = (0L - smibuf->meta_msgnum);
3417
3419 &TheIndex, (int)sizeof(long),
3420 smibuf, (int)sizeof(struct MetaData)
3421 );
3422}
3423
3424
3425/*
3426 * Convenience function to process a big block of AdjRefCount() operations
3427 */
3428void AdjRefCountList(long *msgnum, long nmsg, int incr)
3429{
3430 long i;
3431
3432 for (i = 0; i < nmsg; i++) {
3433 AdjRefCount(msgnum[i], incr);
3434 }
3435}
3436
3437
3438/*
3439 * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
3440 */
3441void AdjRefCount(long msgnum, int incr)
3442{
3443 struct MetaData smi;
3444 long delnum;
3445
3446 /* This is a *tight* critical section; please keep it that way, as
3447 * it may get called while nested in other critical sections.
3448 * Complicating this any further will surely cause deadlock!
3449 */
3451 GetMetaData(&smi, msgnum);
3452 smi.meta_refcount += incr;
3453 PutMetaData(&smi);
3455 syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
3456
3457 /* If the reference count is now zero, delete both the message and its metadata record.
3458 */
3459 if (smi.meta_refcount == 0) {
3460 syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
3461
3462 /* Call delete hooks with NULL room to show it has gone altogether */
3463 PerformDeleteHooks(NULL, msgnum);
3464
3465 /* Remove from message base */
3466 delnum = msgnum;
3467 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3468 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3469
3470 /* Remove metadata record */
3471 delnum = (0L - msgnum);
3472 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3473 }
3474}
3475
3476
3477/*
3478 * Write a generic object to this room
3479 *
3480 * Returns the message number of the written object, in case you need it.
3481 */
3482long CtdlWriteObject(char *req_room, /* Room to stuff it in */
3483 char *content_type, /* MIME type of this object */
3484 char *raw_message, /* Data to be written */
3485 off_t raw_length, /* Size of raw_message */
3486 struct ctdluser *is_mailbox, /* Mailbox room? */
3487 int is_binary, /* Is encoding necessary? */
3488 unsigned int flags /* Internal save flags */
3489) {
3490 struct ctdlroom qrbuf;
3491 char roomname[ROOMNAMELEN];
3492 struct CtdlMessage *msg;
3493 StrBuf *encoded_message = NULL;
3494
3495 if (is_mailbox != NULL) {
3496 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3497 }
3498 else {
3499 safestrncpy(roomname, req_room, sizeof(roomname));
3500 }
3501
3502 syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
3503
3504 if (is_binary) {
3505 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3506 }
3507 else {
3508 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3509 }
3510
3511 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3512 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3513 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3514
3515 if (is_binary) {
3516 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3517 }
3518 else {
3519 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3520 }
3521
3522 if (is_binary) {
3523 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3524 }
3525 else {
3526 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3527 }
3528
3529 syslog(LOG_DEBUG, "msgbase: allocating");
3530 msg = malloc(sizeof(struct CtdlMessage));
3531 memset(msg, 0, sizeof(struct CtdlMessage));
3533 msg->cm_anon_type = MES_NORMAL;
3534 msg->cm_format_type = 4;
3535 CM_SetField(msg, eAuthor, CC->user.fullname, -1);
3536 CM_SetField(msg, eOriginalRoom, req_room, -1);
3537 msg->cm_flags = flags;
3538
3539 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3540
3541 /* Create the requested room if we have to. */
3542 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3543 CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
3544 }
3545
3546 /* Now write the data */
3547 long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
3548 CM_Free(msg);
3549 return new_msgnum;
3550}
3551
3552
3553/************************************************************************/
3554/* MODULE INITIALIZATION */
3555/************************************************************************/
3556
3558{
3559 if (!threading) {
3561 }
3562
3563 /* return our module id for the log */
3564 return "msgbase";
3565}
#define ROOMNAMELEN
Definition: citadel.h:56
#define MES_ANONONLY
Definition: citadel.h:136
#define MES_NORMAL
Definition: citadel.h:135
#define LONG_MAX
Definition: citadel.h:167
#define MES_ANONOPT
Definition: citadel.h:137
char * CtdlGetConfigStr(char *key)
Definition: config.c:393
int CtdlGetConfigInt(char *key)
Definition: config.c:426
long CtdlGetConfigLong(char *key)
Definition: config.c:437
void CtdlBumpNewMailCounter(long which_user)
Definition: context.c:130
#define CC
Definition: context.h:140
long get_new_message_number(void)
Definition: control.c:179
void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room)
Definition: user_ops.c:321
void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix)
Definition: user_ops.c:351
int CtdlLockGetCurrentUser(void)
Definition: user_ops.c:103
int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name)
Definition: room_ops.c:342
long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf)
Definition: euidindex.c:67
void CtdlPutCurrentUserLock(void)
Definition: user_ops.c:138
void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
#define TWITDETECT
Definition: ctdl_module.h:262
int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name)
Definition: room_ops.c:387
void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room)
Definition: user_ops.c:306
unsigned CtdlCreateRoom(char *new_room_name, int new_room_type, char *new_room_pass, int new_room_floor, int really_create, int avoid_access, int new_room_view)
Definition: room_ops.c:1136
void CtdlUserGoto(char *where, int display_result, int transiently, int *msgs, int *new, long *oldest, long *newest)
Definition: room_ops.c:702
int CtdlGetUser(struct ctdluser *usbuf, char *name)
Definition: user_ops.c:77
void CtdlPutRoomLock(struct ctdlroom *qrbuf)
Definition: room_ops.c:444
#define CTDL_MODULE_INIT(module_name)
Definition: ctdl_module.h:50
void cdb_free(struct cdbdata *cdb)
Definition: database.c:609
int cdb_store(int cdb, const void *ckey, int ckeylen, void *cdata, int cdatalen)
Definition: database.c:397
struct cdbdata * cdb_fetch(int cdb, const void *key, int keylen)
Definition: database.c:546
int cdb_delete(int cdb, void *key, int keylen)
Definition: database.c:482
void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum)
Definition: euidindex.c:102
int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf)
Definition: euidindex.c:40
long datestring(char *buf, size_t n, time_t xtime, int which_format)
Definition: genstamp.c:26
@ DATESTRING_RFC822
Definition: genstamp.h:5
struct recptypes * validate_recipients(char *supplied_recipients, const char *RemoteIdentifier, int Flags)
void free_recipients(struct recptypes *valid)
int IsDirectory(char *addr, int allow_masq_domains)
void sanitize_truncated_recipient(char *str)
struct CtdlMessage * convert_internet_message(char *rfc822)
char * harvest_collected_addresses(struct CtdlMessage *msg)
char * qp_encode_email_addrs(char *source)
#define CIT_OK
Definition: ipcdef.h:6
#define ILLEGAL_VALUE
Definition: ipcdef.h:16
#define INTERNAL_ERROR
Definition: ipcdef.h:14
#define LISTING_FOLLOWS
Definition: ipcdef.h:5
#define HIGHER_ACCESS_REQUIRED
Definition: ipcdef.h:23
#define ROOM_NOT_FOUND
Definition: ipcdef.h:31
#define TOO_BIG
Definition: ipcdef.h:15
#define RESOURCE_BUSY
Definition: ipcdef.h:25
#define BINARY_FOLLOWS
Definition: ipcdef.h:10
#define FILE_NOT_FOUND
Definition: ipcdef.h:30
#define US_LASTOLD
Definition: ipcdef.h:64
#define QR_MAILBOX
Definition: ipcdef.h:53
#define NOT_LOGGED_IN
Definition: ipcdef.h:17
#define ERROR
Definition: ipcdef.h:9
#define MESSAGE_NOT_FOUND
Definition: ipcdef.h:34
void JournalBackgroundSubmit(struct CtdlMessage *msg, StrBuf *saved_rfc822_version, struct recptypes *recps)
Definition: journaling.c:30
void CM_Free(struct CtdlMessage *msg)
Definition: msgbase.c:310
void AdjRefCount(long msgnum, int incr)
Definition: msgbase.c:3441
void ReplicationChecks(struct CtdlMessage *msg)
Definition: msgbase.c:2578
void output_preferred(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1325
long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply)
Definition: msgbase.c:2414
void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto)
Definition: msgbase.c:1633
void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
Definition: msgbase.c:197
int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg)
Definition: msgbase.c:2399
void fixed_output_post(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1208
void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
Definition: msgbase.c:176
int check_cached_msglist(long msgnum)
Definition: msgbase.c:1451
int CtdlOutputMsg(long msg_num, int mode, int headers_only, int do_proto, int crlf, char *section, int flags, char **Author, char **Address, char **MessageID)
Definition: msgbase.c:1481
void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
Definition: msgbase.c:217
struct CtdlMessage * CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
Definition: msgbase.c:1068
char * msgkeys[]
Definition: msgbase.c:37
void list_this_pref(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:963
void GetMetaData(struct MetaData *smibuf, long msgnum)
Definition: msgbase.c:3383
void CtdlSerializeMessage(struct ser_ret *ret, struct CtdlMessage *msg)
Definition: msgbase.c:2520
struct CtdlMessage * CtdlMakeMessage(struct ctdluser *author, char *recipient, char *recp_cc, char *room, int type, int format_type, char *fake_name, char *my_email, char *subject, char *supplied_euid, char *preformatted_text, char *references)
Definition: msgbase.c:3060
void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1431
void CM_FreeContents(struct CtdlMessage *msg)
Definition: msgbase.c:296
StrBuf * CtdlReadMessageBodyBuf(char *terminator, long tlen, size_t maxlen, StrBuf *exist, int crlf)
Definition: msgbase.c:2976
void fixed_output(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1228
void DumpFormatFixed(struct CtdlMessage *TheMessage, int mode, const char *nl, int nllen)
Definition: msgbase.c:1898
void list_this_part(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:939
int CtdlForEachMessage(int mode, long ref, char *search_string, char *content_type, struct CtdlMessage *compare, ForEachMsgCallback CallBack, void *userdata)
Definition: msgbase.c:626
HashList * msgKeyLookup
Definition: msgbase.c:76
int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, int mode, int headers_only, int do_proto, int crlf, int flags)
Definition: msgbase.c:1969
struct CtdlMessage * CtdlFetchMessage(long msgnum, int with_body)
Definition: msgbase.c:1135
struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
Definition: msgbase.c:334
void choose_preferred(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1300
void mime_spew_section(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1045
long CtdlWriteObject(char *req_room, char *content_type, char *raw_message, off_t raw_length, struct ctdluser *is_mailbox, int is_binary, unsigned int flags)
Definition: msgbase.c:3482
int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template)
Definition: msgbase.c:365
long quickie_message(const char *from, const char *fromaddr, const char *to, char *room, const char *text, int format_type, const char *subject)
Definition: msgbase.c:2921
static const long NDiskFields
Definition: msgbase.c:134
int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
Definition: msgbase.c:137
int CtdlDeleteMessages(const char *room_name, long *dmsgnums, int num_dmsgnums, char *content_type)
Definition: msgbase.c:3240
long CtdlSubmitMsg(struct CtdlMessage *msg, struct recptypes *recps, const char *force)
Definition: msgbase.c:2603
int CM_IsValidMsg(struct CtdlMessage *msg)
Definition: msgbase.c:284
void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
Definition: msgbase.c:142
void fixed_output_pre(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1187
int GetFieldFromMnemonic(eMsgField *f, const char *c)
Definition: msgbase.c:78
char * CtdlReadMessageBody(char *terminator, long tlen, size_t maxlen, StrBuf *exist, int crlf)
Definition: msgbase.c:3037
void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
Definition: msgbase.c:242
eMsgField FieldOrder[]
Definition: msgbase.c:100
void OpenCmdResult(char *filename, const char *mime_type)
Definition: serv_file.c:138
void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
Definition: msgbase.c:164
void memfmout(char *mptr, const char *nl)
Definition: msgbase.c:861
long send_message(struct CtdlMessage *msg)
Definition: msgbase.c:2480
void CtdlSetSeen(long *target_msgnums, int num_target_msgnums, int target_setting, int which_set, struct ctdluser *which_user, struct ctdlroom *which_room)
Definition: msgbase.c:409
void OutputRFC822MsgHeaders(struct CtdlMessage *TheMessage, int flags, const char *nl, int nlen, char *mid, long sizeof_mid, char *suser, long sizeof_suser, char *luser, long sizeof_luser, char *fuser, long sizeof_fuser, char *snode, long sizeof_snode)
Definition: msgbase.c:1696
int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
Definition: msgbase.c:320
void list_this_suff(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:983
void CtdlGetSeen(char *buf, int which_set)
Definition: msgbase.c:393
void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
Definition: msgbase.c:156
void PutMetaData(struct MetaData *smibuf)
Definition: msgbase.c:3411
struct CtdlMessage * CtdlMakeMessageLen(struct ctdluser *author, char *recipient, long rcplen, char *recp_cc, long cclen, char *room, long roomlen, int type, int format_type, char *fake_name, long fnlen, char *my_email, long myelen, char *subject, long subjlen, char *supplied_euid, long euidlen, char *preformatted_text, long textlen, char *references, long reflen)
Definition: msgbase.c:3107
void Dump_RFC822HeadersBody(struct CtdlMessage *TheMessage, int headers_only, int flags, const char *nl, int nlen)
Definition: msgbase.c:1818
void CM_Flush(struct CtdlMessage *Msg)
Definition: msgbase.c:184
void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
Definition: msgbase.c:268
void AdjRefCountList(long *msgnum, long nmsg, int incr)
Definition: msgbase.c:3428
void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
Definition: msgbase.c:258
struct addresses_to_be_filed * atbf
Definition: msgbase.c:33
void FillMsgKeyLookupTable(void)
Definition: msgbase.c:87
void mime_download(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata)
Definition: msgbase.c:1004
int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs, int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj)
Definition: msgbase.c:2236
#define HEADERS_NONE
Definition: msgbase.h:41
#define SUPPRESS_ENV_TO
Definition: msgbase.h:163
#define ESC_DOT
Definition: msgbase.h:162
@ ctdlsetseen_seen
Definition: msgbase.h:176
@ ctdlsetseen_answered
Definition: msgbase.h:177
#define CM_RANGE(Message, Which)
Definition: msgbase.h:127
void(* ForEachMsgCallback)(long MsgNumber, void *UserData)
Definition: msgbase.h:90
#define HEADERS_ALL
Definition: msgbase.h:39
@ om_no_such_msg
Definition: msgbase.h:31
@ om_access_denied
Definition: msgbase.h:33
@ om_mime_error
Definition: msgbase.h:32
@ om_not_logged_in
Definition: msgbase.h:30
@ om_ok
Definition: msgbase.h:29
#define HEADERS_FAST
Definition: msgbase.h:42
#define QP_EADDR
Definition: msgbase.h:160
#define HEADERS_ONLY
Definition: msgbase.h:40
@ MSGS_FIRST
Definition: msgbase.h:9
@ MSGS_NEW
Definition: msgbase.h:8
@ MSGS_SEARCH
Definition: msgbase.h:13
@ MSGS_ALL
Definition: msgbase.h:6
@ MSGS_GT
Definition: msgbase.h:11
@ MSGS_EQ
Definition: msgbase.h:12
@ MSGS_OLD
Definition: msgbase.h:7
@ MSGS_LAST
Definition: msgbase.h:10
@ MSGS_LT
Definition: msgbase.h:14
void * malloc(size_t)
void free(void *)
int sort_msglist(long listptrs[], int oldcount)
Definition: room_ops.c:657
int CtdlDoIHavePermissionToReadMessagesInThisRoom(void)
Definition: room_ops.c:31
int PerformRoomHooks(struct ctdlroom *target_room)
int PerformFixedOutputHooks(char *content_type, char *content, int content_length)
int PerformMessageHooks(struct CtdlMessage *msg, struct recptypes *recps, int EventType)
void PerformDeleteHooks(char *room, long msgnum)
struct MetaData smi
Definition: serv_migrate.c:504
struct ctdlroom qrbuf
Definition: serv_migrate.c:497
visit vbuf
Definition: serv_migrate.c:503
#define EVT_AFTERUSRMBOXSAVE
Definition: server.h:234
#define FMT_FIXED
Definition: server.h:177
enum _MsgField eMsgField
#define CTDLMESSAGE_MAGIC
Definition: server.h:42
@ eMessagePath
Definition: server.h:317
@ eExclusiveID
Definition: server.h:309
@ eWeferences
Definition: server.h:322
@ eenVelopeTo
Definition: server.h:321
@ emessageId
Definition: server.h:311
@ eErrorMsg
Definition: server.h:324
@ eMesageText
Definition: server.h:315
@ eVltMsgNum
Definition: server.h:327
@ eSuppressIdx
Definition: server.h:325
@ erFc822Addr
Definition: server.h:310
@ eAuthor
Definition: server.h:307
@ eReplyTo
Definition: server.h:313
@ eCarbonCopY
Definition: server.h:323
@ eTimestamp
Definition: server.h:319
@ eMsgSubject
Definition: server.h:320
@ eExtnotify
Definition: server.h:326
@ eJournal
Definition: server.h:312
@ eRecipient
Definition: server.h:318
@ eListID
Definition: server.h:314
@ eOriginalRoom
Definition: server.h:316
@ eBig_message
Definition: server.h:308
#define EVT_AFTERSAVE
Definition: server.h:232
#define FMT_RFC822
Definition: server.h:178
#define FMT_CITADEL
Definition: server.h:176
@ MT_CITADEL
Definition: server.h:166
@ MT_DOWNLOAD
Definition: server.h:169
@ MT_SPEW_SECTION
Definition: server.h:170
@ MT_MIME
Definition: server.h:168
@ MT_RFC822
Definition: server.h:167
@ CDB_BIGMSGS
Definition: server.h:193
@ CDB_MSGLISTS
Definition: server.h:189
@ CDB_MSGMAIN
Definition: server.h:185
@ S_ATBF
Definition: server.h:148
@ S_SUPPMSGMAIN
Definition: server.h:142
#define EVT_BEFORESAVE
Definition: server.h:231
int snprintf(char *buf, size_t max, const char *fmt,...)
Definition: snprintf.c:69
long cm_lengths[256]
Definition: server.h:38
int cm_magic
Definition: server.h:34
char cm_anon_type
Definition: server.h:35
unsigned int cm_flags
Definition: server.h:39
char * cm_fields[256]
Definition: server.h:37
char cm_format_type
Definition: server.h:36
char meta_content_type[64]
Definition: server.h:270
long meta_rfc822_length
Definition: server.h:271
int meta_refcount
Definition: server.h:269
long meta_msgnum
Definition: server.h:268
char v_answered[4096]
Definition: server.h:252
char v_seen[4096]
Definition: server.h:251
char * collected_addresses
Definition: msgbase.h:71
struct addresses_to_be_filed * next
Definition: msgbase.h:69
size_t len
Definition: server.h:203
char * ptr
Definition: server.h:204
char QRname[128]
Definition: citadel.h:108
long QRnumber
Definition: citadel.h:119
long QRhighest
Definition: citadel.h:111
char fullname[64]
Definition: citadel.h:90
long usernum
Definition: citadel.h:87
size_t msglen
Definition: msgbase.c:1426
char desired_section[64]
Definition: msgbase.c:1424
char * msg
Definition: msgbase.c:1425
char chosen_part[128]
Definition: msgbase.h:50
int chosen_pref
Definition: msgbase.h:51
int did_print
Definition: msgbase.h:49
int is_ma
Definition: msgbase.h:46
int use_fo_hooks
Definition: msgbase.h:52
int freeze
Definition: msgbase.h:47
int dont_decode
Definition: msgbase.h:53
char * recp_room
Definition: server.h:57
int num_internet
Definition: server.h:50
int num_ignet
Definition: server.h:51
char * bounce_to
Definition: server.h:60
int num_room
Definition: server.h:52
char * recp_local
Definition: server.h:55
int num_local
Definition: server.h:49
size_t len
Definition: server.h:289
unsigned char * ser
Definition: server.h:290
#define MAILROOM
Definition: sysconfig.h:61
#define SMTP_SPOOLOUT_ROOM
Definition: sysconfig.h:73
#define SENTITEMS
Definition: sysconfig.h:62
#define USERCONTACTSROOM
Definition: sysconfig.h:67
#define SIZ
Definition: sysconfig.h:33
#define BIGMSG
Definition: sysconfig.h:39
int CtdlClientGetLine(StrBuf *Target)
Definition: sysdep.c:518
void cprintf(const char *format,...)
Definition: sysdep.c:381
int client_write(const char *buf, int nbytes)
Definition: sysdep.c:307
void begin_critical_section(int which_one)
Definition: threads.c:67
void end_critical_section(int which_one)
Definition: threads.c:85
int server_shutting_down
Definition: threads.c:31
int is_room_aide(void)
Definition: user_ops.c:441