src/group.c

/* [<][>]
[^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following functions.
  1. safeGroupInit
  2. safeGroup
  3. blddir
  4. safePath
  5. getServerGroup
  6. setGroupDir
  7. setGD
  8. setGroup
  9. attachGroupTalk
  10. attachGroup
  11. authGroup
  12. CMDgroup
  13. int_comp
  14. printListgroup
  15. 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;
}

/* [<][>][^][v][top][bottom][index][help] */