citadel/ 0000755 0000000 0000000 00000000000 13223341045 011156 5 ustar root root citadel/serv_extensions.h 0000644 0000000 0000000 00000003542 13223341037 014572 0 ustar root root
#ifndef SERV_EXTENSIONS_H
#define SERV_EXTENSIONS_H
#include "server.h"
/*
* ServiceFunctionHook extensions are used for hooks which implement various
* protocols (either on TCP or on unix domain sockets) directly in the Citadel server.
*/
typedef struct ServiceFunctionHook ServiceFunctionHook;
struct ServiceFunctionHook {
ServiceFunctionHook *next;
int tcp_port;
char *sockpath;
void (*h_greeting_function) (void) ;
void (*h_command_function) (void) ;
void (*h_async_function) (void) ;
int msock;
const char* ServiceName; /* this is just for debugging and logging purposes. */
};
extern ServiceFunctionHook *ServiceHookTable;
typedef struct CleanupFunctionHook CleanupFunctionHook;
struct CleanupFunctionHook {
CleanupFunctionHook *next;
void (*h_function_pointer) (void);
};
extern CleanupFunctionHook *CleanupHookTable;
void initialize_server_extensions(void);
int DLoader_Exec_Cmd(char *cmdbuf);
char *Dynamic_Module_Init(void);
void CtdlDestroySessionHooks(void);
void PerformSessionHooks(int EventType);
void CtdlDestroyUserHooks(void);
void PerformUserHooks(struct ctdluser *usbuf, int EventType);
int PerformXmsgHooks(char *, char *, char *, char *);
void CtdlDestroyXmsgHooks(void);
void CtdlDestroyMessageHook(void);
int PerformMessageHooks(struct CtdlMessage *, recptypes *recps, int EventType);
void CtdlDestroyNetprocHooks(void);
int PerformNetprocHooks(struct CtdlMessage *, char *);
void CtdlDestroyRoomHooks(void);
int PerformRoomHooks(struct ctdlroom *);
void CtdlDestroyDeleteHooks(void);
void PerformDeleteHooks(char *, long);
void CtdlDestroyCleanupHooks(void);
void CtdlDestroyProtoHooks(void);
void CtdlDestroyServiceHook(void);
void CtdlDestroySearchHooks(void);
void CtdlDestroyFixedOutputHooks(void);
int PerformFixedOutputHooks(char *, char *, int);
void netcfg_keyname(char *keybuf, long roomnum);
#endif /* SERV_EXTENSIONS_H */
citadel/package-version.txt 0000644 0000000 0000000 00000000004 13223341045 014767 0 ustar root root 917
citadel/typesize.h 0000644 0000000 0000000 00000004043 13223341037 013205 0 ustar root root
/*
This file defines typedefs for 8, 16, and 32 bit integers. They are:
cit_int8_t default 8-bit int
cit_int16_t default 16-bit int
cit_int32_t default 32-bit int
cit_int64_t default 64-bit int (not implemented yet)
cit_sint8_t signed 8-bit int
cit_sint16_t signed 16-bit int
cit_sint32_t signed 32-bit int
cit_sint64_t signed 64-bit int (not implemented yet)
cit_uint8_t unsigned 8-bit int
cit_uint16_t unsigned 16-bit int
cit_uint32_t unsigned 32-bit int
cit_uint64_t unsigned 64-bit int (not implemented yet)
The sizes are determined during the configure process; see the
AC_CHECK_SIZEOF macros in configure.in. In no way do we assume that any
given datatype is any particular width, e.g. we don't assume short is two
bytes; we check for it specifically.
This might seem excessively paranoid, but I've seen some WEIRD systems
and some bizarre compilers (Domain/OS for instance) in my time.
*/
#ifndef _CITADEL_UX_TYPESIZE_H
#define _CITADEL_UX_TYPESIZE_H
/* Include sysdep.h if not already included */
#ifndef CTDLDIR
# include "sysdep.h"
#endif
/* 8-bit - If this fails, your compiler is broken */
#if SIZEOF_CHAR == 1
typedef char cit_int8_t;
typedef signed char cit_sint8_t;
typedef unsigned char cit_uint8_t;
#else
# error Unable to find an 8-bit integer datatype
#endif
/* 16-bit - If this fails, your compiler is broken */
#if SIZEOF_SHORT == 2
typedef short cit_int16_t;
typedef signed short cit_sint16_t;
typedef unsigned short cit_uint16_t;
#elif SIZEOF_INT == 2
typedef int cit_int16_t;
typedef signed int cit_sint16_t;
typedef unsigned int cit_uint16_t;
#else
# error Unable to find a 16-bit integer datatype
#endif
/* 32-bit - If this fails, your compiler is broken */
#if SIZEOF_INT == 4
typedef int cit_int32_t;
typedef signed int cit_sint32_t;
typedef unsigned int cit_uint32_t;
#elif SIZEOF_LONG == 4
typedef long cit_int32_t;
typedef signed long cit_sint32_t;
typedef unsigned long cit_uint32_t;
#else
# error Unable to find a 32-bit integer datatype
#endif
#endif /* _CITADEL_UX_TYPESIZE_H */
citadel/contrib/ 0000755 0000000 0000000 00000000000 13223341037 012617 5 ustar root root citadel/contrib/getdoku.sh 0000755 0000000 0000000 00000002771 13223341037 014627 0 ustar root root #!/bin/bash
BASE_SITE=http://www.citadel.org
#retrieves an index document from the citadel.org website, and filters it
# 1: URL
# 2: outfile where to put the filtered content at
GetIndex()
{
cd /tmp/; wget -q "${BASE_SITE}/${1}"
cat "/tmp/${1}" | \
grep /doku.php/ | \
grep -v "do=" | \
sed -e "s;.*href=\";;" \
-e "s;\" .*;;" \
-e "s;doku.php/;doku.php?id=;"| \
grep "^/doku" > \
"/tmp/$2"
}
rm -f /tmp/mainindex /tmp/doku.php*
GetIndex "doku.php?id=faq:start" mainindex
for i in `cat /tmp/mainindex`; do
TMPNAME=`echo $i|sed "s;.*=;;"`
echo $i $TMPNAME
mkdir /tmp/$TMPNAME
GetIndex "$i" "$TMPNAME/$TMPNAME"
for j in `cat /tmp/$TMPNAME/$TMPNAME`; do
echo "-----------$j----------------"
cd /tmp/$TMPNAME/;
DOCUMENT_NAME=`echo $j|sed -e "s;/doku.php?id=.*:;;"`
PLAIN_NAME=`grep "$DOCUMENT_NAME" /tmp/doku*$TMPNAME |head -n1 |sed -e "s;','/doku.*;;" -e "s;.*';;"`
echo "********** retrieving $DOCUMENT_NAME ************"
wget -q "${BASE_SITE}/${j}&do=export_xhtmlbody"
mv "/tmp/$TMPNAME/${j}&do=export_xhtmlbody" /tmp/$TMPNAME/$DOCUMENT_NAME
echo "
$PLAIN_NAME" >>collect_index
echo "" >>collect_bodies
cat $DOCUMENT_NAME>>collect_bodies
done
(
echo "$TMPNAME"
cat "/tmp/$TMPNAME/collect_index"
echo "
"
cat "/tmp/$TMPNAME/collect_bodies"
echo ""
) >/tmp/`echo $TMPNAME|sed "s;:;_;g"`.html
done citadel/euidindex.h 0000644 0000000 0000000 00000000600 13223341037 013302 0 ustar root root /*
* Index messages by EUID per room.
*/
int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf);
/* locate_message_by_euid is deprecated. Use CtdlLocateMessageByEuid instead */
long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) __attribute__ ((deprecated));
void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum);
void rebuild_euid_index(void);
citadel/modules_upgrade.c 0000644 0000000 0000000 00000001136 13223341045 014502 0 ustar root root /*
* /var/www/easyinstall/citadel/citadel/modules_upgrade.c
* Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE
*/
#include "sysdep.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "citadel.h"
#include "modules_init.h"
#include "sysdep_decls.h"
#include "serv_extensions.h"
void upgrade_modules (void)
{
const char *pMod;
syslog(LOG_INFO, "modules: upgrading.");
pMod = CTDL_UPGRADE_CALL(upgrade);
syslog(LOG_INFO, "modules: %s\n", pMod);
}
citadel/msgbase.c 0000644 0000000 0000000 00000311217 13223341037 012751 0 ustar root root /*
* Implements the message store.
*
* Copyright (c) 1987-2017 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include
#include
#include
#include
#include
#include
#include "md5.h"
#include "ctdl_module.h"
#include "citserver.h"
#include "control.h"
#include "config.h"
#include "clientsocket.h"
#include "genstamp.h"
#include "room_ops.h"
#include "user_ops.h"
#include "internet_addressing.h"
#include "euidindex.h"
#include "msgbase.h"
#include "journaling.h"
struct addresses_to_be_filed *atbf = NULL;
/* This temp file holds the queue of operations for AdjRefCount() */
static FILE *arcfp = NULL;
void AdjRefCountList(long *msgnum, long nmsg, int incr);
/*
* These are the four-character field headers we use when outputting
* messages in Citadel format (as opposed to RFC822 format).
*/
char *msgkeys[91] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL,
"from", /* A -> eAuthor */
NULL, /* B -> eBig_message */
NULL, /* C -> eRemoteRoom */
NULL, /* D -> eDestination */
"exti", /* E -> eXclusivID */
"rfca", /* F -> erFc822Addr */
NULL, /* G */
"hnod", /* H -> eHumanNode */
"msgn", /* I -> emessageId */
"jrnl", /* J -> eJournal */
"rep2", /* K -> eReplyTo */
"list", /* L -> eListID */
"text", /* M -> eMesageText */
"node", /* N -> eNodeName */
"room", /* O -> eOriginalRoom */
"path", /* P -> eMessagePath */
NULL, /* Q */
"rcpt", /* R -> eRecipient */
"spec", /* S -> eSpecialField */
"time", /* T -> eTimestamp */
"subj", /* U -> eMsgSubject */
"nvto", /* V -> eenVelopeTo */
"wefw", /* W -> eWeferences */
NULL, /* X */
"cccc", /* Y -> eCarbonCopY */
NULL /* Z */
};
HashList *msgKeyLookup = NULL;
int GetFieldFromMnemonic(eMsgField *f, const char* c)
{
void *v = NULL;
if (GetHash(msgKeyLookup, c, 4, &v)) {
*f = (eMsgField) v;
return 1;
}
return 0;
}
void FillMsgKeyLookupTable(void)
{
long i;
msgKeyLookup = NewHash (1, FourHash);
for (i=0; i < 91; i++) {
if (msgkeys[i] != NULL) {
Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
}
}
}
eMsgField FieldOrder[] = {
/* Important fields */
emessageId ,
eMessagePath ,
eTimestamp ,
eAuthor ,
erFc822Addr ,
eOriginalRoom,
eNodeName ,
eHumanNode ,
eRecipient ,
eDestination ,
/* Semi-important fields */
eBig_message ,
eRemoteRoom ,
eExclusiveID ,
eWeferences ,
eJournal ,
/* G is not used yet, may become virus signature*/
eReplyTo ,
eListID ,
/* Q is not used yet */
eSpecialField,
eenVelopeTo ,
/* X is not used yet */
/* Z is not used yet */
eCarbonCopY ,
eMsgSubject ,
/* internal only */
eErrorMsg ,
eSuppressIdx ,
eExtnotify ,
/* Message text (MUST be last) */
eMesageText
/* Not saved to disk:
eVltMsgNum
*/
};
static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
{
return !((Msg->cm_fields[which] != NULL) &&
(Msg->cm_fields[which][0] != '\0'));
}
void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
{
if (Msg->cm_fields[which] != NULL)
free (Msg->cm_fields[which]);
Msg->cm_fields[which] = malloc(length + 1);
memcpy(Msg->cm_fields[which], buf, length);
Msg->cm_fields[which][length] = '\0';
Msg->cm_lengths[which] = length;
}
void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
{
char buf[128];
long len;
len = snprintf(buf, sizeof(buf), "%ld", lvalue);
CM_SetField(Msg, which, buf, len);
}
void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
{
if (Msg->cm_fields[WhichToCut] == NULL)
return;
if (Msg->cm_lengths[WhichToCut] > maxlen)
{
Msg->cm_fields[WhichToCut][maxlen] = '\0';
Msg->cm_lengths[WhichToCut] = maxlen;
}
}
void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
{
if (Msg->cm_fields[which] != NULL)
free (Msg->cm_fields[which]);
Msg->cm_fields[which] = NULL;
Msg->cm_lengths[which] = 0;
}
void CM_Flush(struct CtdlMessage *Msg)
{
int i;
if (CM_IsValidMsg(Msg) == 0)
return;
for (i = 0; i < 256; ++i)
{
CM_FlushField(Msg, i);
}
}
void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
{
long len;
if (Msg->cm_fields[WhichToPutTo] != NULL)
free (Msg->cm_fields[WhichToPutTo]);
if (Msg->cm_fields[WhichtToCopy] != NULL)
{
len = Msg->cm_lengths[WhichtToCopy];
Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
Msg->cm_fields[WhichToPutTo][len] = '\0';
Msg->cm_lengths[WhichToPutTo] = len;
}
else
{
Msg->cm_fields[WhichToPutTo] = NULL;
Msg->cm_lengths[WhichToPutTo] = 0;
}
}
void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
{
if (Msg->cm_fields[which] != NULL) {
long oldmsgsize;
long newmsgsize;
char *new;
oldmsgsize = Msg->cm_lengths[which] + 1;
newmsgsize = length + oldmsgsize;
new = malloc(newmsgsize);
memcpy(new, buf, length);
memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
free(Msg->cm_fields[which]);
Msg->cm_fields[which] = new;
Msg->cm_lengths[which] = newmsgsize - 1;
}
else {
Msg->cm_fields[which] = malloc(length + 1);
memcpy(Msg->cm_fields[which], buf, length);
Msg->cm_fields[which][length] = '\0';
Msg->cm_lengths[which] = length;
}
}
void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
{
if (Msg->cm_fields[which] != NULL)
free (Msg->cm_fields[which]);
Msg->cm_fields[which] = *buf;
*buf = NULL;
Msg->cm_lengths[which] = length;
}
void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
{
if (Msg->cm_fields[which] != NULL)
free (Msg->cm_fields[which]);
Msg->cm_lengths[which] = StrLength(*buf);
Msg->cm_fields[which] = SmashStrBuf(buf);
}
void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
{
if (Msg->cm_fields[which] != NULL)
{
*retlen = Msg->cm_lengths[which];
*ret = Msg->cm_fields[which];
Msg->cm_fields[which] = NULL;
Msg->cm_lengths[which] = 0;
}
else
{
*ret = NULL;
*retlen = 0;
}
}
/*
* Returns 1 if the supplied pointer points to a valid Citadel message.
* If the pointer is NULL or the magic number check fails, returns 0.
*/
int CM_IsValidMsg(struct CtdlMessage *msg) {
if (msg == NULL)
return 0;
if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
return 0;
}
return 1;
}
void CM_FreeContents(struct CtdlMessage *msg)
{
int i;
for (i = 0; i < 256; ++i)
if (msg->cm_fields[i] != NULL) {
free(msg->cm_fields[i]);
msg->cm_lengths[i] = 0;
}
msg->cm_magic = 0; /* just in case */
}
/*
* 'Destructor' for struct CtdlMessage
*/
void CM_Free(struct CtdlMessage *msg)
{
if (CM_IsValidMsg(msg) == 0)
{
if (msg != NULL) free (msg);
return;
}
CM_FreeContents(msg);
free(msg);
}
int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
{
long len;
len = OrgMsg->cm_lengths[i];
NewMsg->cm_fields[i] = malloc(len + 1);
if (NewMsg->cm_fields[i] == NULL)
return 0;
memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
NewMsg->cm_fields[i][len] = '\0';
NewMsg->cm_lengths[i] = len;
return 1;
}
struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
{
int i;
struct CtdlMessage *NewMsg;
if (CM_IsValidMsg(OrgMsg) == 0)
return NULL;
NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
if (NewMsg == NULL)
return NULL;
memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
for (i = 0; i < 256; ++i)
{
if (OrgMsg->cm_fields[i] != NULL)
{
if (!CM_DupField(i, OrgMsg, NewMsg))
{
CM_Free(NewMsg);
return NULL;
}
}
}
return NewMsg;
}
/* Determine if a given message matches the fields in a message template.
* Return 0 for a successful match.
*/
int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
int i;
/* If there aren't any fields in the template, all messages will
* match.
*/
if (template == NULL) return(0);
/* Null messages are bogus. */
if (msg == NULL) return(1);
for (i='A'; i<='Z'; ++i) {
if (template->cm_fields[i] != NULL) {
if (msg->cm_fields[i] == NULL) {
/* Considered equal if temmplate is empty string */
if (IsEmptyStr(template->cm_fields[i])) continue;
return 1;
}
if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
(strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
return 1;
}
}
/* All compares succeeded: we have a match! */
return 0;
}
/*
* Retrieve the "seen" message list for the current room.
*/
void CtdlGetSeen(char *buf, int which_set) {
visit vbuf;
/* Learn about the user and room in question */
CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
if (which_set == ctdlsetseen_seen)
safestrncpy(buf, vbuf.v_seen, SIZ);
if (which_set == ctdlsetseen_answered)
safestrncpy(buf, vbuf.v_answered, SIZ);
}
/*
* Manipulate the "seen msgs" string (or other message set strings)
*/
void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
int target_setting, int which_set,
struct ctdluser *which_user, struct ctdlroom *which_room) {
struct cdbdata *cdbfr;
int i, k;
int is_seen = 0;
int was_seen = 0;
long lo = (-1L);
long hi = (-1L); /// TODO: we just write here. y?
visit vbuf;
long *msglist;
int num_msgs = 0;
StrBuf *vset;
StrBuf *setstr;
StrBuf *lostr;
StrBuf *histr;
const char *pvset;
char *is_set; /* actually an array of booleans */
/* Don't bother doing *anything* if we were passed a list of zero messages */
if (num_target_msgnums < 1) {
return;
}
/* If no room was specified, we go with the current room. */
if (!which_room) {
which_room = &CC->room;
}
/* If no user was specified, we go with the current user. */
if (!which_user) {
which_user = &CC->user;
}
syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
num_target_msgnums, target_msgnums[0],
(target_setting ? "SET" : "CLEAR"),
which_set,
which_room->QRname);
/* Learn about the user and room in question */
CtdlGetRelationship(&vbuf, which_user, which_room);
/* Load the message list */
cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
if (cdbfr != NULL) {
msglist = (long *) cdbfr->ptr;
cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
num_msgs = cdbfr->len / sizeof(long);
cdb_free(cdbfr);
} else {
return; /* No messages at all? No further action. */
}
is_set = malloc(num_msgs * sizeof(char));
memset(is_set, 0, (num_msgs * sizeof(char)) );
/* Decide which message set we're manipulating */
switch(which_set) {
case ctdlsetseen_seen:
vset = NewStrBufPlain(vbuf.v_seen, -1);
break;
case ctdlsetseen_answered:
vset = NewStrBufPlain(vbuf.v_answered, -1);
break;
default:
vset = NewStrBuf();
}
#if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
for (i=0; i 0) && (msglist[i] <= msglist[i-1])) abort();
}
syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
for (k=0; k 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
}
#endif
/* Translate the existing sequence set into an array of booleans */
setstr = NewStrBuf();
lostr = NewStrBuf();
histr = NewStrBuf();
pvset = NULL;
while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
StrBufExtract_token(lostr, setstr, 0, ':');
if (StrBufNum_tokens(setstr, ':') >= 2) {
StrBufExtract_token(histr, setstr, 1, ':');
}
else {
FlushStrBuf(histr);
StrBufAppendBuf(histr, lostr, 0);
}
lo = StrTol(lostr);
if (!strcmp(ChrPtr(histr), "*")) {
hi = LONG_MAX;
}
else {
hi = StrTol(histr);
}
for (i = 0; i < num_msgs; ++i) {
if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
is_set[i] = 1;
}
}
}
FreeStrBuf(&setstr);
FreeStrBuf(&lostr);
FreeStrBuf(&histr);
/* Now translate the array of booleans back into a sequence set */
FlushStrBuf(vset);
was_seen = 0;
lo = (-1);
hi = (-1);
for (i=0; i 0) {
StrBufAppendBufPlain(vset, HKEY(","), 0);
}
if (lo == hi) {
StrBufAppendPrintf(vset, "%ld", hi);
}
else {
StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
}
}
if ((is_seen) && (i == num_msgs - 1)) {
if (StrLength(vset) > 0) {
StrBufAppendBufPlain(vset, HKEY(","), 0);
}
if ((i==0) || (was_seen == 0)) {
StrBufAppendPrintf(vset, "%ld", msglist[i]);
}
else {
StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
}
}
was_seen = is_seen;
}
/*
* We will have to stuff this string back into a 4096 byte buffer, so if it's
* larger than that now, truncate it by removing tokens from the beginning.
* The limit of 100 iterations is there to prevent an infinite loop in case
* something unexpected happens.
*/
int number_of_truncations = 0;
while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
StrBufRemove_token(vset, 0, ',');
++number_of_truncations;
}
/*
* If we're truncating the sequence set of messages marked with the 'seen' flag,
* we want the earliest messages (the truncated ones) to be marked, not unmarked.
* Otherwise messages at the beginning will suddenly appear to be 'unseen'.
*/
if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
StrBuf *first_tok;
first_tok = NewStrBuf();
StrBufExtract_token(first_tok, vset, 0, ',');
StrBufRemove_token(vset, 0, ',');
if (StrBufNum_tokens(first_tok, ':') > 1) {
StrBufRemove_token(first_tok, 0, ':');
}
StrBuf *new_set;
new_set = NewStrBuf();
StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
StrBufAppendBuf(new_set, first_tok, 0);
StrBufAppendBufPlain(new_set, HKEY(":"), 0);
StrBufAppendBuf(new_set, vset, 0);
FreeStrBuf(&vset);
FreeStrBuf(&first_tok);
vset = new_set;
}
/* Decide which message set we're manipulating */
switch (which_set) {
case ctdlsetseen_seen:
safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
break;
case ctdlsetseen_answered:
safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
break;
}
free(is_set);
free(msglist);
CtdlSetRelationship(&vbuf, which_user, which_room);
FreeStrBuf(&vset);
}
/*
* API function to perform an operation for each qualifying message in the
* current room. (Returns the number of messages processed.)
*/
int CtdlForEachMessage(int mode, long ref, char *search_string,
char *content_type,
struct CtdlMessage *compare,
ForEachMsgCallback CallBack,
void *userdata)
{
int a, i, j;
visit vbuf;
struct cdbdata *cdbfr;
long *msglist = NULL;
int num_msgs = 0;
int num_processed = 0;
long thismsg;
struct MetaData smi;
struct CtdlMessage *msg = NULL;
int is_seen = 0;
long lastold = 0L;
int printed_lastold = 0;
int num_search_msgs = 0;
long *search_msgs = NULL;
regex_t re;
int need_to_free_re = 0;
regmatch_t pm;
if ((content_type) && (!IsEmptyStr(content_type))) {
regcomp(&re, content_type, 0);
need_to_free_re = 1;
}
/* Learn about the user and room in question */
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
return -1;
}
CtdlGetUser(&CC->user, CC->curr_user);
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
return -1;
}
CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
return -1;
}
/* Load the message list */
cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
if (cdbfr == NULL) {
if (need_to_free_re) regfree(&re);
return 0; /* No messages at all? No further action. */
}
msglist = (long *) cdbfr->ptr;
num_msgs = cdbfr->len / sizeof(long);
cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
cdb_free(cdbfr); /* we own this memory now */
/*
* Now begin the traversal.
*/
if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
/* If the caller is looking for a specific MIME type, filter
* out all messages which are not of the type requested.
*/
if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
/* This call to GetMetaData() sits inside this loop
* so that we only do the extra database read per msg
* if we need to. Doing the extra read all the time
* really kills the server. If we ever need to use
* metadata for another search criterion, we need to
* move the read somewhere else -- but still be smart
* enough to only do the read if the caller has
* specified something that will need it.
*/
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
free(msglist);
return -1;
}
GetMetaData(&smi, msglist[a]);
/* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
msglist[a] = 0L;
}
}
}
num_msgs = sort_msglist(msglist, num_msgs);
/* If a template was supplied, filter out the messages which
* don't match. (This could induce some delays!)
*/
if (num_msgs > 0) {
if (compare != NULL) {
for (a = 0; a < num_msgs; ++a) {
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
free(msglist);
return -1;
}
msg = CtdlFetchMessage(msglist[a], 1, 1);
if (msg != NULL) {
if (CtdlMsgCmp(msg, compare)) {
msglist[a] = 0L;
}
CM_Free(msg);
}
}
}
}
/* If a search string was specified, get a message list from
* the full text index and remove messages which aren't on both
* lists.
*
* How this works:
* Since the lists are sorted and strictly ascending, and the
* output list is guaranteed to be shorter than or equal to the
* input list, we overwrite the bottom of the input list. This
* eliminates the need to memmove big chunks of the list over and
* over again.
*/
if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
/* Call search module via hook mechanism.
* NULL means use any search function available.
* otherwise replace with a char * to name of search routine
*/
CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
if (num_search_msgs > 0) {
int orig_num_msgs;
orig_num_msgs = num_msgs;
num_msgs = 0;
for (i=0; i 0)
for (a = 0; a < num_msgs; ++a) {
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
free(msglist);
return num_processed;
}
thismsg = msglist[a];
if (mode == MSGS_ALL) {
is_seen = 0;
}
else {
is_seen = is_msg_in_sequence_set(
vbuf.v_seen, thismsg);
if (is_seen) lastold = thismsg;
}
if ((thismsg > 0L)
&& (
(mode == MSGS_ALL)
|| ((mode == MSGS_OLD) && (is_seen))
|| ((mode == MSGS_NEW) && (!is_seen))
|| ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
|| ((mode == MSGS_FIRST) && (a < ref))
|| ((mode == MSGS_GT) && (thismsg > ref))
|| ((mode == MSGS_LT) && (thismsg < ref))
|| ((mode == MSGS_EQ) && (thismsg == ref))
)
) {
if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
if (CallBack)
CallBack(lastold, userdata);
printed_lastold = 1;
++num_processed;
}
if (CallBack) CallBack(thismsg, userdata);
++num_processed;
}
}
if (need_to_free_re) regfree(&re);
/*
* We cache the most recent msglist in order to do security checks later
*/
if (CC->client_socket > 0) {
if (CC->cached_msglist != NULL) {
free(CC->cached_msglist);
}
CC->cached_msglist = msglist;
CC->cached_num_msgs = num_msgs;
}
else {
free(msglist);
}
return num_processed;
}
/*
* memfmout() - Citadel text formatter and paginator.
* Although the original purpose of this routine was to format
* text to the reader's screen width, all we're really using it
* for here is to format text out to 80 columns before sending it
* to the client. The client software may reformat it again.
*/
void memfmout(
char *mptr, /* where are we going to get our text from? */
const char *nl /* string to terminate lines with */
) {
int column = 0;
unsigned char ch = 0;
char outbuf[1024];
int len = 0;
int nllen = 0;
if (!mptr) return;
nllen = strlen(nl);
while (ch=*(mptr++), ch != 0) {
if (ch == '\n') {
if (client_write(outbuf, len) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
return;
}
len = 0;
if (client_write(nl, nllen) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
return;
}
column = 0;
}
else if (ch == '\r') {
/* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
}
else if (isspace(ch)) {
if (column > 72) { /* Beyond 72 columns, break on the next space */
if (client_write(outbuf, len) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
return;
}
len = 0;
if (client_write(nl, nllen) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
return;
}
column = 0;
}
else {
outbuf[len++] = ch;
++column;
}
}
else {
outbuf[len++] = ch;
++column;
if (column > 1000) { /* Beyond 1000 columns, break anywhere */
if (client_write(outbuf, len) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
return;
}
len = 0;
if (client_write(nl, nllen) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
return;
}
column = 0;
}
}
}
if (len) {
if (client_write(outbuf, len) == -1)
{
syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
return;
}
client_write(nl, nllen);
column = 0;
}
}
/*
* Callback function for mime parser that simply lists the part
*/
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)
{
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
if (ma->is_ma == 0) {
cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
name,
filename,
partnum,
disp,
cbtype,
(long)length,
cbid,
cbcharset);
}
}
/*
* Callback function for multipart prefix
*/
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)
{
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
if (!strcasecmp(cbtype, "multipart/alternative")) {
++ma->is_ma;
}
if (ma->is_ma == 0) {
cprintf("pref=%s|%s\n", partnum, cbtype);
}
}
/*
* Callback function for multipart sufffix
*/
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)
{
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
if (ma->is_ma == 0) {
cprintf("suff=%s|%s\n", partnum, cbtype);
}
if (!strcasecmp(cbtype, "multipart/alternative")) {
--ma->is_ma;
}
}
/*
* Callback function for mime parser that opens a section for downloading
* we use serv_files function here:
*/
extern void OpenCmdResult(char *filename, const char *mime_type);
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)
{
int rv = 0;
/* Silently go away if there's already a download open. */
if (CC->download_fp != NULL)
return;
if (
(!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
|| (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
) {
CC->download_fp = tmpfile();
if (CC->download_fp == NULL) {
syslog(LOG_EMERG, "msgbase: mime_download() couldn't write: %m");
cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
return;
}
rv = fwrite(content, length, 1, CC->download_fp);
if (rv <= 0) {
syslog(LOG_EMERG, "msgbase: mime_download() Couldn't write: %m");
cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
fclose(CC->download_fp);
CC->download_fp = NULL;
return;
}
fflush(CC->download_fp);
rewind(CC->download_fp);
OpenCmdResult(filename, cbtype);
}
}
/*
* Callback function for mime parser that outputs a section all at once.
* We can specify the desired section by part number *or* content-id.
*/
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)
{
int *found_it = (int *)cbuserdata;
if (
(!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
|| (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
) {
*found_it = 1;
cprintf("%d %d|-1|%s|%s|%s\n",
BINARY_FOLLOWS,
(int)length,
filename,
cbtype,
cbcharset
);
client_write(content, length);
}
}
struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
{
struct CtdlMessage *ret = NULL;
const char *mptr;
const char *upper_bound;
cit_uint8_t ch;
cit_uint8_t field_header;
eMsgField which;
mptr = Buffer;
upper_bound = Buffer + Length;
/* Parse the three bytes that begin EVERY message on disk.
* The first is always 0xFF, the on-disk magic number.
* The second is the anonymous/public type byte.
* The third is the format type byte (vari, fixed, or MIME).
*/
ch = *mptr++;
if (ch != 255) {
syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
return NULL;
}
ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
memset(ret, 0, sizeof(struct CtdlMessage));
ret->cm_magic = CTDLMESSAGE_MAGIC;
ret->cm_anon_type = *mptr++; /* Anon type byte */
ret->cm_format_type = *mptr++; /* Format type byte */
/*
* The rest is zero or more arbitrary fields. Load them in.
* We're done when we encounter either a zero-length field or
* have just processed the 'M' (message text) field.
*/
do {
field_header = '\0';
long len;
/* work around possibly buggy messages: */
while (field_header == '\0')
{
if (mptr >= upper_bound) {
break;
}
field_header = *mptr++;
}
if (mptr >= upper_bound) {
break;
}
which = field_header;
len = strlen(mptr);
CM_SetField(ret, which, mptr, len);
mptr += len + 1; /* advance to next field */
} while ((mptr < upper_bound) && (field_header != 'M'));
return (ret);
}
/*
* Load a message from disk into memory.
* This is used by CtdlOutputMsg() and other fetch functions.
*
* NOTE: Caller is responsible for freeing the returned CtdlMessage struct
* using the CM_Free(); function.
*/
struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body, int run_msg_hooks)
{
struct cdbdata *dmsgtext;
struct CtdlMessage *ret = NULL;
syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
if (dmsgtext == NULL) {
syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Failed!", msgnum, with_body);
return NULL;
}
if (dmsgtext->ptr[dmsgtext->len - 1] != '\0')
{
syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
dmsgtext->ptr[dmsgtext->len - 1] = '\0';
}
ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
cdb_free(dmsgtext);
if (ret == NULL) {
return NULL;
}
/* Always make sure there's something in the msg text field. If
* it's NULL, the message text is most likely stored separately,
* so go ahead and fetch that. Failing that, just set a dummy
* body so other code doesn't barf.
*/
if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
if (dmsgtext != NULL) {
CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
cdb_free(dmsgtext);
}
}
if (CM_IsEmpty(ret, eMesageText)) {
CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
}
/* Perform "before read" hooks (aborting if any return nonzero) */
if (run_msg_hooks && (PerformMessageHooks(ret, NULL, EVT_BEFOREREAD) > 0)) {
CM_Free(ret);
return NULL;
}
return (ret);
}
/*
* Pre callback function for multipart/alternative
*
* NOTE: this differs from the standard behavior for a reason. Normally when
* displaying multipart/alternative you want to show the _last_ usable
* format in the message. Here we show the _first_ one, because it's
* usually text/plain. Since this set of functions is designed for text
* output to non-MIME-aware clients, this is the desired behavior.
*
*/
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)
{
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
if (!strcasecmp(cbtype, "multipart/alternative")) {
++ma->is_ma;
ma->did_print = 0;
}
if (!strcasecmp(cbtype, "message/rfc822")) {
++ma->freeze;
}
}
/*
* Post callback function for multipart/alternative
*/
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)
{
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
if (!strcasecmp(cbtype, "multipart/alternative")) {
--ma->is_ma;
ma->did_print = 0;
}
if (!strcasecmp(cbtype, "message/rfc822")) {
--ma->freeze;
}
}
/*
* Inline callback function for mime parser that wants to display text
*/
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)
{
char *ptr;
char *wptr;
size_t wlen;
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
syslog(LOG_DEBUG,
"msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
partnum, filename, cbtype, (long)length
);
/*
* If we're in the middle of a multipart/alternative scope and
* we've already printed another section, skip this one.
*/
if ( (ma->is_ma) && (ma->did_print) ) {
syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
return;
}
ma->did_print = 1;
if ( (!strcasecmp(cbtype, "text/plain"))
|| (IsEmptyStr(cbtype)) ) {
wptr = content;
if (length > 0) {
client_write(wptr, length);
if (wptr[length-1] != '\n') {
cprintf("\n");
}
}
return;
}
if (!strcasecmp(cbtype, "text/html")) {
ptr = html_to_ascii(content, length, 80, 0);
wlen = strlen(ptr);
client_write(ptr, wlen);
if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
cprintf("\n");
}
free(ptr);
return;
}
if (ma->use_fo_hooks) {
if (PerformFixedOutputHooks(cbtype, content, length)) {
/* above function returns nonzero if it handled the part */
return;
}
}
if (strncasecmp(cbtype, "multipart/", 10)) {
cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
partnum, filename, cbtype, (long)length);
return;
}
}
/*
* The client is elegant and sophisticated and wants to be choosy about
* MIME content types, so figure out which multipart/alternative part
* we're going to send.
*
* We use a system of weights. When we find a part that matches one of the
* MIME types we've declared as preferential, we can store it in ma->chosen_part
* and then set ma->chosen_pref to that MIME type's position in our preference
* list. If we then hit another match, we only replace the first match if
* the preference value is lower.
*/
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)
{
char buf[1024];
int i;
struct ma_info *ma;
ma = (struct ma_info *)cbuserdata;
// NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
// http://bugzilla.citadel.org/show_bug.cgi?id=220
// I don't know if there are any side effects! Please TEST TEST TEST
//if (ma->is_ma > 0) {
for (i=0; ipreferred_formats, '|'); ++i) {
extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
if (i < ma->chosen_pref) {
syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
ma->chosen_pref = i;
}
}
}
}
/*
* Now that we've chosen our preferred part, output it.
*/
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)
{
int i;
char buf[128];
int add_newline = 0;
char *text_content;
struct ma_info *ma;
char *decoded = NULL;
size_t bytes_decoded;
int rc = 0;
ma = (struct ma_info *)cbuserdata;
/* This is not the MIME part you're looking for... */
if (strcasecmp(partnum, ma->chosen_part)) return;
/* If the content-type of this part is in our preferred formats
* list, we can simply output it verbatim.
*/
for (i=0; ipreferred_formats, '|'); ++i) {
extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
if (!strcasecmp(buf, cbtype)) {
/* Yeah! Go! W00t!! */
if (ma->dont_decode == 0)
rc = mime_decode_now (content,
length,
encoding,
&decoded,
&bytes_decoded);
if (rc < 0)
break; /* Give us the chance, maybe theres another one. */
if (rc == 0) text_content = (char *)content;
else {
text_content = decoded;
length = bytes_decoded;
}
if (text_content[length-1] != '\n') {
++add_newline;
}
cprintf("Content-type: %s", cbtype);
if (!IsEmptyStr(cbcharset)) {
cprintf("; charset=%s", cbcharset);
}
cprintf("\nContent-length: %d\n",
(int)(length + add_newline) );
if (!IsEmptyStr(encoding)) {
cprintf("Content-transfer-encoding: %s\n", encoding);
}
else {
cprintf("Content-transfer-encoding: 7bit\n");
}
cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
cprintf("\n");
if (client_write(text_content, length) == -1)
{
syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
return;
}
if (add_newline) cprintf("\n");
if (decoded != NULL) free(decoded);
return;
}
}
/* No translations required or possible: output as text/plain */
cprintf("Content-type: text/plain\n\n");
rc = 0;
if (ma->dont_decode == 0)
rc = mime_decode_now (content,
length,
encoding,
&decoded,
&bytes_decoded);
if (rc < 0)
return; /* Give us the chance, maybe theres another one. */
if (rc == 0) text_content = (char *)content;
else {
text_content = decoded;
length = bytes_decoded;
}
fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
length, encoding, cbid, cbuserdata);
if (decoded != NULL) free(decoded);
}
struct encapmsg {
char desired_section[64];
char *msg;
size_t msglen;
};
/*
* Callback function for
*/
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)
{
struct encapmsg *encap;
encap = (struct encapmsg *)cbuserdata;
/* Only proceed if this is the desired section... */
if (!strcasecmp(encap->desired_section, partnum)) {
encap->msglen = length;
encap->msg = malloc(length + 2);
memcpy(encap->msg, content, length);
return;
}
}
/*
* Determine whether the specified message exists in the cached_msglist
* (This is a security check)
*/
int check_cached_msglist(long msgnum) {
/* cases in which we skip the check */
if (!CC) return om_ok; /* not a session */
if (CC->client_socket <= 0) return om_ok; /* not a client session */
if (CC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
if (CC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
/* Do a binary search within the cached_msglist for the requested msgnum */
int min = 0;
int max = (CC->cached_num_msgs - 1);
while (max >= min) {
int middle = min + (max-min) / 2 ;
if (msgnum == CC->cached_msglist[middle]) {
return om_ok;
}
if (msgnum > CC->cached_msglist[middle]) {
min = middle + 1;
}
else {
max = middle - 1;
}
}
return om_access_denied;
}
/*
* Get a message off disk. (returns om_* values found in msgbase.h)
*
*/
int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
int mode, /* how would you like that message? */
int headers_only, /* eschew the message body? */
int do_proto, /* do Citadel protocol responses? */
int crlf, /* Use CRLF newlines instead of LF? */
char *section, /* NULL or a message/rfc822 section */
int flags, /* various flags; see msgbase.h */
char **Author,
char **Address,
char **MessageID
) {
struct CtdlMessage *TheMessage = NULL;
int retcode = CIT_OK;
struct encapmsg encap;
int r;
syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
msg_num, mode,
(section ? section : "<>")
);
r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
if (r != om_ok) {
if (do_proto) {
if (r == om_not_logged_in) {
cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
}
else {
cprintf("%d An unknown error has occurred.\n", ERROR);
}
}
return(r);
}
/*
* Check to make sure the message is actually IN this room
*/
r = check_cached_msglist(msg_num);
if (r == om_access_denied) {
/* Not in the cache? We get ONE shot to check it again. */
CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
r = check_cached_msglist(msg_num);
}
if (r != om_ok) {
syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
msg_num, CC->room.QRname
);
if (do_proto) {
if (r == om_access_denied) {
cprintf("%d message %ld was not found in this room\n",
ERROR + HIGHER_ACCESS_REQUIRED,
msg_num
);
}
}
return(r);
}
/*
* Fetch the message from disk. If we're in HEADERS_FAST mode,
* request that we don't even bother loading the body into memory.
*/
if (headers_only == HEADERS_FAST) {
TheMessage = CtdlFetchMessage(msg_num, 0, 1);
}
else {
TheMessage = CtdlFetchMessage(msg_num, 1, 1);
}
if (TheMessage == NULL) {
if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
ERROR + MESSAGE_NOT_FOUND, msg_num);
return(om_no_such_msg);
}
/* Here is the weird form of this command, to process only an
* encapsulated message/rfc822 section.
*/
if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
memset(&encap, 0, sizeof encap);
safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
mime_parser(CM_RANGE(TheMessage, eMesageText),
*extract_encapsulated_message,
NULL, NULL, (void *)&encap, 0
);
if ((Author != NULL) && (*Author == NULL))
{
long len;
CM_GetAsField(TheMessage, eAuthor, Author, &len);
}
if ((Address != NULL) && (*Address == NULL))
{
long len;
CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
}
if ((MessageID != NULL) && (*MessageID == NULL))
{
long len;
CM_GetAsField(TheMessage, emessageId, MessageID, &len);
}
CM_Free(TheMessage);
TheMessage = NULL;
if (encap.msg) {
encap.msg[encap.msglen] = 0;
TheMessage = convert_internet_message(encap.msg);
encap.msg = NULL; /* no free() here, TheMessage owns it now */
/* Now we let it fall through to the bottom of this
* function, because TheMessage now contains the
* encapsulated message instead of the top-level
* message. Isn't that neat?
*/
}
else {
if (do_proto) {
cprintf("%d msg %ld has no part %s\n",
ERROR + MESSAGE_NOT_FOUND,
msg_num,
section);
}
retcode = om_no_such_msg;
}
}
/* Ok, output the message now */
if (retcode == CIT_OK)
retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
if ((Author != NULL) && (*Author == NULL))
{
long len;
CM_GetAsField(TheMessage, eAuthor, Author, &len);
}
if ((Address != NULL) && (*Address == NULL))
{
long len;
CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
}
if ((MessageID != NULL) && (*MessageID == NULL))
{
long len;
CM_GetAsField(TheMessage, emessageId, MessageID, &len);
}
CM_Free(TheMessage);
return(retcode);
}
void OutputCtdlMsgHeaders(
struct CtdlMessage *TheMessage,
int do_proto) /* do Citadel protocol responses? */
{
int i;
int suppress_f = 0;
char buf[SIZ];
char display_name[256];
/* begin header processing loop for Citadel message format */
safestrncpy(display_name, "", sizeof display_name);
if (!CM_IsEmpty(TheMessage, eAuthor)) {
strcpy(buf, TheMessage->cm_fields[eAuthor]);
if (TheMessage->cm_anon_type == MES_ANONONLY) {
safestrncpy(display_name, "****", sizeof display_name);
}
else if (TheMessage->cm_anon_type == MES_ANONOPT) {
safestrncpy(display_name, "anonymous", sizeof display_name);
}
else {
safestrncpy(display_name, buf, sizeof display_name);
}
if ((is_room_aide())
&& ((TheMessage->cm_anon_type == MES_ANONONLY)
|| (TheMessage->cm_anon_type == MES_ANONOPT))) {
size_t tmp = strlen(display_name);
snprintf(&display_name[tmp],
sizeof display_name - tmp,
" [%s]", buf);
}
}
/* Don't show Internet address for users on the
* local Citadel network.
*/
suppress_f = 0;
if (!CM_IsEmpty(TheMessage, eNodeName) &&
(haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
{
suppress_f = 1;
}
/* Now spew the header fields in the order we like them. */
for (i=0; i< NDiskFields; ++i) {
eMsgField Field;
Field = FieldOrder[i];
if (Field != eMesageText) {
if ( (!CM_IsEmpty(TheMessage, Field))
&& (msgkeys[Field] != NULL) ) {
if ((Field == eenVelopeTo) ||
(Field == eRecipient) ||
(Field == eCarbonCopY)) {
sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
}
if (Field == eAuthor) {
if (do_proto) cprintf("%s=%s\n",
msgkeys[Field],
display_name);
}
else if ((Field == erFc822Addr) && (suppress_f)) {
/* do nothing */
}
/* Masquerade display name if needed */
else {
if (do_proto) cprintf("%s=%s\n",
msgkeys[Field],
TheMessage->cm_fields[Field]
);
}
}
}
}
}
void OutputRFC822MsgHeaders(
struct CtdlMessage *TheMessage,
int flags, /* should the bessage be exported clean */
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)
{
char datestamp[100];
int subject_found = 0;
char buf[SIZ];
int i, j, k;
char *mptr = NULL;
char *mpptr = NULL;
char *hptr;
for (i = 0; i < NDiskFields; ++i) {
if (TheMessage->cm_fields[FieldOrder[i]]) {
mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
switch (FieldOrder[i]) {
case eAuthor:
safestrncpy(luser, mptr, sizeof_luser);
safestrncpy(suser, mptr, sizeof_suser);
break;
case eCarbonCopY:
if ((flags & QP_EADDR) != 0) {
mptr = qp_encode_email_addrs(mptr);
}
sanitize_truncated_recipient(mptr);
cprintf("CC: %s%s", mptr, nl);
break;
case eMessagePath:
cprintf("Return-Path: %s%s", mptr, nl);
break;
case eListID:
cprintf("List-ID: %s%s", mptr, nl);
break;
case eenVelopeTo:
if ((flags & QP_EADDR) != 0)
mptr = qp_encode_email_addrs(mptr);
hptr = mptr;
while ((*hptr != '\0') && isspace(*hptr))
hptr ++;
if (!IsEmptyStr(hptr))
cprintf("Envelope-To: %s%s", hptr, nl);
break;
case eMsgSubject:
cprintf("Subject: %s%s", mptr, nl);
subject_found = 1;
break;
case emessageId:
safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
break;
case erFc822Addr:
safestrncpy(fuser, mptr, sizeof_fuser);
/* case eOriginalRoom:
cprintf("X-Citadel-Room: %s%s",
mptr, nl)
break;
; */
case eNodeName:
safestrncpy(snode, mptr, sizeof_snode);
break;
case eRecipient:
if (haschar(mptr, '@') == 0)
{
sanitize_truncated_recipient(mptr);
cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
cprintf("%s", nl);
}
else
{
if ((flags & QP_EADDR) != 0) {
mptr = qp_encode_email_addrs(mptr);
}
sanitize_truncated_recipient(mptr);
cprintf("To: %s", mptr);
cprintf("%s", nl);
}
break;
case eTimestamp:
datestring(datestamp, sizeof datestamp,
atol(mptr), DATESTRING_RFC822);
cprintf("Date: %s%s", datestamp, nl);
break;
case eWeferences:
cprintf("References: ");
k = num_tokens(mptr, '|');
for (j=0; j", buf);
if (j == (k-1)) {
cprintf("%s", nl);
}
else {
cprintf(" ");
}
}
break;
case eReplyTo:
hptr = mptr;
while ((*hptr != '\0') && isspace(*hptr))
hptr ++;
if (!IsEmptyStr(hptr))
cprintf("Reply-To: %s%s", mptr, nl);
break;
case eRemoteRoom:
case eDestination:
case eExclusiveID:
case eHumanNode:
case eJournal:
case eMesageText:
case eBig_message:
case eOriginalRoom:
case eSpecialField:
case eErrorMsg:
case eSuppressIdx:
case eExtnotify:
case eVltMsgNum:
/* these don't map to mime message headers. */
break;
}
if (mptr != mpptr)
free (mptr);
}
}
if (subject_found == 0) {
cprintf("Subject: (no subject)%s", nl);
}
}
void Dump_RFC822HeadersBody(
struct CtdlMessage *TheMessage,
int headers_only, /* eschew the message body? */
int flags, /* should the bessage be exported clean? */
const char *nl, int nlen)
{
cit_uint8_t prev_ch;
int eoh = 0;
const char *StartOfText = StrBufNOTNULL;
char outbuf[1024];
int outlen = 0;
int nllen = strlen(nl);
char *mptr;
int lfSent = 0;
mptr = TheMessage->cm_fields[eMesageText];
prev_ch = '\0';
while (*mptr != '\0') {
if (*mptr == '\r') {
/* do nothing */
}
else {
if ((!eoh) &&
(*mptr == '\n'))
{
eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
if (!eoh)
eoh = *(mptr+1) == '\n';
if (eoh)
{
StartOfText = mptr;
StartOfText = strchr(StartOfText, '\n');
StartOfText = strchr(StartOfText, '\n');
}
}
if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
((headers_only != HEADERS_NONE) &&
(headers_only != HEADERS_ONLY))
) {
if (*mptr == '\n') {
memcpy(&outbuf[outlen], nl, nllen);
outlen += nllen;
outbuf[outlen] = '\0';
}
else {
outbuf[outlen++] = *mptr;
}
}
}
if (flags & ESC_DOT)
{
if ((prev_ch == '\n') &&
(*mptr == '.') &&
((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
{
outbuf[outlen++] = '.';
}
prev_ch = *mptr;
}
++mptr;
if (outlen > 1000) {
if (client_write(outbuf, outlen) == -1)
{
syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
return;
}
lfSent = (outbuf[outlen - 1] == '\n');
outlen = 0;
}
}
if (outlen > 0) {
client_write(outbuf, outlen);
lfSent = (outbuf[outlen - 1] == '\n');
}
if (!lfSent)
client_write(nl, nlen);
}
/* If the format type on disk is 1 (fixed-format), then we want
* everything to be output completely literally ... regardless of
* what message transfer format is in use.
*/
void DumpFormatFixed(
struct CtdlMessage *TheMessage,
int mode, /* how would you like that message? */
const char *nl, int nllen)
{
cit_uint8_t ch;
char buf[SIZ];
int buflen;
int xlline = 0;
char *mptr;
mptr = TheMessage->cm_fields[eMesageText];
if (mode == MT_MIME) {
cprintf("Content-type: text/plain\n\n");
}
*buf = '\0';
buflen = 0;
while (ch = *mptr++, ch > 0) {
if (ch == '\n')
ch = '\r';
if ((buflen > 250) && (!xlline)){
int tbuflen;
tbuflen = buflen;
while ((buflen > 0) &&
(!isspace(buf[buflen])))
buflen --;
if (buflen == 0) {
xlline = 1;
}
else {
mptr -= tbuflen - buflen;
buf[buflen] = '\0';
ch = '\r';
}
}
/* if we reach the outer bounds of our buffer,
abort without respect what whe purge. */
if (xlline &&
((isspace(ch)) ||
(buflen > SIZ - nllen - 2)))
ch = '\r';
if (ch == '\r') {
memcpy (&buf[buflen], nl, nllen);
buflen += nllen;
buf[buflen] = '\0';
if (client_write(buf, buflen) == -1)
{
syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
return;
}
*buf = '\0';
buflen = 0;
xlline = 0;
} else {
buf[buflen] = ch;
buflen++;
}
}
buf[buflen] = '\0';
if (!IsEmptyStr(buf))
cprintf("%s%s", buf, nl);
}
/*
* Get a message off disk. (returns om_* values found in msgbase.h)
*/
int CtdlOutputPreLoadedMsg(
struct CtdlMessage *TheMessage,
int mode, /* how would you like that message? */
int headers_only, /* eschew the message body? */
int do_proto, /* do Citadel protocol responses? */
int crlf, /* Use CRLF newlines instead of LF? */
int flags /* should the bessage be exported clean? */
) {
int i;
const char *nl; /* newline string */
int nlen;
struct ma_info ma;
/* Buffers needed for RFC822 translation. These are all filled
* using functions that are bounds-checked, and therefore we can
* make them substantially smaller than SIZ.
*/
char suser[100];
char luser[100];
char fuser[100];
char snode[100];
char mid[100];
syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
((TheMessage == NULL) ? "NULL" : "not null"),
mode, headers_only, do_proto, crlf
);
strcpy(mid, "unknown");
nl = (crlf ? "\r\n" : "\n");
nlen = crlf ? 2 : 1;
if (!CM_IsValidMsg(TheMessage)) {
syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
cit_backtrace ();
return(om_no_such_msg);
}
/* Suppress envelope recipients if required to avoid disclosing BCC addresses.
* Pad it with spaces in order to avoid changing the RFC822 length of the message.
*/
if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
}
/* Are we downloading a MIME component? */
if (mode == MT_DOWNLOAD) {
if (TheMessage->cm_format_type != FMT_RFC822) {
if (do_proto)
cprintf("%d This is not a MIME message.\n",
ERROR + ILLEGAL_VALUE);
} else if (CC->download_fp != NULL) {
if (do_proto) cprintf(
"%d You already have a download open.\n",
ERROR + RESOURCE_BUSY);
} else {
/* Parse the message text component */
mime_parser(CM_RANGE(TheMessage, eMesageText),
*mime_download, NULL, NULL, NULL, 0);
/* If there's no file open by this time, the requested
* section wasn't found, so print an error
*/
if (CC->download_fp == NULL) {
if (do_proto) cprintf(
"%d Section %s not found.\n",
ERROR + FILE_NOT_FOUND,
CC->download_desired_section);
}
}
return((CC->download_fp != NULL) ? om_ok : om_mime_error);
}
/* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
* in a single server operation instead of opening a download file.
*/
if (mode == MT_SPEW_SECTION) {
if (TheMessage->cm_format_type != FMT_RFC822) {
if (do_proto)
cprintf("%d This is not a MIME message.\n",
ERROR + ILLEGAL_VALUE);
} else {
/* Parse the message text component */
int found_it = 0;
mime_parser(CM_RANGE(TheMessage, eMesageText),
*mime_spew_section, NULL, NULL, (void *)&found_it, 0);
/* If section wasn't found, print an error
*/
if (!found_it) {
if (do_proto) cprintf(
"%d Section %s not found.\n",
ERROR + FILE_NOT_FOUND,
CC->download_desired_section);
}
}
return((CC->download_fp != NULL) ? om_ok : om_mime_error);
}
/* now for the user-mode message reading loops */
if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
/* Does the caller want to skip the headers? */
if (headers_only == HEADERS_NONE) goto START_TEXT;
/* Tell the client which format type we're using. */
if ( (mode == MT_CITADEL) && (do_proto) ) {
cprintf("type=%d\n", TheMessage->cm_format_type);
}
/* nhdr=yes means that we're only displaying headers, no body */
if ( (TheMessage->cm_anon_type == MES_ANONONLY)
&& ((mode == MT_CITADEL) || (mode == MT_MIME))
&& (do_proto)
) {
cprintf("nhdr=yes\n");
}
if ((mode == MT_CITADEL) || (mode == MT_MIME))
OutputCtdlMsgHeaders(TheMessage, do_proto);
/* begin header processing loop for RFC822 transfer format */
strcpy(suser, "");
strcpy(luser, "");
strcpy(fuser, "");
memcpy(snode, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")) + 1);
if (mode == MT_RFC822)
OutputRFC822MsgHeaders(
TheMessage,
flags,
nl, nlen,
mid, sizeof(mid),
suser, sizeof(suser),
luser, sizeof(luser),
fuser, sizeof(fuser),
snode, sizeof(snode)
);
for (i=0; !IsEmptyStr(&suser[i]); ++i) {
suser[i] = tolower(suser[i]);
if (!isalnum(suser[i])) suser[i]='_';
}
if (mode == MT_RFC822) {
if (!strcasecmp(snode, NODENAME)) {
safestrncpy(snode, FQDN, sizeof snode);
}
/* Construct a fun message id */
cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
if (strchr(mid, '@')==NULL) {
cprintf("@%s", snode);
}
cprintf(">%s", nl);
if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
cprintf("From: \"----\" %s", nl);
}
else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
cprintf("From: \"anonymous\" %s", nl);
}
else if (!IsEmptyStr(fuser)) {
cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
}
else {
cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
}
/* Blank line signifying RFC822 end-of-headers */
if (TheMessage->cm_format_type != FMT_RFC822) {
cprintf("%s", nl);
}
}
/* end header processing loop ... at this point, we're in the text */
START_TEXT:
if (headers_only == HEADERS_FAST) goto DONE;
/* Tell the client about the MIME parts in this message */
if (TheMessage->cm_format_type == FMT_RFC822) {
if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
memset(&ma, 0, sizeof(struct ma_info));
mime_parser(CM_RANGE(TheMessage, eMesageText),
(do_proto ? *list_this_part : NULL),
(do_proto ? *list_this_pref : NULL),
(do_proto ? *list_this_suff : NULL),
(void *)&ma, 1);
}
else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
Dump_RFC822HeadersBody(
TheMessage,
headers_only,
flags,
nl, nlen);
goto DONE;
}
}
if (headers_only == HEADERS_ONLY) {
goto DONE;
}
/* signify start of msg text */
if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
if (do_proto) cprintf("text\n");
}
if (TheMessage->cm_format_type == FMT_FIXED)
DumpFormatFixed(
TheMessage,
mode, /* how would you like that message? */
nl, nlen);
/* If the message on disk is format 0 (Citadel vari-format), we
* output using the formatter at 80 columns. This is the final output
* form if the transfer format is RFC822, but if the transfer format
* is Citadel proprietary, it'll still work, because the indentation
* for new paragraphs is correct and the client will reformat the
* message to the reader's screen width.
*/
if (TheMessage->cm_format_type == FMT_CITADEL) {
if (mode == MT_MIME) {
cprintf("Content-type: text/x-citadel-variformat\n\n");
}
memfmout(TheMessage->cm_fields[eMesageText], nl);
}
/* If the message on disk is format 4 (MIME), we've gotta hand it
* off to the MIME parser. The client has already been told that
* this message is format 1 (fixed format), so the callback function
* we use will display those parts as-is.
*/
if (TheMessage->cm_format_type == FMT_RFC822) {
memset(&ma, 0, sizeof(struct ma_info));
if (mode == MT_MIME) {
ma.use_fo_hooks = 0;
strcpy(ma.chosen_part, "1");
ma.chosen_pref = 9999;
ma.dont_decode = CC->msg4_dont_decode;
mime_parser(CM_RANGE(TheMessage, eMesageText),
*choose_preferred, *fixed_output_pre,
*fixed_output_post, (void *)&ma, 1);
mime_parser(CM_RANGE(TheMessage, eMesageText),
*output_preferred, NULL, NULL, (void *)&ma, 1);
}
else {
ma.use_fo_hooks = 1;
mime_parser(CM_RANGE(TheMessage, eMesageText),
*fixed_output, *fixed_output_pre,
*fixed_output_post, (void *)&ma, 0);
}
}
DONE: /* now we're done */
if (do_proto) cprintf("000\n");
return(om_ok);
}
/*
* Save one or more message pointers into a specified room
* (Returns 0 for success, nonzero for failure)
* roomname may be NULL to use the current room
*
* Note that the 'supplied_msg' field may be set to NULL, in which case
* the message will be fetched from disk, by number, if we need to perform
* replication checks. This adds an additional database read, so if the
* caller already has the message in memory then it should be supplied. (Obviously
* this mode of operation only works if we're saving a single message.)
*/
int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
) {
int i, j, unique;
char hold_rm[ROOMNAMELEN];
struct cdbdata *cdbfr;
int num_msgs;
long *msglist;
long highest_msg = 0L;
long msgid = 0;
struct CtdlMessage *msg = NULL;
long *msgs_to_be_merged = NULL;
int num_msgs_to_be_merged = 0;
syslog(LOG_DEBUG,
"msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
);
strcpy(hold_rm, CC->room.QRname);
/* Sanity checks */
if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
if (num_newmsgs > 1) supplied_msg = NULL;
/* Now the regular stuff */
if (CtdlGetRoomLock(&CC->room,
((roomname != NULL) ? roomname : CC->room.QRname) )
!= 0) {
syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
return(ERROR + ROOM_NOT_FOUND);
}
msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
num_msgs_to_be_merged = 0;
cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
if (cdbfr == NULL) {
msglist = NULL;
num_msgs = 0;
} else {
msglist = (long *) cdbfr->ptr;
cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
num_msgs = cdbfr->len / sizeof(long);
cdb_free(cdbfr);
}
/* Create a list of msgid's which were supplied by the caller, but do
* not already exist in the target room. It is absolutely taboo to
* have more than one reference to the same message in a room.
*/
for (i=0; i 0) for (j=0; jroom.QRnumber, (int)sizeof(long),
msglist, (int)(num_msgs * sizeof(long)));
/* Free up the memory we used. */
free(msglist);
/* Update the highest-message pointer and unlock the room. */
CC->room.QRhighest = highest_msg;
CtdlPutRoomLock(&CC->room);
/* Perform replication checks if necessary */
if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
for (i=0; icm_fields[eExclusiveID], &CC->room, msgid);
}
/* Free up the memory we may have allocated */
if (msg != supplied_msg) {
CM_Free(msg);
}
}
}
}
else {
syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
}
/* Submit this room for processing by hooks */
PerformRoomHooks(&CC->room);
/* Go back to the room we were in before we wandered here... */
CtdlGetRoom(&CC->room, hold_rm);
/* Bump the reference count for all messages which were merged */
if (!suppress_refcount_adj) {
AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
}
/* Free up memory... */
if (msgs_to_be_merged != NULL) {
free(msgs_to_be_merged);
}
/* Return success. */
return (0);
}
/*
* This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
* a single message.
*/
int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
int do_repl_check, struct CtdlMessage *supplied_msg)
{
return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
}
/*
* Message base operation to save a new message to the message store
* (returns new message number)
*
* This is the back end for CtdlSubmitMsg() and should not be directly
* called by server-side modules.
*
*/
long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
long retval;
struct ser_ret smr;
int is_bigmsg = 0;
char *holdM = NULL;
long holdMLen = 0;
/*
* If the message is big, set its body aside for storage elsewhere
* and we hide the message body from the serializer
*/
if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG)
{
is_bigmsg = 1;
holdM = msg->cm_fields[eMesageText];
msg->cm_fields[eMesageText] = NULL;
holdMLen = msg->cm_lengths[eMesageText];
msg->cm_lengths[eMesageText] = 0;
}
/* Serialize our data structure for storage in the database */
CtdlSerializeMessage(&smr, msg);
if (is_bigmsg) {
/* put the message body back into the message */
msg->cm_fields[eMesageText] = holdM;
msg->cm_lengths[eMesageText] = holdMLen;
}
if (smr.len == 0) {
if (Reply) {
cprintf("%d Unable to serialize message\n",
ERROR + INTERNAL_ERROR);
}
else {
syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
}
return (-1L);
}
/* Write our little bundle of joy into the message base */
retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long),
smr.ser, smr.len);
if (retval < 0) {
syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
}
else {
if (is_bigmsg) {
retval = cdb_store(CDB_BIGMSGS,
&msgid,
(int)sizeof(long),
holdM,
(holdMLen + 1)
);
if (retval < 0) {
syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
}
}
}
/* Free the memory we used for the serialized message */
free(smr.ser);
return(retval);
}
long send_message(struct CtdlMessage *msg) {
long newmsgid;
long retval;
char msgidbuf[256];
long msgidbuflen;
/* Get a new message number */
newmsgid = get_new_message_number();
/* Generate an ID if we don't have one already */
if (CM_IsEmpty(msg, emessageId)) {
msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
(long unsigned int) time(NULL),
(long unsigned int) newmsgid,
CtdlGetConfigStr("c_fqdn")
);
CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
}
retval = CtdlSaveThisMessage(msg, newmsgid, 1);
if (retval == 0) {
retval = newmsgid;
}
/* Return the *local* message ID to the caller
* (even if we're storing an incoming network message)
*/
return(retval);
}
/*
* Serialize a struct CtdlMessage into the format used on disk and network.
*
* This function loads up a "struct ser_ret" (defined in server.h) which
* contains the length of the serialized message and a pointer to the
* serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
*/
void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
struct CtdlMessage *msg) /* unserialized msg */
{
size_t wlen;
int i;
/*
* Check for valid message format
*/
if (CM_IsValidMsg(msg) == 0) {
syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
ret->len = 0;
ret->ser = NULL;
return;
}
ret->len = 3;
for (i=0; i < NDiskFields; ++i)
if (msg->cm_fields[FieldOrder[i]] != NULL)
ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
ret->ser = malloc(ret->len);
if (ret->ser == NULL) {
syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
ret->len = 0;
ret->ser = NULL;
return;
}
ret->ser[0] = 0xFF;
ret->ser[1] = msg->cm_anon_type;
ret->ser[2] = msg->cm_format_type;
wlen = 3;
for (i=0; i < NDiskFields; ++i)
if (msg->cm_fields[FieldOrder[i]] != NULL)
{
ret->ser[wlen++] = (char)FieldOrder[i];
memcpy(&ret->ser[wlen],
msg->cm_fields[FieldOrder[i]],
msg->cm_lengths[FieldOrder[i]] + 1);
wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
}
if (ret->len != wlen) {
syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
}
return;
}
/*
* Check to see if any messages already exist in the current room which
* carry the same Exclusive ID as this one. If any are found, delete them.
*/
void ReplicationChecks(struct CtdlMessage *msg) {
long old_msgnum = (-1L);
if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
/* No exclusive id? Don't do anything. */
if (msg == NULL) return;
if (CM_IsEmpty(msg, eExclusiveID)) return;
/*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
msg->cm_fields[eExclusiveID], CC->room.QRname);*/
old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
if (old_msgnum > 0L) {
syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
}
}
/*
* Save a message to disk and submit it into the delivery system.
*/
long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
recptypes *recps, /* recipients (if mail) */
const char *force, /* force a particular room? */
int flags /* should the message be exported clean? */
)
{
char hold_rm[ROOMNAMELEN];
char actual_rm[ROOMNAMELEN];
char force_room[ROOMNAMELEN];
char content_type[SIZ]; /* We have to learn this */
char recipient[SIZ];
char bounce_to[1024];
const char *room;
long newmsgid;
const char *mptr = NULL;
struct ctdluser userbuf;
int a, i;
struct MetaData smi;
char *collected_addresses = NULL;
struct addresses_to_be_filed *aptr = NULL;
StrBuf *saved_rfc822_version = NULL;
int qualified_for_journaling = 0;
syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
/* If this message has no timestamp, we take the liberty of
* giving it one, right now.
*/
if (CM_IsEmpty(msg, eTimestamp)) {
CM_SetFieldLONG(msg, eTimestamp, time(NULL));
}
/* If this message has no path, we generate one.
*/
if (CM_IsEmpty(msg, eMessagePath)) {
if (!CM_IsEmpty(msg, eAuthor)) {
CM_CopyField(msg, eMessagePath, eAuthor);
for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
if (isspace(msg->cm_fields[eMessagePath][a])) {
msg->cm_fields[eMessagePath][a] = ' ';
}
}
}
else {
CM_SetField(msg, eMessagePath, HKEY("unknown"));
}
}
if (force == NULL) {
force_room[0] = '\0';
}
else {
strcpy(force_room, force);
}
/* Learn about what's inside, because it's what's inside that counts */
if (CM_IsEmpty(msg, eMesageText)) {
syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
return(-2);
}
switch (msg->cm_format_type) {
case 0:
strcpy(content_type, "text/x-citadel-variformat");
break;
case 1:
strcpy(content_type, "text/plain");
break;
case 4:
strcpy(content_type, "text/plain");
mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
if (mptr != NULL) {
char *aptr;
safestrncpy(content_type, &mptr[13], sizeof content_type);
striplt(content_type);
aptr = content_type;
while (!IsEmptyStr(aptr)) {
if ((*aptr == ';')
|| (*aptr == ' ')
|| (*aptr == 13)
|| (*aptr == 10)) {
*aptr = 0;
}
else aptr++;
}
}
}
/* Goto the correct room */
room = (recps) ? CC->room.QRname : SENTITEMS;
syslog(LOG_DEBUG, "msgbase: selected room %s", room);
strcpy(hold_rm, CC->room.QRname);
strcpy(actual_rm, CC->room.QRname);
if (recps != NULL) {
strcpy(actual_rm, SENTITEMS);
}
/* If the user is a twit, move to the twit room for posting */
if (TWITDETECT) {
if (CC->user.axlevel == AxProbU) {
strcpy(hold_rm, actual_rm);
strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
syslog(LOG_DEBUG, "msgbase: diverting to twit room");
}
}
/* ...or if this message is destined for Aide> then go there. */
if (!IsEmptyStr(force_room)) {
strcpy(actual_rm, force_room);
}
syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
if (strcasecmp(actual_rm, CC->room.QRname)) {
/* CtdlGetRoom(&CC->room, actual_rm); */
CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
}
/*
* If this message has no O (room) field, generate one.
*/
if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
}
/* Perform "before save" hooks (aborting if any return nonzero) */
syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
/*
* If this message has an Exclusive ID, and the room is replication
* checking enabled, then do replication checks.
*/
if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
ReplicationChecks(msg);
}
/* Save it to disk */
syslog(LOG_DEBUG, "msgbase: saving to disk");
newmsgid = send_message(msg);
if (newmsgid <= 0L) return(-5);
/* Write a supplemental message info record. This doesn't have to
* be a critical section because nobody else knows about this message
* yet.
*/
syslog(LOG_DEBUG, "msgbase: creating metadata record");
memset(&smi, 0, sizeof(struct MetaData));
smi.meta_msgnum = newmsgid;
smi.meta_refcount = 0;
safestrncpy(smi.meta_content_type, content_type,
sizeof smi.meta_content_type);
/*
* Measure how big this message will be when rendered as RFC822.
* We do this for two reasons:
* 1. We need the RFC822 length for the new metadata record, so the
* POP and IMAP services don't have to calculate message lengths
* while the user is waiting (multiplied by potentially hundreds
* or thousands of messages).
* 2. If journaling is enabled, we will need an RFC822 version of the
* message to attach to the journalized copy.
*/
if (CC->redirect_buffer != NULL) {
syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
abort();
}
CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
saved_rfc822_version = CC->redirect_buffer;
CC->redirect_buffer = NULL;
PutMetaData(&smi);
/* Now figure out where to store the pointers */
syslog(LOG_DEBUG, "msgbase: storing pointers");
/* If this is being done by the networker delivering a private
* message, we want to BYPASS saving the sender's copy (because there
* is no local sender; it would otherwise go to the Trashcan).
*/
if ((!CC->internal_pgm) || (recps == NULL)) {
if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
syslog(LOG_ERR, "msgbase: ERROR saving message pointer!");
CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
}
}
/* For internet mail, drop a copy in the outbound queue room */
if ((recps != NULL) && (recps->num_internet > 0)) {
CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
}
/* If other rooms are specified, drop them there too. */
if ((recps != NULL) && (recps->num_room > 0))
for (i=0; irecp_room, '|'); ++i) {
extract_token(recipient, recps->recp_room, i,
'|', sizeof recipient);
syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
}
/* Bump this user's messages posted counter. */
syslog(LOG_DEBUG, "msgbase: updating user");
CtdlLockGetCurrentUser();
CC->user.posted = CC->user.posted + 1;
CtdlPutCurrentUserLock();
/* Decide where bounces need to be delivered */
if ((recps != NULL) && (recps->bounce_to == NULL))
{
if (CC->logged_in)
snprintf(bounce_to, sizeof bounce_to, "%s@%s",
CC->user.fullname, CtdlGetConfigStr("c_nodename"));
else
snprintf(bounce_to, sizeof bounce_to, "%s@%s",
msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
recps->bounce_to = bounce_to;
}
CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
/* If this is private, local mail, make a copy in the
* recipient's mailbox and bump the reference count.
*/
if ((recps != NULL) && (recps->num_local > 0))
{
char *pch;
int ntokens;
pch = recps->recp_local;
recps->recp_local = recipient;
ntokens = num_tokens(pch, '|');
for (i=0; i", recipient);
if (CtdlGetUser(&userbuf, recipient) == 0) {
CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
CtdlBumpNewMailCounter(userbuf.usernum);
PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
}
else {
syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
}
}
recps->recp_local = pch;
}
/* Perform "after save" hooks */
syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
CM_FlushField(msg, eVltMsgNum);
/* Go back to the room we started from */
syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
if (strcasecmp(hold_rm, CC->room.QRname))
CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
/*
* Any addresses to harvest for someone's address book?
*/
if ( (CC->logged_in) && (recps != NULL) ) {
collected_addresses = harvest_collected_addresses(msg);
}
if (collected_addresses != NULL) {
aptr = (struct addresses_to_be_filed *)
malloc(sizeof(struct addresses_to_be_filed));
CtdlMailboxName(actual_rm, sizeof actual_rm,
&CC->user, USERCONTACTSROOM);
aptr->roomname = strdup(actual_rm);
aptr->collected_addresses = collected_addresses;
begin_critical_section(S_ATBF);
aptr->next = atbf;
atbf = aptr;
end_critical_section(S_ATBF);
}
/*
* Determine whether this message qualifies for journaling.
*/
if (!CM_IsEmpty(msg, eJournal)) {
qualified_for_journaling = 0;
}
else {
if (recps == NULL) {
qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
}
else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
}
else {
qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
}
}
/*
* Do we have to perform journaling? If so, hand off the saved
* RFC822 version will be handed off to the journaler for background
* submit. Otherwise, we have to free the memory ourselves.
*/
if (saved_rfc822_version != NULL) {
if (qualified_for_journaling) {
JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
}
else {
FreeStrBuf(&saved_rfc822_version);
}
}
if ((recps != NULL) && (recps->bounce_to == bounce_to))
recps->bounce_to = NULL;
/* Done. */
return(newmsgid);
}
/*
* Convenience function for generating small administrative messages.
*/
long quickie_message(const char *from,
const char *fromaddr,
const char *to,
char *room,
const char *text,
int format_type,
const char *subject)
{
struct CtdlMessage *msg;
recptypes *recp = NULL;
msg = malloc(sizeof(struct CtdlMessage));
memset(msg, 0, sizeof(struct CtdlMessage));
msg->cm_magic = CTDLMESSAGE_MAGIC;
msg->cm_anon_type = MES_NORMAL;
msg->cm_format_type = format_type;
if (!IsEmptyStr(from)) {
CM_SetField(msg, eAuthor, from, strlen(from));
}
else if (!IsEmptyStr(fromaddr)) {
char *pAt;
CM_SetField(msg, eAuthor, fromaddr, strlen(fromaddr));
pAt = strchr(msg->cm_fields[eAuthor], '@');
if (pAt != NULL) {
CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
}
}
else {
msg->cm_fields[eAuthor] = strdup("Citadel");
}
if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, strlen(fromaddr));
if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, strlen(room));
CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
if (!IsEmptyStr(to)) {
CM_SetField(msg, eRecipient, to, strlen(to));
recp = validate_recipients(to, NULL, 0);
}
if (!IsEmptyStr(subject)) {
CM_SetField(msg, eMsgSubject, subject, strlen(subject));
}
if (!IsEmptyStr(text)) {
CM_SetField(msg, eMesageText, text, strlen(text));
}
long msgnum = CtdlSubmitMsg(msg, recp, room, 0);
CM_Free(msg);
if (recp != NULL) free_recipients(recp);
return msgnum;
}
/*
* Back end function used by CtdlMakeMessage() and similar functions
*/
StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
long tlen,
size_t maxlen, /* maximum message length */
StrBuf *exist, /* if non-null, append to it;
exist is ALWAYS freed */
int crlf /* CRLF newlines instead of LF */
)
{
StrBuf *Message;
StrBuf *LineBuf;
int flushing = 0;
int finished = 0;
int dotdot = 0;
LineBuf = NewStrBufPlain(NULL, SIZ);
if (exist == NULL) {
Message = NewStrBufPlain(NULL, 4 * SIZ);
}
else {
Message = NewStrBufDup(exist);
}
/* Do we need to change leading ".." to "." for SMTP escaping? */
if ((tlen == 1) && (*terminator == '.')) {
dotdot = 1;
}
/* read in the lines of message text one by one */
do {
if (CtdlClientGetLine(LineBuf) < 0) {
finished = 1;
}
if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
finished = 1;
}
if ( (!flushing) && (!finished) ) {
if (crlf) {
StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
}
else {
StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
}
/* Unescape SMTP-style input of two dots at the beginning of the line */
if ((dotdot) &&
(StrLength(LineBuf) == 2) &&
(!strcmp(ChrPtr(LineBuf), "..")))
{
StrBufCutLeft(LineBuf, 1);
}
StrBufAppendBuf(Message, LineBuf, 0);
}
/* if we've hit the max msg length, flush the rest */
if (StrLength(Message) >= maxlen) flushing = 1;
} while (!finished);
FreeStrBuf(&LineBuf);
return Message;
}
/*
* Back end function used by CtdlMakeMessage() and similar functions
*/
char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
long tlen,
size_t maxlen, /* maximum message length */
StrBuf *exist, /* if non-null, append to it;
exist is ALWAYS freed */
int crlf /* CRLF newlines instead of LF */
)
{
StrBuf *Message;
Message = CtdlReadMessageBodyBuf(terminator,
tlen,
maxlen,
exist,
crlf
);
if (Message == NULL)
return NULL;
else
return SmashStrBuf(&Message);
}
struct CtdlMessage *CtdlMakeMessage(
struct ctdluser *author, /* author's user structure */
char *recipient, /* NULL if it's not mail */
char *recp_cc, /* NULL if it's not mail */
char *room, /* room where it's going */
int type, /* see MES_ types in header file */
int format_type, /* variformat, plain text, MIME... */
char *fake_name, /* who we're masquerading as */
char *my_email, /* which of my email addresses to use (empty is ok) */
char *subject, /* Subject (optional) */
char *supplied_euid, /* ...or NULL if this is irrelevant */
char *preformatted_text, /* ...or NULL to read text from client */
char *references /* Thread references */
)
{
return CtdlMakeMessageLen(
author, /* author's user structure */
recipient, /* NULL if it's not mail */
(recipient)?strlen(recipient) : 0,
recp_cc, /* NULL if it's not mail */
(recp_cc)?strlen(recp_cc): 0,
room, /* room where it's going */
(room)?strlen(room): 0,
type, /* see MES_ types in header file */
format_type, /* variformat, plain text, MIME... */
fake_name, /* who we're masquerading as */
(fake_name)?strlen(fake_name): 0,
my_email, /* which of my email addresses to use (empty is ok) */
(my_email)?strlen(my_email): 0,
subject, /* Subject (optional) */
(subject)?strlen(subject): 0,
supplied_euid, /* ...or NULL if this is irrelevant */
(supplied_euid)?strlen(supplied_euid):0,
preformatted_text, /* ...or NULL to read text from client */
(preformatted_text)?strlen(preformatted_text) : 0,
references, /* Thread references */
(references)?strlen(references):0);
}
/*
* Build a binary message to be saved on disk.
* (NOTE: if you supply 'preformatted_text', the buffer you give it
* will become part of the message. This means you are no longer
* responsible for managing that memory -- it will be freed along with
* the rest of the fields when CM_Free() is called.)
*/
struct CtdlMessage *CtdlMakeMessageLen(
struct ctdluser *author, /* author's user structure */
char *recipient, /* NULL if it's not mail */
long rcplen,
char *recp_cc, /* NULL if it's not mail */
long cclen,
char *room, /* room where it's going */
long roomlen,
int type, /* see MES_ types in header file */
int format_type, /* variformat, plain text, MIME... */
char *fake_name, /* who we're masquerading as */
long fnlen,
char *my_email, /* which of my email addresses to use (empty is ok) */
long myelen,
char *subject, /* Subject (optional) */
long subjlen,
char *supplied_euid, /* ...or NULL if this is irrelevant */
long euidlen,
char *preformatted_text, /* ...or NULL to read text from client */
long textlen,
char *references, /* Thread references */
long reflen
)
{
/* Don't confuse the poor folks if it's not routed mail. * /
char dest_node[256] = "";*/
long blen;
char buf[1024];
struct CtdlMessage *msg;
StrBuf *FakeAuthor;
StrBuf *FakeEncAuthor = NULL;
msg = malloc(sizeof(struct CtdlMessage));
memset(msg, 0, sizeof(struct CtdlMessage));
msg->cm_magic = CTDLMESSAGE_MAGIC;
msg->cm_anon_type = type;
msg->cm_format_type = format_type;
if (recipient != NULL) rcplen = striplt(recipient);
if (recp_cc != NULL) cclen = striplt(recp_cc);
/* Path or Return-Path */
if (myelen > 0) {
CM_SetField(msg, eMessagePath, my_email, myelen);
}
else if (!IsEmptyStr(author->fullname)) {
CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname));
}
convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
CM_SetField(msg, eTimestamp, buf, blen);
if (fnlen > 0) {
FakeAuthor = NewStrBufPlain (fake_name, fnlen);
}
else {
FakeAuthor = NewStrBufPlain (author->fullname, -1);
}
StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
FreeStrBuf(&FakeAuthor);
if (!!IsEmptyStr(CC->room.QRname)) {
if (CC->room.QRflags & QR_MAILBOX) { /* room */
CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], strlen(&CC->room.QRname[11]));
}
else {
CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
}
}
CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
CM_SetField(msg, eHumanNode, CtdlGetConfigStr("c_humannode"), strlen(CtdlGetConfigStr("c_humannode")));
if (rcplen > 0) {
CM_SetField(msg, eRecipient, recipient, rcplen);
}
if (cclen > 0) {
CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
}
if (myelen > 0) {
CM_SetField(msg, erFc822Addr, my_email, myelen);
}
else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
}
if (subject != NULL) {
long length;
length = striplt(subject);
if (length > 0) {
long i;
long IsAscii;
IsAscii = -1;
i = 0;
while ((subject[i] != '\0') &&
(IsAscii = isascii(subject[i]) != 0 ))
i++;
if (IsAscii != 0)
CM_SetField(msg, eMsgSubject, subject, subjlen);
else /* ok, we've got utf8 in the string. */
{
char *rfc2047Subj;
rfc2047Subj = rfc2047encode(subject, length);
CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
}
}
}
if (euidlen > 0) {
CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
}
if (reflen > 0) {
CM_SetField(msg, eWeferences, references, reflen);
}
if (preformatted_text != NULL) {
CM_SetField(msg, eMesageText, preformatted_text, textlen);
}
else {
StrBuf *MsgBody;
MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
if (MsgBody != NULL) {
CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
}
}
return(msg);
}
/*
* API function to delete messages which match a set of criteria
* (returns the actual number of messages deleted)
*/
int CtdlDeleteMessages(const char *room_name, /* which room */
long *dmsgnums, /* array of msg numbers to be deleted */
int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
char *content_type /* or "" for any. regular expressions expected. */
)
{
struct ctdlroom qrbuf;
struct cdbdata *cdbfr;
long *msglist = NULL;
long *dellist = NULL;
int num_msgs = 0;
int i, j;
int num_deleted = 0;
int delete_this;
struct MetaData smi;
regex_t re;
regmatch_t pm;
int need_to_free_re = 0;
if (content_type) if (!IsEmptyStr(content_type)) {
regcomp(&re, content_type, 0);
need_to_free_re = 1;
}
syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
/* get room record, obtaining a lock... */
if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
if (need_to_free_re) regfree(&re);
return(0); /* room not found */
}
cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
if (cdbfr != NULL) {
dellist = malloc(cdbfr->len);
msglist = (long *) cdbfr->ptr;
cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
num_msgs = cdbfr->len / sizeof(long);
cdb_free(cdbfr);
}
if (num_msgs > 0) {
int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
int have_more_del = 1;
num_msgs = sort_msglist(msglist, num_msgs);
if (num_dmsgnums > 1)
num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
/*
{
StrBuf *dbg = NewStrBuf();
for (i = 0; i < num_dmsgnums; i++)
StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
FreeStrBuf(&dbg);
}
*/
i = 0; j = 0;
while ((i < num_msgs) && (have_more_del)) {
delete_this = 0x00;
/* Set/clear a bit for each criterion */
/* 0 messages in the list or a null list means that we are
* interested in deleting any messages which meet the other criteria.
*/
if (have_delmsgs) {
delete_this |= 0x01;
}
else {
while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
if (i >= num_msgs)
continue;
if (msglist[i] == dmsgnums[j]) {
delete_this |= 0x01;
}
j++;
have_more_del = (j < num_dmsgnums);
}
if (have_contenttype) {
GetMetaData(&smi, msglist[i]);
if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
delete_this |= 0x02;
}
} else {
delete_this |= 0x02;
}
/* Delete message only if all bits are set */
if (delete_this == 0x03) {
dellist[num_deleted++] = msglist[i];
msglist[i] = 0L;
}
i++;
}
/*
{
StrBuf *dbg = NewStrBuf();
for (i = 0; i < num_deleted; i++)
StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
FreeStrBuf(&dbg);
}
*/
num_msgs = sort_msglist(msglist, num_msgs);
cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
msglist, (int)(num_msgs * sizeof(long)));
if (num_msgs > 0)
qrbuf.QRhighest = msglist[num_msgs - 1];
else
qrbuf.QRhighest = 0;
}
CtdlPutRoomLock(&qrbuf);
/* Go through the messages we pulled out of the index, and decrement
* their reference counts by 1. If this is the only room the message
* was in, the reference count will reach zero and the message will
* automatically be deleted from the database. We do this in a
* separate pass because there might be plug-in hooks getting called,
* and we don't want that happening during an S_ROOMS critical
* section.
*/
if (num_deleted) {
for (i=0; imeta_msgnum = msgnum;
smibuf->meta_refcount = 1; /* Default reference count is 1 */
/* Use the negative of the message number for its supp record index */
TheIndex = (0L - msgnum);
cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
if (cdbsmi == NULL) {
return; /* record not found; go with defaults */
}
memcpy(smibuf, cdbsmi->ptr,
((cdbsmi->len > sizeof(struct MetaData)) ?
sizeof(struct MetaData) : cdbsmi->len));
cdb_free(cdbsmi);
return;
}
/*
* PutMetaData() - (re)write supplementary record for a message
*/
void PutMetaData(struct MetaData *smibuf)
{
long TheIndex;
/* Use the negative of the message number for the metadata db index */
TheIndex = (0L - smibuf->meta_msgnum);
cdb_store(CDB_MSGMAIN,
&TheIndex, (int)sizeof(long),
smibuf, (int)sizeof(struct MetaData));
}
/*
* AdjRefCount - submit an adjustment to the reference count for a message.
* (These are just queued -- we actually process them later.)
*/
void AdjRefCount(long msgnum, int incr)
{
struct arcq new_arcq;
int rv = 0;
syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d", msgnum, incr);
begin_critical_section(S_SUPPMSGMAIN);
if (arcfp == NULL) {
arcfp = fopen(file_arcq, "ab+");
chown(file_arcq, CTDLUID, (-1));
chmod(file_arcq, 0600);
}
end_critical_section(S_SUPPMSGMAIN);
/* msgnum < 0 means that we're trying to close the file */
if (msgnum < 0) {
syslog(LOG_DEBUG, "msgbase: closing the AdjRefCount queue file");
begin_critical_section(S_SUPPMSGMAIN);
if (arcfp != NULL) {
fclose(arcfp);
arcfp = NULL;
}
end_critical_section(S_SUPPMSGMAIN);
return;
}
/*
* If we can't open the queue, perform the operation synchronously.
*/
if (arcfp == NULL) {
TDAP_AdjRefCount(msgnum, incr);
return;
}
new_arcq.arcq_msgnum = msgnum;
new_arcq.arcq_delta = incr;
rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
if (rv == -1) {
syslog(LOG_EMERG, "%s: %m", file_arcq);
}
fflush(arcfp);
return;
}
void AdjRefCountList(long *msgnum, long nmsg, int incr)
{
long i, the_size, offset;
struct arcq *new_arcq;
int rv = 0;
syslog(LOG_DEBUG, "msgbase: AdjRefCountList() msg %ld ref count delta %+d", nmsg, incr);
begin_critical_section(S_SUPPMSGMAIN);
if (arcfp == NULL) {
arcfp = fopen(file_arcq, "ab+");
chown(file_arcq, CTDLUID, (-1));
chmod(file_arcq, 0600);
}
end_critical_section(S_SUPPMSGMAIN);
/*
* If we can't open the queue, perform the operation synchronously.
*/
if (arcfp == NULL) {
for (i = 0; i < nmsg; i++)
TDAP_AdjRefCount(msgnum[i], incr);
return;
}
the_size = sizeof(struct arcq) * nmsg;
new_arcq = malloc(the_size);
for (i = 0; i < nmsg; i++) {
new_arcq[i].arcq_msgnum = msgnum[i];
new_arcq[i].arcq_delta = incr;
}
rv = 0;
offset = 0;
while ((rv >= 0) && (offset < the_size))
{
rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
if (rv == -1) {
syslog(LOG_ERR, "%s: %m", file_arcq);
}
else {
offset += rv;
}
}
free(new_arcq);
fflush(arcfp);
return;
}
/*
* TDAP_ProcessAdjRefCountQueue()
*
* Process the queue of message count adjustments that was created by calls
* to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
* for each one. This should be an "off hours" operation.
*/
int TDAP_ProcessAdjRefCountQueue(void)
{
char file_arcq_temp[PATH_MAX];
int r;
FILE *fp;
struct arcq arcq_rec;
int num_records_processed = 0;
snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
begin_critical_section(S_SUPPMSGMAIN);
if (arcfp != NULL) {
fclose(arcfp);
arcfp = NULL;
}
r = link(file_arcq, file_arcq_temp);
if (r != 0) {
syslog(LOG_ERR, "%s: %m", file_arcq_temp);
end_critical_section(S_SUPPMSGMAIN);
return(num_records_processed);
}
unlink(file_arcq);
end_critical_section(S_SUPPMSGMAIN);
fp = fopen(file_arcq_temp, "rb");
if (fp == NULL) {
syslog(LOG_ERR, "%s: %m", file_arcq_temp);
return(num_records_processed);
}
while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
++num_records_processed;
}
fclose(fp);
r = unlink(file_arcq_temp);
if (r != 0) {
syslog(LOG_ERR, "%s: %m", file_arcq_temp);
}
return(num_records_processed);
}
/*
* TDAP_AdjRefCount - adjust the reference count for a message.
* This one does it "for real" because it's called by
* the autopurger function that processes the queue
* created by AdjRefCount(). If a message's reference
* count becomes zero, we also delete the message from
* disk and de-index it.
*/
void TDAP_AdjRefCount(long msgnum, int incr)
{
struct MetaData smi;
long delnum;
/* This is a *tight* critical section; please keep it that way, as
* it may get called while nested in other critical sections.
* Complicating this any further will surely cause deadlock!
*/
begin_critical_section(S_SUPPMSGMAIN);
GetMetaData(&smi, msgnum);
smi.meta_refcount += incr;
PutMetaData(&smi);
end_critical_section(S_SUPPMSGMAIN);
syslog(LOG_DEBUG, "msgbase: TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d",
msgnum, incr, smi.meta_refcount
);
/* If the reference count is now zero, delete the message
* (and its supplementary record as well).
*/
if (smi.meta_refcount == 0) {
syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
/* Call delete hooks with NULL room to show it has gone altogether */
PerformDeleteHooks(NULL, msgnum);
/* Remove from message base */
delnum = msgnum;
cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
/* Remove metadata record */
delnum = (0L - msgnum);
cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
}
}
/*
* Write a generic object to this room
*
* Note: this could be much more efficient. Right now we use two temporary
* files, and still pull the message into memory as with all others.
*/
void CtdlWriteObject(char *req_room, /* Room to stuff it in */
char *content_type, /* MIME type of this object */
char *raw_message, /* Data to be written */
off_t raw_length, /* Size of raw_message */
struct ctdluser *is_mailbox, /* Mailbox room? */
int is_binary, /* Is encoding necessary? */
int is_unique, /* Del others of this type? */
unsigned int flags /* Internal save flags */
)
{
struct ctdlroom qrbuf;
char roomname[ROOMNAMELEN];
struct CtdlMessage *msg;
StrBuf *encoded_message = NULL;
if (is_mailbox != NULL) {
CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
}
else {
safestrncpy(roomname, req_room, sizeof(roomname));
}
syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
if (is_binary) {
encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
}
else {
encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
}
StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
if (is_binary) {
StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
}
else {
StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
}
if (is_binary) {
StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
}
else {
StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
}
syslog(LOG_DEBUG, "msgbase: allocating");
msg = malloc(sizeof(struct CtdlMessage));
memset(msg, 0, sizeof(struct CtdlMessage));
msg->cm_magic = CTDLMESSAGE_MAGIC;
msg->cm_anon_type = MES_NORMAL;
msg->cm_format_type = 4;
CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room));
CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
CM_SetField(msg, eHumanNode, CtdlGetConfigStr("c_humannode"), strlen(CtdlGetConfigStr("c_humannode")));
msg->cm_flags = flags;
CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
/* Create the requested room if we have to. */
if (CtdlGetRoom(&qrbuf, roomname) != 0) {
CtdlCreateRoom(roomname,
( (is_mailbox != NULL) ? 5 : 3 ),
"", 0, 1, 0, VIEW_BBS);
}
/* If the caller specified this object as unique, delete all
* other objects of this type that are currently in the room.
*/
if (is_unique) {
syslog(LOG_DEBUG, "msgbase: deleted %d other msgs of this type",
CtdlDeleteMessages(roomname, NULL, 0, content_type)
);
}
/* Now write the data */
CtdlSubmitMsg(msg, NULL, roomname, 0);
CM_Free(msg);
}
/*****************************************************************************/
/* MODULE INITIALIZATION STUFF */
/*****************************************************************************/
CTDL_MODULE_INIT(msgbase)
{
if (!threading) {
FillMsgKeyLookupTable();
}
/* return our Subversion id for the Log */
return "msgbase";
}
citadel/md5.h 0000644 0000000 0000000 00000001326 13223341037 012017 0 ustar root root
#ifndef MD5_H
#define MD5_H
#include "sysdep.h"
#include "typesize.h"
struct MD5Context {
cit_uint32_t buf[4];
cit_uint32_t bits[2];
cit_uint32_t in[16];
};
void MD5Init(struct MD5Context *context);
void MD5Update(struct MD5Context *context, unsigned char const *buf,
unsigned len);
void MD5Final(unsigned char digest[16], struct MD5Context *context);
void MD5Transform(cit_uint32_t buf[4], cit_uint32_t const in[16]);
char *make_apop_string(char *realpass, char *nonce, char *buffer, size_t n);
/*
* This is needed to make RSAREF happy on some MS-DOS compilers.
*/
#ifndef HAVE_OPENSSL
typedef struct MD5Context MD5_CTX;
#endif
#define MD5_DIGEST_LEN 16
#define MD5_HEXSTRING_SIZE 33
#endif /* !MD5_H */
citadel/parsedate.h 0000644 0000000 0000000 00000000041 13223341037 013273 0 ustar root root
time_t parsedate(const char *);
citadel/ical_dezonify.h 0000644 0000000 0000000 00000000126 13223341037 014146 0 ustar root root void ical_dezonify(icalcomponent *cal);
icaltimezone *get_default_icaltimezone(void);
citadel/sysdep_decls.h 0000644 0000000 0000000 00000003772 13223341037 014022 0 ustar root root
#ifndef SYSDEP_DECLS_H
#define SYSDEP_DECLS_H
#include
#include "sysdep.h"
#ifdef HAVE_PTHREAD_H
#include
#endif
#ifdef HAVE_DB_H
#include
#elif defined(HAVE_DB4_DB_H)
#include
#else
#error Neither nor was found by configure. Install db4-devel.
#endif
#if DB_VERSION_MAJOR < 4 || DB_VERSION_MINOR < 1
#error Citadel requires Berkeley DB v4.1 or newer. Please upgrade.
#endif
#include "server.h"
#include "database.h"
#if SIZEOF_SIZE_T == SIZEOF_INT
#define SIZE_T_FMT "%d"
#else
#define SIZE_T_FMT "%ld"
#endif
#if SIZEOF_LOFF_T == SIZEOF_LONG
#define LOFF_T_FMT "%ld"
#else
#define LOFF_T_FMT "%lld"
#endif
void cputbuf(const StrBuf *Buf);
#ifdef __GNUC__
void cprintf (const char *format, ...) __attribute__((__format__(__printf__,1,2)));
#else
void cprintf (const char *format, ...);
#endif
void init_sysdep (void);
int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len, char *errormessage);
int ctdl_uds_server(char *sockpath, int queue_len, char *errormessage);
void buffer_output(void);
void unbuffer_output(void);
void flush_output(void);
int client_write (const char *buf, int nbytes);
int client_read_to (char *buf, int bytes, int timeout);
int client_read (char *buf, int bytes);
int client_getln (char *buf, int maxbytes);
int CtdlClientGetLine(StrBuf *Target);
int client_read_blob(StrBuf *Target, int bytes, int timeout);
void client_set_inbound_buf(long N);
int client_read_random_blob(StrBuf *Target, int timeout);
void client_close(void);
void sysdep_master_cleanup (void);
void kill_session (int session_to_kill);
void start_daemon (int do_close_stdio);
void checkcrash(void);
int convert_login (char *NameToConvert);
void init_master_fdset(void);
void *worker_thread(void *);
extern volatile int exit_signal;
extern volatile int shutdown_and_halt;
extern volatile int running_as_daemon;
extern volatile int restart_server;
extern int verbosity;
extern int rescan[];
extern int SyslogFacility(char *name);
#endif /* SYSDEP_DECLS_H */
citadel/network/ 0000755 0000000 0000000 00000000000 13223341037 012650 5 ustar root root citadel/network/mail.aliases 0000644 0000000 0000000 00000000121 13223341037 015127 0 ustar root root bbs,room_aide
root,room_aide
Auto,room_aide
postmaster,room_aide
abuse,room_aide
citadel/housekeeping.c 0000644 0000000 0000000 00000012474 13223341037 014021 0 ustar root root /*
* This file contains miscellaneous housekeeping tasks.
*
* Copyright (c) 1987-2017 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include
#include
#include "ctdl_module.h"
#include "serv_extensions.h"
#include "room_ops.h"
#include "internet_addressing.h"
#include "config.h"
#include "journaling.h"
#include "citadel_ldap.h"
void check_sched_shutdown(void) {
if ((ScheduledShutdown == 1) && (ContextList == NULL)) {
syslog(LOG_NOTICE, "housekeeping: scheduled shutdown initiating");
server_shutting_down = 1;
}
}
/*
* Check (and fix) floor reference counts. This doesn't need to be done
* very often, since the counts should remain correct during normal operation.
*/
void check_ref_counts_backend(struct ctdlroom *qrbuf, void *data) {
int *new_refcounts;
new_refcounts = (int *) data;
++new_refcounts[(int)qrbuf->QRfloor];
}
void check_ref_counts(void) {
struct floor flbuf;
int a;
int new_refcounts[MAXFLOORS];
syslog(LOG_DEBUG, "housekeeping: checking floor reference counts");
for (a=0; a 0) {
flbuf.f_flags = flbuf.f_flags | QR_INUSE;
}
else {
flbuf.f_flags = flbuf.f_flags & ~QR_INUSE;
}
lputfloor(&flbuf, a);
syslog(LOG_DEBUG, "housekeeping: floor %d has %d rooms", a, new_refcounts[a]);
}
}
/*
* This is the housekeeping loop. Worker threads come through here after
* processing client requests but before jumping back into the pool. We
* only allow housekeeping to execute once per minute, and we only allow one
* instance to run at a time.
*/
static int housekeeping_in_progress = 0;
static time_t last_timer = 0L;
void do_housekeeping(void) {
int do_housekeeping_now = 0;
int do_perminute_housekeeping_now = 0;
time_t now;
/*
* We do it this way instead of wrapping the whole loop in an
* S_HOUSEKEEPING critical section because it eliminates the need to
* potentially have multiple concurrent mutexes in progress.
*/
begin_critical_section(S_HOUSEKEEPING);
if (housekeeping_in_progress == 0) {
do_housekeeping_now = 1;
housekeeping_in_progress = 1;
}
end_critical_section(S_HOUSEKEEPING);
now = time(NULL);
if (do_housekeeping_now == 0) {
if ( (now - last_timer) > (time_t)300 ) {
syslog(LOG_WARNING,
"housekeeping: WARNING: housekeeping loop has not run for %ld minutes. Is something stuck?",
((now - last_timer) / 60)
);
}
return;
}
/*
* Ok, at this point we've made the decision to run the housekeeping
* loop. Everything below this point is real work.
*/
if ( (now - last_timer) > (time_t)60 ) {
do_perminute_housekeeping_now = 1;
last_timer = time(NULL);
}
/* First, do the "as often as needed" stuff... */
JournalRunQueue();
PerformSessionHooks(EVT_HOUSE);
/* Then, do the "once per minute" stuff... */
if (do_perminute_housekeeping_now) {
cdb_check_handles();
PerformSessionHooks(EVT_TIMER); // Run all registered TIMER hooks
#ifdef HAVE_LDAP // LDAP sync isn't in a module so we can put it here
static time_t last_ldap_sync = 0L;
if ( (now - last_ldap_sync) > (time_t)CtdlGetConfigLong("c_ldap_sync_freq") ) {
CtdlSynchronizeUsersFromLDAP();
last_ldap_sync = time(NULL);
}
#endif
}
/*
* All done.
*/
begin_critical_section(S_HOUSEKEEPING);
housekeeping_in_progress = 0;
end_critical_section(S_HOUSEKEEPING);
}
void CtdlDisableHouseKeeping(void)
{
int ActiveBackgroundJobs;
int do_housekeeping_now = 0;
struct CitContext *nptr;
int nContexts, i;
retry_block_housekeeping:
syslog(LOG_INFO, "housekeeping: trying to disable services");
begin_critical_section(S_HOUSEKEEPING);
if (housekeeping_in_progress == 0) {
do_housekeeping_now = 1;
housekeeping_in_progress = 1;
}
end_critical_section(S_HOUSEKEEPING);
if (do_housekeeping_now == 0) {
usleep(1000000);
goto retry_block_housekeeping;
}
syslog(LOG_INFO, "housekeeping: checking for running server jobs");
retry_wait_for_contexts:
/* So that we don't keep the context list locked for a long time
* we create a copy of it first
*/
ActiveBackgroundJobs = 0;
nptr = CtdlGetContextArray(&nContexts) ;
if (nptr)
{
for (i=0; i