src/group.c
/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following functions.
- safeGroupInit
- safeGroup
- blddir
- safePath
- getServerGroup
- setGroupDir
- setGD
- setGroup
- attachGroupTalk
- attachGroup
- authGroup
- CMDgroup
- int_comp
- printListgroup
- CMDlistgroup
/* $Id: group.c,v 1.4 2002/03/26 11:18:35 proff Exp $
* $Copyright$
*/
#include "nglobal.h"
#include "filesystem.h"
#include "acc.h"
#include "nlist.h"
#include "xover.h"
#include "group.h"
/* this isn't as lame L1 cache wise as one might think, because the hits are
* (excluding iso characters) between elements 97 and 122, with the exception
* of 46 ('.'). the strspn we were using was O((m*n/2)^2). We could use
* a bitmap, but the the extra shifts and masks remove all benefit gained by the
* 32 byte foot-print.
*/
static n_u8 safe_group[256]; /* this is expanded from conf->safeGroup at startup */
EXPORT void safeGroupInit (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
n_u8 *p = (n_u8*)s;
bzero (safe_group, sizeof safe_group);
for (; *p; p++)
safe_group[*p] = 1;
}
EXPORT bool safeGroup (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
n_u8 *p = (n_u8*)s;
for (; *p; p++)
if (!safe_group[*p])
return FALSE;
return TRUE;
}
/*
* recursively build directory hierarchy, starting at leaf
*/
EXPORT bool blddir (char *name)
/* [<][>][^][v][top][bottom][index][help] */
{
char *p;
if ((p = strrchr (name, '/')) == NULL)
return FALSE;
*p = '\0';
if (mkdir (name, (mode_t) 0775) == -1)
{
if (!blddir (name))
{
*p = '/';
return FALSE;
}
if (mkdir (name, (mode_t) 0775) == -1)
{
if (errno != EEXIST)
{
loge (("error building directory hierarchy %s", name));
*p = '/';
return FALSE;
}
}
}
Stats->groupsCached++;
*p = '/';
return TRUE;
}
EXPORT bool safePath (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
if (strstr(s, "../"))
return FALSE;
return TRUE;
}
EXPORT struct server_cfg *getServerGroup (char *group)
/* [<][>][^][v][top][bottom][index][help] */
{
struct group_cfg *gcf = NULL, *gcf2 = NULL;
for (gcf = GroupList; gcf; gcf = gcf->next)
if (matchExp (gcf->group_pat, group, 1, 0))
gcf2 = gcf;
if (!gcf2)
return NULL;
return gcf2->server_cfg;
}
/*
* chdir to correct group directory. we need to know
* the server host also, as it is used as the second
* level directory. if group is NULL, then
* we goto the second level dir and no lower
*/
EXPORT bool setGroupDir (char *group, struct server_cfg *scfg)
/* [<][>][^][v][top][bottom][index][help] */
{
char dir[MAX_PATH];
if (!scfg)
return FALSE;
if (group)
{
strExchange(group, '.', '/');
sprintf(dir, "%.127s/%.255s", scfg->host, group);
} else
strcpy(dir, scfg->host);
if (group)
strExchange(group, '/', '.');
if (strEq(dir, CurrentDir))
return TRUE;
if (chdir(con->cacheDir)!=0)
{
loge (("chdir(\"%s\") failed!", con->cacheDir));
return FALSE;
}
if (chdir(dir)!=0)
{
if (mkdir(dir, (mode_t)0775) != 0 &&
blddir(dir) && mkdir(dir, (mode_t)0775) != 0)
goto bad;
if (chdir(dir)!=0)
{
bad:
loge (("chdir(\"%s\") failed", dir));
return FALSE;
}
logd (("cwd now %s", dir));
}
strcpy(CurrentDir, dir);
return TRUE;
}
EXPORT bool setGD ()
/* [<][>][^][v][top][bottom][index][help] */
{
return setGroupDir (CurrentGroup, CurrentGroupScfg);
}
EXPORT void setGroup (struct newsgroup *n, int cnt, int lo, int hi)
/* [<][>][^][v][top][bottom][index][help] */
{
bool f_changed = FALSE;
if (n->msgs != cnt)
{
n->msgs = cnt;
f_changed = TRUE;
}
if (n->lo_server != lo)
{
n->lo_server = lo;
f_changed = TRUE;
}
if (n->hi_server != hi)
{
n->hi_server = hi;
f_changed = TRUE;
}
if (f_changed)
n->group_change_time = time(NULL); /* XXX syscall overhead */
}
/*
* set current group. we can be called by via socket.c, so we avoid using
* C*emit routines.
*/
EXPORT bool attachGroupTalk (char *group, struct server_cfg *scfg, bool send_client)
/* [<][>][^][v][top][bottom][index][help] */
{
char buf[MAX_LINE];
struct newsgroup *n;
int hi, lo, msgs;
int cc;
if (!(n=newsgroupFindAddLock(group, NULL, FALSE)))
{
if (send_client)
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
newsgroupUnlockRead(n);
fprintf (scfg->fh, "group %s\r\n", group);
if (fflush (scfg->fh)!=0 ||
!(cc = Cfget (scfg, buf, sizeof buf)))
{
scfg->share->group_fail++;
if (send_client)
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
if (strToi (buf) != NNTP_GROUPOK_VAL)
{
if (send_client)
emit (buf);
scfg->share->group_fail++;
return FALSE;
}
n=newsgroupFindAddLock(group, NULL, FALSE);
if (sscanf (buf, "%*d %d %d %d", &msgs, &lo, &hi) == 3)
{
newsgroupUnlockRead(n); /* XXX not atomic */
newsgroupLockWrite(n);
{
n->group_time = time(NULL);
if (!(hi == 0 && lo == 0)) /* 0 0 == `undefined' */
setGroup(n, msgs, lo, hi);
newsgroupUnlockWrite(n);
newsgroupLockRead(n);
}
}
hi = getHi(n);
lo = getLo(n);
msgs = n->msgs;
newsgroupUnlockRead(n);
scfg->artno = lo;
if (send_client)
emitf ("%d %d %d %d %s\r\n", NNTP_GROUPOK_VAL, msgs, lo, hi, group);
if (!scfg->group || !strEq(scfg->group, group))
{
if (scfg->group)
free(scfg->group);
scfg->group = Sstrdup(group);
}
if (!scfg->group_actual || !strEq(scfg->group_actual, group))
{
if (scfg->group_actual)
free(scfg->group_actual);
scfg->group_actual = Sstrdup(group);
logd (("current newsgroup on server '%s' now '%s'", scfg->host, group));
}
scfg->share->group_good++;
return TRUE;
}
EXPORT bool attachGroup (char *group, bool sendclient)
/* [<][>][^][v][top][bottom][index][help] */
{
int hi, lo, msgs, gt = 0; /* stop gcc whinging */
struct newsgroup *n;
char *group_orig;
struct server_cfg *scfg=getServerGroup(group);
struct server_cfg *cf;
if (!scfg)
{
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
if (!scfg || !(n=newsgroupFindAddLock(group, NULL, FALSE)))
{
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
lo = getLo(n);
hi = getHi(n);
msgs = n->msgs;
gt = n->group_time;
newsgroupUnlockRead(n);
group_orig=scfg->group;
if (!setGroupDir(group, scfg))
{
emitf ("%d setGroupDir(%s, %s) failed\r\n", NNTP_PERM_VAL, group, scfg->host); /* don't use NNTP_NOSUCHGROUP here */
return FALSE;
}
if (lo && hi)
{
if (GroupNextNoCache)
{
GroupNextNoCache = FALSE;
}
else
{
if (time(NULL) - gt < scfg->group_timeout)
{
if (sendclient)
emitf ("%d %d %d %d %s\r\n", NNTP_GROUPOK_VAL, msgs, lo, hi, group);
if (scfg->group)
free(scfg->group);
scfg->group = Sstrdup(group);
goto good;
}
}
}
scfg->group = NULL; /* stop attachServer setting the group */
cf=attachServer (scfg);
scfg->group = group_orig;
if (!cf)
{
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
if (!attachGroupTalk(group, scfg, sendclient))
{
if (!sendclient)
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
} else
{
newsgroupLockRead(n);
lo = getLo(n);
}
good:
CurrentGroupNode = n;
CurrentGroupArtNum = lo;
CurrentGroupScfg = CurrentScfg = scfg;
strcpy (CurrentGroup, group);
return TRUE;
}
/*
* TODO: stress test this
*/
EXPORT struct authent *authGroup (char *attempted_group, bool output)
/* [<][>][^][v][top][bottom][index][help] */
{
struct authent *gp;
gp = authorise (RemoteHosts, attempted_group);
if (gp && gp->auth && AuthState != valid && gp->read)
{
if (output)
{
log (("%s not permitted read access to %s [missing credentials]", ClientHost, attempted_group));
emitrn (NNTP_NOSUCHGROUP);
}
return NULL;
}
if (!gp || !gp->read || gp->deny)
{
if (output)
{
log (("%s not permitted read access to %s", ClientHost, attempted_group));
emitrn (NNTP_NOSUCHGROUP);
}
return NULL;
}
return gp;
}
EXPORT bool CMDgroup (char *args)
/* [<][>][^][v][top][bottom][index][help] */
{
char attempted_group[MAX_GROUP + 20];
struct server_cfg *scfg;
struct authent *auth = NULL;
if (sscanf (args, "%*s %127[^\r\n \t]", attempted_group) != 1)
{
emitrn (NNTP_SYNTAX_USE);
return FALSE;
}
GroupsEntered++;
if (CurrentGroupArtRead && CurrentGroupScfg && CurrentGroupScfg->group)
loginn (("%s group %s %d", ClientHostNormal,
CurrentGroupScfg->group, CurrentGroupArtRead));
CurrentGroupArtRead = 0;
if (!safeGroup(attempted_group))
{
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
if (!(scfg = getServerGroup (attempted_group)))
{
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
if (con->groupSecurity || con->contentFilters)
{
auth=authGroup (attempted_group, TRUE);
if (!auth && con->groupSecurity)
{
CS->auth_blocked++;
return FALSE;
}
}
if (attachGroup (attempted_group, TRUE))
{
CurrentGroupAuth = auth;
if (auth)
{
CurrentGroupXoverIsFilt = xoverIsFilt (auth);
CurrentGroupNocem = auth->nocem;
}
else
{
CurrentGroupXoverIsFilt = FALSE;
CurrentGroupNocem = FALSE;
}
return TRUE;
}
return FALSE;
}
static int int_comp(const void *a, const void *b)
/* [<][>][^][v][top][bottom][index][help] */
{
int i = *(const int *)a, j = *(const int *)b;
if (i < j) return -1;
else if (i == j) return 0;
else return 1;
}
static bool printListgroup (struct server_cfg *scfg, FILE *fh)
/* [<][>][^][v][top][bottom][index][help] */
{
DIR *dp;
char buf[MAX_LINE];
char *p;
struct dirent *ep;
int cc, i, m;
int last;
int *listgroup, *cur;
int len = 0, cap = MAX_XOVER;
int bytes=0;
cur = listgroup = Smalloc (cap * sizeof(int));
while ((cc = Cfget (scfg, buf, sizeof buf)) && !EL (buf))
{
bytes += cc;
if (len >= cap)
{
cap += MAX_XOVER;
listgroup = Srealloc (listgroup, cap * sizeof(int));
cur = &listgroup[len];
}
*cur++ = strToi (buf);
len++;
}
if (!cc)
{
free (listgroup);
emitrn (".");
return FALSE;
}
if (!(dp = opendir(".")))
{
loge (("printListgroup : opendir('.') failed"));
free (listgroup);
emitrn (".");
return TRUE;
}
for (m=0; (ep = readdir(dp)); m++)
{
p = ep->d_name;
i = strspn(p, "01234567890");
if (i == 0 || p[i])
continue;
if (len >= cap)
{
cap += MAX_XOVER;
listgroup = realloc(listgroup, cap * sizeof(int));
cur = &listgroup[len];
}
*cur++ = strToi(p);
len++;
}
qsort (listgroup, len, sizeof(int), int_comp);
for (bytes = 0, cur = listgroup, last = 0; len > 0; cur++, len--, m--)
{
if (*cur == last)
continue;
last = *cur;
cc = emitf ("%d\r\n", last);
if (m>0)
bytes += cc;
if (fh)
fprintf (fh, "%d\r\n", last);
}
bytes += emitrn (".");
return TRUE;
}
EXPORT bool CMDlistgroup (char *args)
/* [<][>][^][v][top][bottom][index][help] */
{
char gr[MAX_GROUP] = "";
char *group;
char buf[MAX_LINE];
struct server_cfg *scfg;
struct authent *auth = NULL;
char fn2[MAX_GROUP + 20];
FILE *fh;
struct newsgroup *n;
int cc;
sscanf(args, "%*s %127[^\r\n \t]", gr);
if (*gr && !strEq(CurrentGroup, gr)) /* XXX listgroup is sugar. modularise with CMDgroup */
{
GroupsEntered++;
if (CurrentGroupArtRead && CurrentGroupScfg && CurrentGroupScfg->group)
loginn (("%s group %s %d", ClientHostNormal, CurrentGroupScfg->group, CurrentGroupArtRead));
CurrentGroupArtRead = 0;
if (!safeGroup(gr))
{
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
if (con->groupSecurity || con->contentFilters)
{
auth=authGroup (gr, TRUE);
if (!auth && con->groupSecurity)
{
CS->auth_blocked++;
return FALSE;
}
}
if (attachGroup (gr, FALSE))
{
CurrentGroupAuth = auth;
if (auth)
CurrentGroupXoverIsFilt = xoverIsFilt (auth);
else
CurrentGroupXoverIsFilt = FALSE;
} else
{
return FALSE;
}
} else
{
if (!*CurrentGroup)
{
emitrn (NNTP_NOTINGROUP);
return FALSE;
}
}
group = CurrentGroup;
scfg = getServerGroup(group);
if (!scfg)
{
emitrn (NNTP_NOSUCHGROUP);
return FALSE;
}
if (!scfg->listgroup_timeout)
{
Cemit (args);
Cflush ();
if (!(cc = Cget (buf, sizeof buf)))
{
scfg->share->listgroup_fail++;
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
emit (buf);
flush ();
if (strToi (buf) != NNTP_LIST_GROUP_FOLLOWS_VAL)
{
scfg->share->listgroup_fail++;
return FALSE;
}
if (printListgroup (scfg, NULL))
{
scfg->share->listgroup_good++;
return TRUE;
}
else
{
scfg->share->listgroup_fail++;
return FALSE;
}
}
if (!setGroupDir(group, scfg))
{
scfg->share->listgroup_fail++;
emitrn(NNTP_PERM);
return FALSE;
}
n=newsgroupFindAddLock(group, NULL, FALSE);
if (n)
{
if (n->group_change_time && n->listgroup_time >= n->group_change_time)
{
newsgroupUnlockRead(n);
if ((fh=fopen(".listgroup", "r")))
{
emitrn (NNTP_LIST_GROUP_FOLLOWS);
while (fgets (buf, sizeof buf, fh))
{
emit (buf);
}
emitrn (".");
if (ferror (fh))
{
loge (("problems reading %s/%s ... unlinked", CurrentDir, ".listgroup"));
unlink (".listgroup");
}
fclose (fh);
return TRUE;
}
}
else
newsgroupUnlockRead(n);
}
if (!attachServer(scfg))
{
scfg->share->listgroup_fail++;
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
Cfemit (scfg, args);
Cfflush (scfg);
if (!(cc = Cfget (scfg, buf, sizeof buf)))
{
emitrn (NNTP_SERVERTEMPDOWN);
return FALSE;
}
emit (buf);
if (strToi (buf) != NNTP_LIST_GROUP_FOLLOWS_VAL)
{
scfg->share->listgroup_fail++;
return FALSE;
}
sprintf (fn2, ".listgroup%d", getpid());
fh = fopen (fn2, "w");
if (!fh)
{
blddir (fn2);
fh = fopen (fn2, "w");
}
if (!fh)
loge (("couldn't open %s for write", fn2));
if (!printListgroup(scfg, fh))
{
scfg->share->listgroup_fail++;
unlink (fn2);
if (fh)
fclose(fh);
return FALSE;
}
scfg->share->listgroup_good++;
if (ferror (fh))
unlink (fn2);
else
rename (fn2, ".listgroup");
if (fh)
fclose (fh);
n=newsgroupFindAddLock(group, NULL, TRUE);
if (n)
{
time_t tim = time(NULL);
n->listgroup_time = tim;
newsgroupUnlockWrite(n);
#ifndef MMALLOC
PutSetListGroup(group, tim);
#endif
}
return TRUE;
}