/*
* DREADERD/READER.C - reader task
*
* Reader task, main control loop(s).
*
* (c)Copyright 1998, Matthew Dillon, All Rights Reserved. Refer to
* the COPYRIGHT file in the base directory of this distribution
* for specific rights granted.
*/
#include "defs.h"
Prototype void ReaderTask(int fd, const char *id);
Prototype void NNCommand(Connection *conn);
Prototype void NNCommand2(Connection *conn);
Prototype void NNBadCommandUse(Connection *conn);
Prototype void NNUnknownCommand(Connection *conn);
Prototype void NNTerminate(Connection *conn);
Prototype void NNAuthDone(Connection *conn);
Prototype void NNWriteHello(Connection *conn);
Prototype void NNWaitThread(Connection *conn);
Prototype void NNTPHelp(Connection *conn, char **pptr);
Prototype Connection *InitConnection(ForkDesc *desc, DnsRes *dres);
Prototype void DeleteConnection(Connection *conn);
Prototype void StatusUpdate(Connection *conn, const char *ctl, ...);
Prototype void LogCmd(Connection *conn, char dir, char *msg);
Prototype void GroupStats(Connection *conn);
Prototype KPDB *KDBActive;
void HandleReaderMsg(ForkDesc *desc);
int makeReaderSlot(void);
void freeReaderSlot(int slot);
void sigHup(int SigNo);
void sigSegVReader(int sigNo);
KPDB *KDBActive;
int TFd = -1; /* thread's return fd to the parent */
int NumReaders;
int *ReaderSlotAry;
int TerminatePending = 0;
int CanTerminate = 0;
time_t TerminateTime = 0;
void LogCmd(Connection *conn, char dir, char *msg)
{
char *ptr, tmp = '\0';
int doLog;
doLog = DOpts.ReaderDetailLog;
if (conn->co_Auth.dr_ReaderDef &&
conn->co_Auth.dr_ReaderDef->rd_LogCmd >= 0)
doLog = conn->co_Auth.dr_ReaderDef->rd_LogCmd;
if (doLog == 0)
return;
if (conn->co_Desc->d_Slot < 0) {
/*
* Oh, lame. Diablo generally doesn't strip \r from inputted text
* lines, and seeing caret-M in syslog drives me nuts. I'm not
* sure which is more lame, the problem or the hack/workaround
*
* JG200012210208
*/
if ((ptr = strpbrk(msg, "\r\n"))) {
tmp = *ptr;
*ptr = '\0';
}
logit(LOG_DEBUG, "%s [%d] %c %s", conn->co_Desc->d_Id, getpid(), dir, msg);
if (ptr) {
*ptr = tmp;
}
} else {
logit(LOG_DEBUG, "%s%s%s%s%s [%d/%d] %c %s",
*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
*conn->co_Auth.dr_AuthUser ? "/" : "",
*conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",
*conn->co_Auth.dr_IdentUser ? "@" : "",
conn->co_Auth.dr_Host, getpid(), conn->co_Desc->d_Slot,
dir, msg);
}
}
void
ReaderTask(int fd, const char *id)
{
time_t dtime = 0;
time_t ltime = 0;
time_t itime = 0;
time_t ftime = 0;
time_t atime = 0;
int counter = 0;
int forceallcheck = 0;
int check_disconn_counter = 0;
TFd = fd;
/*
* [re]open RTStatus
*/
RTStatusOpen(RTStatus, ThisReaderFork * DOpts.ReaderThreads + 1, DOpts.ReaderThreads);
/*
* Since we setuid(), we won't core. This is for debugging
*/
if (CoreDebugOpt || (DOpts.ReaderCrashHandler != NULL &&
strcasecmp(DOpts.ReaderCrashHandler, "none") != 0)) {
signal(SIGSEGV, sigSegVReader);
signal(SIGBUS, sigSegVReader);
signal(SIGFPE, sigSegVReader);
signal(SIGILL, sigSegVReader);
}
signal(SIGHUP, sigHup);
/*
* Setup thread for passed pipe
*/
ResetThreads();
AddThread("reader", fd, -1, THREAD_READER, -1, 0);
FD_SET(fd, &RFds);
/*
* Open KPDB database for active file
*/
if ((KDBActive = KPDBOpen(PatDbExpand(ReaderDActivePat), O_RDWR)) == NULL) {
logit(LOG_CRIT, "Unable to open %s", PatDbExpand(ReaderDActivePat));
sleep(60);
exit(1);
}
LoadExpireCtl(1);
/*
* Only startup connections to backend spools for reader threads
*/
if (!FeedOnlyServer)
CheckServerConfig(time(NULL), 1);
/*
* Selection core
*/
while (!TerminatePending || NReadServAct || NumReaders) {
/*
* select core
*/
struct timeval tv;
fd_set rfds = RFds;
fd_set wfds = WFds;
fd_set read_only_to_find_eof_fds;
int i, sel_r;
if (TerminatePending) {
if (TerminateTime == 0)
TerminateTime = time(NULL) + 2;
if (TerminateTime < time(NULL) || !NumReaders)
CanTerminate = 1;
}
/*
* Get next scheduled timeout, no more then 2 seconds
* (x 10 counter counts = 20 seconds max for {d,i,f}time
* check)
*
* If we are terminating, then speed up the select to clear
* out the connections.
*
*/
if (TerminatePending)
NextTimeout(&tv, 50);
else
NextTimeout(&tv, 2 * 1000);
stprintf("%s readers=%02d spoolsrv=%d/%d postsrv=%d/%d",
id,
NumReaders,
NReadServAct, NReadServers,
NWriteServAct, NWriteServers
);
/* Check for disconnected clients every 50 times through the loop */
FD_ZERO(&read_only_to_find_eof_fds);
if (++check_disconn_counter == 50) {
for (i = 0; i < MaxFds; ++i) {
if (FD_ISSET(i, &wfds) && (!(FD_ISSET(i, &rfds)))) {
FD_SET(i, &rfds);
FD_SET(i, &read_only_to_find_eof_fds);
}
}
check_disconn_counter = 0;
}
#if USE_AIO
AIOUnblockSignal();
#endif
sel_r = select(MaxFds, &rfds, &wfds, NULL, &tv);
#if USE_AIO
AIOBlockSignal();
#endif
gettimeofday(&CurTime, NULL);
if(sel_r < 0 && errno != EINTR)
logit(LOG_CRIT,
"select error: %s (rfds=0x%x, wfds=0x%x)",
strerror(errno),
rfds,
wfds);
/*
* select is critical, don't make unnecessary system calls. Only
* test the time every 10 selects (20 seconds worst case), and
* only check for a new server configuration file every 60 seconds
* after the initial load. This may rearrange THREAD_SPOOL and
* THREAD_POST threads.
*
* We do not startup spool/post servers for feed-only forks
*
* However, flush overview cache even for feed-only forks.
*/
if (FeedOnlyServer <= 0) {
if (++counter == 10) {
time_t t = CurTime.tv_sec;
if (ltime) {
dtime += t - ltime;
itime += t - ltime;
ftime += t - ltime;
atime += t - ltime;
}
/*
* Check for server config change once a minute
*/
if (dtime < -5 || dtime >= 5) {
if (!TerminatePending)
CheckServerConfig(t, ServersTerminated);
dtime = 0;
}
/*
* Flush overview every 30 seconds to allow dexpireover to work
*/
if (ftime < -5 || ftime >= 30) {
FlushOverCache();
LoadExpireCtl(0);
ftime = 0;
}
/*
* Poll all active descriptors once every 5 minutes. This
* will work around a linux embrionic close bug that
* doesn't wakeup select(), and is used to idle-timeout
* connections. XXX
*/
if (itime < -5 || itime >= 300) {
rfds = RFds;
itime = 0;
}
/*
* Force a check all of FD's every 30 seconds to handle
* idle and session timeouts
*/
if (atime < -5 || atime >= 30) {
forceallcheck = 1;
atime = 0;
}
ltime = t;
counter = 0;
}
} else {
/*
* For a feed-only server, we only flush the overview FD
* cache every 5 minutes, and with a greater granularity.
* It should cycle much faster than that normally, and this
* is to prevent idle feed-only forks from keeping locks.
*
*/
if (++counter == 10) {
time_t t = CurTime.tv_sec;
if (ltime) {
ftime += t - ltime;
}
if (ftime < -5 || ftime >= 300) {
FlushOverCache();
ftime = 0;
}
ltime = t;
counter = 0;
}
}
for (i = 0; i < MaxFds; ++i) {
if (FD_ISSET(i, &rfds) && FD_ISSET(i, &read_only_to_find_eof_fds)) {
char junk_byte;
int ret_val;
/*
* This FD is not marked for reading, but select() claims
* it has something to say. We don't actually want to read
* from it, but we do want to close it if the associated
* connection is dead.
*/
FD_CLR(i, &rfds);
/* Use recv() with MSG_PEEK to see if it's closed.
* We shouldn't block because we're O_NONBLOCK.
*/
ret_val = recv(i, &junk_byte, 1, MSG_PEEK);
/* If ret_val is zero, this means the socket is closed.
* Blast it. Otherwise, ignore it.
*/
if(ret_val == 0) {
ForkDesc *desc;
if((desc = FindThread(i, -1)) != NULL) {
Connection *conn = desc->d_Data;
if(conn) {
NNTerminate(conn);
DeleteConnection(conn);
}
DelThread(desc);
}
}
}
}
for (i = 0; i < MaxFds; ++i) {
if (forceallcheck || TerminatePending ||
FD_ISSET(i, &rfds) || FD_ISSET(i, &wfds)) {
ForkDesc *desc;
if ((desc = FindThread(i, -1)) != NULL) {
Connection *conn = desc->d_Data;
if (conn) {
/*
* handle output I/O (optimization)
*/
MBFlush(conn, &conn->co_TMBuf);
conn->co_FCounter = 0;
}
/*
* Function dispatch
*/
switch(desc->d_Type) {
case THREAD_READER:
if (FD_ISSET(i, &rfds) || FD_ISSET(i, &wfds))
HandleReaderMsg(desc);
break;
case THREAD_NNTP: /* client */
conn->co_Func(conn);
if (conn->co_Auth.dr_ResultFlags & DR_REQUIRE_DNS) {
/* Go back to parent for DNS check */
conn->co_Auth.dr_Code = 0;
conn->co_TMBuf.mh_WEof = 1;
}
break;
case THREAD_SPOOL: /* spool server */
case THREAD_POST: /* posting server */
conn->co_Func(conn);
LogServerInfo(conn, TFd);
break;
default:
/* panic */
break;
}
/*
* do not call MBFlush after the function because the
* function may be waiting for write data to drain and
* we don't want to cause write data to drain here and
* then not get a select wakeup later.
*
* check for connection termination
*/
if (conn) {
int idleTimeout = 0;
if (conn->co_Auth.dr_ReaderDef) {
if (conn->co_Auth.dr_ReaderDef->rd_IdleTimeout &&
conn->co_LastActiveTime +
conn->co_Auth.dr_ReaderDef->rd_IdleTimeout <=
CurTime.tv_sec) {
logit(LOG_INFO, "timeout idle %s",
conn->co_Auth.dr_Host);
MBLogPrintf(conn,
&conn->co_TMBuf,
"400 %s: Idle timeout.\r\n",
conn->co_Auth.dr_VServerDef->vs_HostName
);
idleTimeout = 1;
NNTerminate(conn);
}
if (conn->co_Auth.dr_ReaderDef->rd_SessionTimeout &&
conn->co_SessionStartTime +
conn->co_Auth.dr_ReaderDef->rd_SessionTimeout <=
CurTime.tv_sec) {
logit(LOG_INFO, "timeout session %s",
conn->co_Auth.dr_Host);
MBLogPrintf(conn,
&conn->co_TMBuf,
"400 %s: Session timeout.\r\n",
conn->co_Auth.dr_VServerDef->vs_HostName
);
idleTimeout = 1;
NNTerminate(conn);
}
}
if ((!conn->co_Auth.dr_Code &&
desc->d_Type == THREAD_NNTP) ||
idleTimeout ||
(conn->co_RMBuf.mh_REof &&
conn->co_TMBuf.mh_WEof &&
conn->co_TMBuf.mh_MBuf == NULL) ||
(TerminatePending &&
!(conn->co_Flags & COF_MAYNOTCLOSE))
) {
DeleteConnection(conn);
DelThread(desc);
}
}
}
}
}
forceallcheck = 0;
(void)ScanTimers(1, 0);
if (CanTerminate)
break;
}
}
void
HandleReaderMsg(ForkDesc *desc)
{
int r;
int recv_fd;
DnsRes dres;
if ((r = RecvMsg(desc->d_Fd, &recv_fd, &dres)) == sizeof(DnsRes)) {
if (recv_fd >= MAXFDS) {
logit(LOG_WARNING, "fd too large %d/%d, increase MAXFDS for select. Closing fd", recv_fd, MAXFDS);
/*
* Tell the main server that we are done with the connection
*/
fcntl(TFd, F_SETFL, 0);
SendMsg(TFd, recv_fd, &dres);
fcntl(TFd, F_SETFL, O_NONBLOCK);
} else if (recv_fd >= 0) {
ForkDesc *ndesc;
Connection *conn;
char vsbuf[11];
char hsbuf[31];
if (ReadAccessCache() == 1) {
ScanThreads(THREAD_NNTP, UpdateAuthDetails);
ScanThreads(THREAD_SPOOL, UpdateAuthDetails);
ScanThreads(THREAD_READER, UpdateAuthDetails);
ScanThreads(THREAD_SPOOL, UpdateAuthDetails);
ScanThreads(THREAD_FEEDER, UpdateAuthDetails);
ClearOldAccessMap();
}
SetAuthDetails(&dres, dres.dr_ReaderName);
ndesc = AddThread("client", recv_fd, -1, THREAD_NNTP, makeReaderSlot(), 0);
++NumReaders;
if (DebugOpt)
printf("add thread fd=%d\n", recv_fd);
FD_SET(ndesc->d_Fd, &WFds); /* will cause immediate effect */
conn = InitConnection(ndesc, &dres);
if (conn->co_Auth.dr_Flags & DF_FEED)
conn->co_Flags |= COF_SERVER;
snprintf(vsbuf, sizeof(vsbuf), "%s", conn->co_Auth.dr_VServerDef->vs_Name);
snprintf(hsbuf, sizeof(hsbuf), "%s%s%s%s%s",
*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
*conn->co_Auth.dr_AuthUser ? "/" : "",
*conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",
*conn->co_Auth.dr_IdentUser ? "@" : "",
conn->co_Auth.dr_Host);
RTStatusBase(conn->co_Desc->d_Slot, "ACTV %-10s %-30s", vsbuf, hsbuf);
StatusUpdate(conn, "(startup)");
if (conn->co_Auth.dr_ResultFlags & DR_REQUIRE_DNS)
NNAuthDone(conn);
else
NNWriteHello(conn);
} else {
if (DebugOpt)
printf("recvmsg(): EOF1\n");
DelThread(desc);
TerminatePending = 1;
}
}
/*
* If recv error, check errno. If temporary error,
* leave r negative (select loop). Set r = 0 to
* terminate.
*/
if (r != sizeof(DnsRes)) {
if (DebugOpt)
printf("recvmsg(): Bad size read from RecvMsg\n");
}
if (r < 0) {
if (errno != EINTR &&
errno != EWOULDBLOCK &&
errno != EAGAIN
) {
r = 0;
}
}
/*
* EOF (or error)
*/
if (r == 0) {
if (DebugOpt)
printf("recvmsg(): EOF/error from parent %s\n", strerror(errno));
DelThread(desc);
TerminatePending = 1;
}
}
Connection *
InitConnection(ForkDesc *desc, DnsRes *dres)
{
MemPool *pool = NULL;
Connection *conn = zalloc(&pool, sizeof(Connection));
desc->d_Data = conn;
if (dres)
conn->co_Auth = *dres;
conn->co_Desc = desc;
conn->co_MemPool = pool;
gettimeofday(&conn->co_RateTv, NULL);
conn->co_LastActiveTime = conn->co_SessionStartTime = conn->co_RateTv.tv_sec;
MBInit(&conn->co_TMBuf, desc->d_Fd, &conn->co_MemPool, &conn->co_BufPool);
MBInit(&conn->co_RMBuf, desc->d_Fd, &conn->co_MemPool, &conn->co_BufPool);
MBInit(&conn->co_ArtBuf, -1, &conn->co_MemPool, &conn->co_BufPool);
if (conn->co_Desc->d_Type == THREAD_NNTP) {
conn->co_ClientTotalByteCount = 0.0;
conn->co_ClientTotalArticleCount = 0;
conn->co_ClientGroupByteCount = 0.0;
conn->co_ClientGroupArticleCount = 0;
conn->co_ClientGroupCount = conn->co_ClientPostCount = 0;
}
return(conn);
}
void
DeleteConnection(Connection *conn)
{
MemPool *mpool = conn->co_MemPool;
if (conn->co_Desc->d_Type == THREAD_SPOOL) {
conn->co_LastServerLog = 1;
LogServerInfo(conn, TFd);
} else if (conn->co_Desc->d_Type == THREAD_NNTP) {
char statbuf[1024];
char vsbuf[11];
char hsbuf[31];
snprintf(vsbuf, sizeof(vsbuf), "%s", conn->co_Auth.dr_VServerDef->vs_Name);
snprintf(hsbuf, sizeof(hsbuf), "%s%s%s%s%s",
*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
*conn->co_Auth.dr_AuthUser ? "/" : "",
*conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",
*conn->co_Auth.dr_IdentUser ? "@" : "",
conn->co_Auth.dr_Host);
RTStatusBase(conn->co_Desc->d_Slot, "CLSD %-10s %-30s", vsbuf, hsbuf);
GroupStats(conn);
snprintf(statbuf, sizeof(statbuf), "exit articles %lu groups %lu posts %lu bytes %.0f", conn->co_ClientTotalArticleCount, conn->co_ClientGroupCount, conn->co_ClientPostCount, conn->co_ClientTotalByteCount);
LogCmd(conn, '$', statbuf);
StatusUpdate(conn, "(closed)");
freeReaderSlot(conn->co_Desc->d_Slot);
--NumReaders;
/*
* Inform the main server that we are done with the descriptor
* by writing the DnsRes structure back to it, so the main server
* can track who from where is connecting to what and when that
* connection terminates.
*/
conn->co_Auth.dr_ByteCount = conn->co_TMBuf.mh_TotalBytes;
SendMsg(TFd, conn->co_Desc->d_Fd, &conn->co_Auth);
}
FreeControl(conn);
freePool(&conn->co_BufPool);
freePool(&mpool); /* includes Connection structure itself */
}
void
NNTerminate(Connection *conn)
{
conn->co_Func = NNTerminate;
conn->co_State = "term";
conn->co_RMBuf.mh_REof = 1;
conn->co_TMBuf.mh_WEof = 1;
/*
* conn->co_SReq is only non-NULL for a client
* connection. Server use of the field will have
* already NULL'd it out in NNServerTerminate().
*
* The problem we have is that the server may be actively
* using the client connection's MBuf's and be in some
* intermediate state. Therefore, we must change the SReq
* to point to a sink-NULL client XXX.
*/
if (conn->co_SReq) {
if (conn->co_SReq->sr_CConn != conn)
fatal("NNTerminate(): server conn had non_NULL co_SReq");
/*
* Disconnect the co_SReq from the client. This will cause
* the server operation-in-progress to abort, if possible.
*/
conn->co_SReq->sr_CConn = NULL;
conn->co_SReq = NULL;
}
/*
* If this connection was performing a list operation and had the
* active cache locked, unlock it. If it was building the cache,
* blast the cache because it's half-built.
*/
if (conn->co_ListCacheMode == ACMODE_READ)
ActiveCacheReadUnlock();
if (conn->co_ListCacheMode == ACMODE_WRITE) {
ActiveCacheFreeMain();
ActiveCacheWriteUnlock();
}
MBFlush(conn, &conn->co_TMBuf);
FD_SET(conn->co_Desc->d_Fd, &WFds);
}
void
NNAuthDone(Connection *conn)
{
conn->co_Auth.dr_ResultFlags = 0;
if (conn->co_Auth.dr_Code == 1 && (conn->co_Auth.dr_Flags & DF_AUTHREQUIRED) == 0) {
conn->co_Auth.dr_Flags &= ~DF_AUTHREQUIRED;
MBLogPrintf(conn, &conn->co_TMBuf, "281 Ok\r\n");
logit(LOG_DEBUG,
"Access granted to %s via %s", conn->co_Auth.dr_Host,
conn->co_Auth.dr_ReaderName);
NNCommand(conn);
} else {
MBLogPrintf(conn, &conn->co_TMBuf, "502 Authentication error\r\n");
NNTerminate(conn);
}
}
char *
WelcomeString(char *base, Vserver *vs, const char *servertype)
{
static char r[512];
int l = 0;
r[0] = 0;
while (l < sizeof(r) && *base) {
if (*base == '%') {
switch (*(++base)) {
case 'a':
l += snprintf(&r[l], 512 - l, "%s", vs->vs_NewsAdm);
break;
case 'c':
l += snprintf(&r[l], 512 - l, "%s", vs->vs_ClusterName);
break;
case 'h':
l += snprintf(&r[l], 512 - l, "%s", vs->vs_HostName);
break;
case 'o':
l += snprintf(&r[l], 512 - l, "%s", vs->vs_Org);
break;
case 't':
l += snprintf(&r[l], 512 - l, "%s", servertype);
break;
case 'v':
l += snprintf(&r[l], 512 - l, "Diablo %s-%s", VERS, SUBREV);
break;
}
base++;
} else {
r[l++] = *base++;
}
}
r[l] = '\0';
return(r);
}
void
NNWriteHello(Connection *conn)
{
const char *postingOk = "(no posting)";
const char *noReading = "";
const char *serverType = "NNRP";
if (conn->co_Auth.dr_Flags & DF_POST)
postingOk = "(posting ok)";
if (conn->co_Flags & COF_SERVER) {
serverType = "NNTP-FEED";
} else if ((conn->co_Auth.dr_Flags & DF_READ) == 0) {
noReading = "(no reading)";
}
if ((conn->co_Auth.dr_Flags & (DF_FEED|DF_READ|DF_POST|DF_AUTHREQUIRED)) == 0) {
MBLogPrintf(conn,
&conn->co_TMBuf,
"502 %s: Access denied to your node%s%s.\r\n",
conn->co_Auth.dr_VServerDef->vs_HostName,
*conn->co_Auth.dr_VServerDef->vs_NewsAdm ? " - " : "",
conn->co_Auth.dr_VServerDef->vs_NewsAdm
);
NNTerminate(conn);
return;
}
if (*conn->co_Auth.dr_VServerDef->vs_Welcome)
MBLogPrintf(conn,
&conn->co_TMBuf,
"%d %s %s%s\r\n",
(conn->co_Auth.dr_Flags & DF_POST) ? 200 : 201,
WelcomeString(conn->co_Auth.dr_VServerDef->vs_Welcome,
conn->co_Auth.dr_VServerDef,
serverType),
postingOk,
noReading
);
else
MBLogPrintf(conn,
&conn->co_TMBuf,
"%d %s %s Service Ready%s%s %s%s.\r\n",
(conn->co_Auth.dr_Flags & DF_POST) ? 200 : 201,
conn->co_Auth.dr_VServerDef->vs_HostName,
serverType,
*conn->co_Auth.dr_VServerDef->vs_NewsAdm ? " - " : "",
conn->co_Auth.dr_VServerDef->vs_NewsAdm,
postingOk,
noReading
);
NNCommand(conn);
}
/*
* NNCommand() - general command entry. Attempt to flush output data
* and then go onto the appropriate command set.
*/
#define CMDF_AUTH 0x00000001
#define CMDF_SERVER 0x00000002
#define CMDF_READER 0x00000004
#define CMDF_NOTFEEDONLY 0x00000008
typedef struct Command {
const char *cmd_Name;
int cmd_Flags;
void (*cmd_Func)(Connection *conn, char **pptr);
const char *cmd_Help;
int cmd_DRBC_Type;
} Command;
Command Cmds[] = {
{
"article",
CMDF_AUTH|CMDF_SERVER|CMDF_READER|CMDF_NOTFEEDONLY,
NNTPArticle,
"[MessageID|Number]",
DRBC_ARTICLE
},
{
"body",
CMDF_AUTH|CMDF_SERVER|CMDF_READER|CMDF_NOTFEEDONLY,
NNTPBody,
"[MessageID|Number]",
DRBC_BODY
},
{
"date",
0 |CMDF_SERVER|CMDF_READER,
NNTPDate,
"",
DRBC_NONE
},
{
"head",
CMDF_AUTH|CMDF_SERVER|CMDF_READER|CMDF_NOTFEEDONLY,
NNTPHead,
"[MessageID|Number]",
DRBC_HEAD
},
{
"help",
0 |CMDF_SERVER|CMDF_READER,
NNTPHelp,
"",
DRBC_NONE
},
{
"ihave",
CMDF_AUTH|CMDF_SERVER|CMDF_READER,
NNTPIHave,
"",
DRBC_NONE
},
{
"takethis",
CMDF_AUTH|CMDF_SERVER|0 ,
NNTPTakeThis,
"MessageID",
DRBC_NONE
},
{
"check",
CMDF_AUTH|CMDF_SERVER|0 ,
NNTPCheck,
"MessageID",
DRBC_NONE
},
{
"mode",
CMDF_AUTH|CMDF_READER,
NNTPMode,
"reader|stream",
DRBC_NONE
},
{
"mode",
CMDF_AUTH|CMDF_SERVER,
NNTPMode ,
"reader|stream|headfeed",
DRBC_NONE
},
{
"slave",
CMDF_AUTH|CMDF_SERVER|CMDF_READER,
NNTPSlave,
"",
DRBC_NONE
},
{
"quit",
0 |CMDF_SERVER|CMDF_READER,
NNTPQuit,
"",
DRBC_NONE
},
{
"group",
CMDF_AUTH|0 |CMDF_READER,
NNTPGroup ,
"newsgroup",
DRBC_NONE
},
{
"last",
CMDF_AUTH|0 |CMDF_READER,
NNTPLast,
"",
DRBC_NONE
},
{
"next",
CMDF_AUTH|0 |CMDF_READER,
NNTPNext,
"",
DRBC_NONE
},
{
"list",
CMDF_AUTH|0 |CMDF_READER,
NNTPList,
"[active|active.times|newsgroups|extensions|distributions|distrib.pats|moderators|overview.fmt|subscriptions]",
DRBC_LIST
},
{
"listgroup",
CMDF_AUTH|0 |CMDF_READER,
NNTPListGroup,
"newsgroup",
DRBC_LIST
},
{
"newgroups",
CMDF_AUTH|0 |CMDF_READER,
NNTPNewgroups,
"yymmdd hhmmss [\"GMT\"] [<distributions>]",
DRBC_LIST
},
{
"newnews",
CMDF_AUTH|0 |CMDF_READER,
NNTPNewNews,
"newsgroups yymmdd hhmmss [\"GMT\"] [<distribution>]",
DRBC_LIST
},
{
"post",
CMDF_AUTH|0 |CMDF_READER,
NNTPPost,
"",
DRBC_NONE
},
{
"stat",
CMDF_AUTH|0 |CMDF_READER,
NNTPStat,
"[MessageID|Number]",
DRBC_NONE
},
{
"xgtitle",
CMDF_AUTH|0 |CMDF_READER,
NNTPXGTitle,
"[group_pattern]",
DRBC_LIST
},
{
"xhdr",
CMDF_AUTH|0 |CMDF_READER,
NNTPXHdr,
"header [range|MessageID]",
DRBC_XHDR
},
{
"hdr",
CMDF_AUTH|0 |CMDF_READER,
NNTPXHdr,
"header [range|MessageID]",
DRBC_XHDR
},
{
"over",
CMDF_AUTH|0 |CMDF_READER,
NNTPXOver,
"[range]",
DRBC_XOVER
},
{
"xover",
CMDF_AUTH|0 |CMDF_READER,
NNTPXOver,
"[range]",
DRBC_XOVER
},
{
"xpat",
CMDF_AUTH|0 |CMDF_READER,
NNTPXPat,
"header range|MessageID pat",
DRBC_XHDR
},
{
"xpath",
CMDF_AUTH|0 |CMDF_READER,
NNTPXPath,
"MessageID",
DRBC_XHDR
},
{
"authinfo",
0 |CMDF_SERVER|CMDF_READER,
NNTPAuthInfo,
"user Name|pass Password",
DRBC_NONE
}
};
void
NNCommand(Connection *conn)
{
/*
* Check for changes to the access cache before every command
*/
if (ReadAccessCache() == 1) {
ScanThreads(THREAD_NNTP, UpdateAuthDetails);
ScanThreads(THREAD_SPOOL, UpdateAuthDetails);
ScanThreads(THREAD_READER, UpdateAuthDetails);
ScanThreads(THREAD_SPOOL, UpdateAuthDetails);
ScanThreads(THREAD_FEEDER, UpdateAuthDetails);
ClearOldAccessMap();
}
MBFlush(conn, &conn->co_TMBuf);
if (conn->co_Auth.dr_ReaderDef->rd_ByteLimit &&
conn->co_Auth.dr_ReaderDef->rd_ByteLimit <
conn->co_ClientTotalByteCount) {
logit(LOG_INFO, "bytelimit session %s byte=%d/%d",
conn->co_Auth.dr_Host,
conn->co_ClientTotalByteCount,
conn->co_Auth.dr_ReaderDef->rd_ByteLimit);
MBLogPrintf(conn,
&conn->co_TMBuf,
"400 %s: Session byte limit reached.\r\n",
conn->co_Auth.dr_VServerDef->vs_HostName
);
NNTerminate(conn);
}
NNCommand2(conn);
conn->co_LastActiveTime = CurTime.tv_sec;
}
void
NNCommand2(Connection *conn)
{
char *ptr;
char *cmd;
char *buf;
Command *scan;
int len;
conn->co_Func = NNCommand2;
conn->co_State = "waitcmd";
/*
* we have to be careful in regards to recursive operation, nor do
* we want one descriptor to hog the process. We can't set RFds
* because the next command may already be entirely loaded into an
* MBuf so setting RFds may not unblock us. Instead, we set WFds
* which basically forces a wakeup at some point in the future.
*/
if (conn->co_FCounter) {
FD_SET(conn->co_Desc->d_Fd, &WFds);
return;
}
++conn->co_FCounter;
/*
* get command
*/
if ((len = MBReadLine(&conn->co_RMBuf, &buf)) == 0) {
StatusUpdate(conn, "(idle)");
return;
}
conn->co_ByteCountType = DRBC_NONE;
/*
* check EOF
*/
if (len < 0) {
NNTerminate(conn);
return;
}
/*
* strip CR LF
*/
ptr = buf;
if (len > 1 && ptr[len-2] == '\r')
ptr[len-2] = 0;
if (DebugOpt)
printf("command: %s\n", ptr);
if (strncasecmp(ptr, "authinfo pass ", 14)) {
LogCmd(conn, '<', ptr);
} else {
LogCmd(conn, '<', "authinfo pass **unlogged**");
}
/*
* extract command (note: StatusUpdate() will limit the line length)
*/
StatusUpdate(conn, "%s", ptr);
if ((cmd = parseword(&ptr, " \t")) == NULL) {
NNCommand(conn);
return;
}
{
int i;
for (i = 0; cmd[i]; ++i)
cmd[i] = tolower((int)(unsigned char)cmd[i]);
}
/*
* Locate and execute command
*/
for (scan = &Cmds[0]; scan < &Cmds[arysize(Cmds)]; ++scan) {
if (strcmp(cmd, scan->cmd_Name) == 0) {
if (conn->co_Flags & COF_SERVER) {
if (scan->cmd_Flags & CMDF_SERVER) {
if ((conn->co_Auth.dr_Flags & DF_FEEDONLY) == 0)
break;
if ((scan->cmd_Flags & CMDF_NOTFEEDONLY) == 0)
break;
}
} else {
if (scan->cmd_Flags & CMDF_READER)
break;
}
}
}
if (scan < &Cmds[arysize(Cmds)]) {
if ((scan->cmd_Flags & CMDF_AUTH) &&
(conn->co_Auth.dr_Flags & DF_AUTHREQUIRED)
) {
MBLogPrintf(conn, &conn->co_TMBuf, "480 Authentication required for command\r\n");
NNCommand(conn);
} else {
conn->co_ByteCountType = scan->cmd_DRBC_Type;
scan->cmd_Func(conn, &ptr);
}
} else {
NNUnknownCommand(conn);
}
}
void
NNBadCommandUse(Connection *conn)
{
MBLogPrintf(conn, &conn->co_TMBuf, "501 Bad command use\r\n");
NNCommand(conn);
}
void
NNUnknownCommand(Connection *conn)
{
MBLogPrintf(conn, &conn->co_TMBuf, "500 What?\r\n");
NNCommand(conn);
}
/*
* NNWaitThread() - wait for some other thread to finish
* our request. Howeve, we allow writes
* to occur on our descriptor while we
* are waiting in case the other thread
* is shoving stuff out our connection.
*/
void
NNWaitThread(Connection *conn)
{
conn->co_Func = NNWaitThread;
conn->co_State = "waitrt";
FD_CLR(conn->co_Desc->d_Fd, &RFds);
/*FD_CLR(conn->co_Desc->d_Fd, &WFds);*/
}
void
NNTPHelp(Connection *conn, char **pptr)
{
Command *scan;
MBLogPrintf(conn, &conn->co_TMBuf, "100 Legal commands\r\n");
for (scan = &Cmds[0]; scan < &Cmds[arysize(Cmds)]; ++scan) {
int ok = 0;
if (conn->co_Flags & COF_SERVER) {
if (scan->cmd_Flags & CMDF_SERVER) {
ok = 1;
if ((scan->cmd_Flags & CMDF_NOTFEEDONLY) &&
(conn->co_Auth.dr_Flags & DF_FEEDONLY)
) {
ok = 0;
}
}
} else {
if (scan->cmd_Flags & CMDF_READER)
ok = 1;
if (!conn->co_Auth.dr_ReaderDef->rd_AllowNewnews &&
(strcmp(scan->cmd_Name, "newnews") == 0))
ok = 0;
}
if (ok)
MBPrintf(&conn->co_TMBuf, " %s %s\r\n", scan->cmd_Name, scan->cmd_Help);
}
MBPrintf(&conn->co_TMBuf, ".\r\n");
NNCommand(conn);
}
void
StatusUpdate(Connection *conn, const char *ctl, ...)
{
char buf[256];
va_list va;
snprintf(buf, sizeof(buf), "%c%c%c %-30s",
((conn->co_Auth.dr_Flags & DF_FEED) ? 'f' : '-'),
((conn->co_Auth.dr_Flags & DF_READ) ? 'r' : '-'),
((conn->co_Auth.dr_Flags & DF_POST) ? 'p' : '-'),
(conn->co_Auth.dr_Flags & DF_GROUPLOG) ?
(conn->co_GroupName ? conn->co_GroupName : "(none)") :
"(none)"
);
buf[34] = ' ';
buf[35] = '\0';
va_start(va, ctl);
vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ctl, va);
va_end(va);
RTStatusUpdate(conn->co_Desc->d_Slot, "%s", buf);
}
int
makeReaderSlot(void)
{
static int SlotNo;
int i;
if (ReaderSlotAry == NULL) {
ReaderSlotAry = zalloc(&SysMemPool, sizeof(*ReaderSlotAry) * DOpts.ReaderThreads);
}
for (i = 0; i < DOpts.ReaderThreads; ++i) {
SlotNo = (SlotNo + 1) % DOpts.ReaderThreads;
if (ReaderSlotAry[SlotNo] == 0) {
ReaderSlotAry[SlotNo] = 1;
return(SlotNo);
}
}
return(-1);
}
void
freeReaderSlot(int slot)
{
if (slot >= 0) {
ReaderSlotAry[slot] = 0;
}
}
void
sigHup(int SigNo)
{
TerminatePending++;
}
void
sigSegVReader(int sigNo)
{
int i;
if (TFd >= 0)
close(TFd);
for (i = 0; i < MaxFds; ++i)
if (FD_ISSET(i, &RFds) || FD_ISSET(i, &WFds))
close(i);
nice(20);
if (DOpts.ReaderCrashHandler == NULL ||
strcasecmp(DOpts.ReaderCrashHandler, "none") == 0) {
/* Chew spare CPU cycles and try to be noticed? :-) */
for (;;)
;
} else {
char cmdbuf[256];
if (fork())
return;
snprintf(cmdbuf, sizeof(cmdbuf), "%s %d", DOpts.ReaderCrashHandler, getpid());
system(cmdbuf);
for (;;)
sleep(60);
}
}
syntax highlighted by Code2HTML, v. 0.9.1