src/acc.c

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

DEFINITIONS

This source file includes following functions.
  1. sigalrm
  2. rfc931_lookup
  3. filterReadConfigs
  4. authReadConfig
  5. striplocaldomain
  6. authorise
  7. getAuth
  8. authWrap
  9. fillAuth

/* $id$
 * $Copyright$
 */

#include "nglobal.h"
#include "network.h"

#include "filter.h"
#include "reg.h"

#include "acc.h"
#include "authinfo.h"

#ifdef HAVE_LIBWRAP
#  include <tcpd.h>
#endif

#include <setjmp.h>

jmp_buf jmp;

#ifdef HAVE_LIBWRAP
int deny_severity;
int allow_severity;
#endif

static RETSIGTYPE
sigalrm (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        longjmp (jmp, 1);
}

/*
 * rfc931_lookup - return remote user name, given socket structures
 * loosly based around ideas in Wietse Venema's rfc931 routine in tcp wrappers
 */

static char *rfc931_lookup (struct sockaddr_in *our_sin, struct sockaddr_in *remote_sin)
/* [<][>][^][v][top][bottom][index][help] */
{
        unsigned remote_port;
        unsigned our_port;
        struct sockaddr_in remote_query_sin;
        struct sockaddr_in our_query_sin;
        volatile int sock;
        static char user[128];
        char buf[512];
        FILE *fh = NULL;        /* stop gcc complaining */
        RETSIGTYPE (*al)(int) = NULL;
        long al_s;

        our_query_sin = *our_sin;
        our_query_sin.sin_port = 0;
        remote_query_sin = *remote_sin;
        remote_query_sin.sin_port = htons (113);

        al = signal (SIGALRM, sigalrm);
        al_s = alarm (con->rfc931Timeout);
        if (setjmp (jmp) == 1)
                goto err;
        if ((sock = socket (AF_INET, SOCK_STREAM, 0)) == -1
            || bind (sock, (struct sockaddr *) &our_query_sin, sizeof (our_query_sin)) == -1)
        {
                logw (("couldn't socket()/bind() for rfc931 request"));
                goto err;
        }
        if (connect (sock, (struct sockaddr *) &remote_query_sin, sizeof (remote_query_sin)) == -1)
                goto err;
        fh = fdopen (sock, "r+");
        setbuf(fh, NULL); /* sunos4.1.x doesn't work correctly with buffered bidirectional stdio pipes */
        fprintf (fh, "%u,%u\r\n", ntohs (remote_sin->sin_port), ntohs (our_sin->sin_port));
        fflush (fh);
        if (fgets (buf, sizeof buf, fh)
            && !ferror (fh)
            && sscanf (buf, "%u , %u : USERID :%*[^:]:%127s", &remote_port, &our_port, user) == 3
            && ntohs (remote_sin->sin_port) == remote_port
            && ntohs (our_sin->sin_port) == our_port)
        {
                char *p = strchr (user, '\r');
                if (p)
                        *p = '\0';
                alarm (0);
                signal (SIGALRM, SIG_DFL);
                nonblock (sock);
                close (sock);
                if (al)
                {
                        signal(SIGALRM, al);
                        if (al_s > 0)
                                alarm(al_s);
                } else
                        signal (SIGALRM, SIG_DFL);
                return user;
        }
      err:
        alarm (0);
        if (al)
        {
                signal(SIGALRM, al);
                if (al_s > 0)
                        alarm(al_s);
        } else
                signal (SIGALRM, SIG_DFL);
        nonblock (sock);
        close (sock);
        return NULL;
}

/* linked list of authentication entries */
struct authent *authent = NULL;

/* linked index into filter-file-chains */
static struct filter_list_index
{
        struct filter_list_index *next;
        char *name;                     /* name of file */
        struct filter *filter;          /* poitner to head of list of filters */
} *filter_list_index;

static struct filter_chain *filterReadConfigs (char *files)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct filter_chain *head=NULL, *fc=NULL;
        char *fi;
        for (fi = strtok(files, ","); fi; fi = strtok(NULL, ","))
        {
                char buf[MAX_LINE];
                struct filter *f=NULL;
                struct filter_list_index *fli = NULL;
                FILE *fp;
                int n;
                fp = fopen(fi, "r");
                if (!fp)
                {
                        loge (("couldn't open filter rule file %s...skipped", fi));
                        continue;
                }
                if (!fc)
                {

                        fc=head=Scalloc(1, sizeof *fc);
                } else
                {
                        fc->next = Scalloc(1, sizeof *fc);
                        fc = fc->next;
                }
                for (fli = filter_list_index; fli; fli=fli->next)
                {
                        if (strEq(fli->name, fi))
                        {
                                fc->filter = fli->filter;
                                goto cont;
                        }
                }
                for (n=0, *buf='\0'; *buf || fgets(buf, sizeof buf, fp); n++)
                {
                        char scope[127], weight_buf[32], *weight=weight_buf, options[MAX_LINE], pat[MAX_LINE];
                        struct strStack *st;
                        char *opt;
                        int err_code;
                        strStripEOL(buf);
                        if (!*buf || buf[0] == '#')
                        {
                                *buf = '\0';
                                continue;
                        }
                        if (sscanf(buf, "%127s %31s %1023s %1023[^\r\n]", scope, weight, options, pat)!=4)
                        {
                                loge (("invalid filter line %d in file %s (ignored): %s", n, fi, buf));
                                *buf = '\0';
                                continue;
                        }
                        if (!f)
                        {
                                f = fc->filter = Scalloc (1, sizeof *f);
                        } else
                        {
                                f->next = Scalloc (1, sizeof *f);
                                f = f->next;
                        }
                        if (strEq(scope, "head"))
                                f->scope = sc_head;
                        else
                        if (strEq(scope, "ahead"))
                                f->scope = sc_ahead;
                        else
                        if (strEq(scope, "body"))
                                f->scope = sc_head;
                        else
                        if (strEq(scope, "article"))
                                f->scope = sc_article;
                        else
                        {
                                char *p=strchr(scope, ':');
                                if (!p)
                                {
                                        loge (("bad filter header/scope %s in file %s: %s", scope, fi, buf));
                                        Exit(1);
                                }
                                *p='\0';
                                f->scope = sc_header;
                                f->scope_header = Sstrdup(scope);
                        }
                        if (*weight=='/' || *weight=='*')
                                f->weight_op = *weight++;
                        else
                                f->weight_op = '\0';
                        if (sscanf(weight, "%d", &f->weight)!=1)
                        {
                                loge (("bad filter weight in %s line %d: '%s'", fi, n, buf));
                                Exit(1);
                        }
                        for (opt = strtok(options, ","); opt; opt = strtok(NULL, ","))
                        {
                                if (strCaseEq(options, "nocase"))
                                        f->ignore_case = TRUE;
                                else
                                if (strCaseEq(options, "case"))
                                        f->ignore_case = FALSE;
                                else
                                {
                                        loge (("bad filter option in %s line %d: '%s'", fi, n, buf));
                                        Exit(1);
                                }
                        }
                        strStripEOL(pat);
                        st = strStackAdd (NULL, pat);
                        for (; fgets(buf, sizeof buf, fp); n++)
                        {
                                char *p;
                                strStripEOL (buf);
                                for (p=buf; isspace(*p); p++);
                                if (p==buf || !*p)
                                        break;
                                st = strStackAdd (st, p);
                        }
                        f->pat = Sstrdup(st->data);
                        strStackFree (st);
#ifdef USE_REGEX
                        if ((err_code = nn_regcomp(&f->preg, f->pat, REG_EXTENDED|REG_NEWLINE|REG_NOSUB|(f->ignore_case? REG_ICASE: 0)))!=0)
                        {
                                char errbuf[MAX_LINE];
                                regerror(err_code, &f->preg, errbuf, sizeof errbuf);
                                loge (("bad regular expression in %s line %d: %s", fi, n, errbuf));
                                Exit(1);
                        }
#endif
                        if (feof(fp) || ferror(fp))
                                break;
                }
                if (!fc->filter)
                {
                        loge (("filter file %s contained no filters!", fi));
                        Exit(1);
                }
                if (!filter_list_index)
                {
                        fli = filter_list_index = Scalloc (1, sizeof *filter_list_index);
                } else
                {
                        
                        fli->next = filter_list_index = Scalloc (1, sizeof *filter_list_index);
                        fli = fli->next;
                }
                fli->name = fc->name = Sstrdup(fi);
                fli->filter = fc->filter;
        cont:
                continue;
        }
        return head;
}

EXPORT bool authReadConfig (char *f)
/* [<][>][^][v][top][bottom][index][help] */
{
        FILE *fp;
        char buf[MAX_LINE];
        struct authent *ll = NULL;
        if (!(fp = fopen (f, "r")))
        {
                loge (("couldn't read access file '%s'", f));
                return FALSE;

        }
        filter_list_index = NULL;
        while (fgets (buf, sizeof buf, fp))
        {
                char host[128], group[128], perms[128], filters[1024]="", users[1024]="", junk[2];
                char *p;
                strStripEOL(buf);
                if (!*buf || buf[0] == '#')
                        continue;
                if (sscanf(buf, "%127s %127s %127s %1023s %1023s %1s", host, group, perms, filters, users, junk) <3)
                {
                        loge (("bad access control in %s: '%s'", f, buf));
                        continue;
                }
                if (!ll)
                        authent = ll = Scalloc (1, sizeof *ll);
                else
                {
                        ll->next = Scalloc (1, sizeof *ll);
                        ll = ll->next;
                }
                ll->host = Sstrdup (host);
                ll->group = Sstrdup (group);
                ll->users = *users? Sstrdup (users): NULL;
                p = strtok(perms, ",");
                do
                {
                        if (strCaseEq(p, "read"))
                                ll->read = TRUE;
                        else
                        if (strCaseEq(p, "post"))
                                ll->post = TRUE;
                        else
                        if (strCaseEq(p, "deny"))
                                ll->deny = TRUE;
                        else
                        if (strnCaseEq(p, "auth", 4))
                        {
                                ll->authinfo = authinfo_get(f, p + 4);
                                ll->auth = TRUE;
                        }
                        else
                        if (strCaseEq(p, "filter"))
                                ll->filter = TRUE;
                        else
                        if (strCaseEq(p, "censor"))
                                ll->censor = TRUE;
                        else
                        if (strCaseEq(p, "ihave"))
                                ll->ihave = TRUE;
                        else
                        if (strCaseEq(p, "quick"))
                                ll->quick = TRUE;
                        else
                        if (strCaseEq(p, "nocem"))
                                ll->nocem = TRUE;
                        else
                        if (strCaseEq(p, "http"))
                                ll->nocem = TRUE;
                        else
                        if (strCaseEq(p, "strip"))
                        {
                                
                                if (!strEq (host, "*"))
                                        loge (("bad access control in %s: strip host must be '*': '%s'", f, buf));
                                ll->strip = TRUE;
                        }
                        else
                                loge (("bad access control in %s: '%s'", f, buf));
                } while ((p=strtok(NULL, ",")));
                if (*filters && *filters != '*')
                        ll->filter_chain = filterReadConfigs(filters);
        }
        fclose (fp);
        if (ll)
                ll->next = NULL;
        return TRUE;
}

static bool striplocaldomain (char *remotehost, char *localhost)
/* [<][>][^][v][top][bottom][index][help] */
{
        int n1, n2;
        char *p;
        n1 = strlen (localhost) - 1;
        n2 = strlen (remotehost) - 1;
        for (; n1 && n2 && (localhost[n1] == remotehost[n2]); n1--, n2--) ;
        if (!(p = strchr (remotehost + n2, '.')))
        {
                *remotehost = '\0';
                return TRUE;
        }
        *p = '\0';
        return TRUE;
}

/* 
 * hosts, group or both must be specified.
 */

EXPORT struct authent *authorise (char **hosts, char *group)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct authent *ll, *ll_last_match = NULL;
        for (ll = authent; ll; ll = ll->next)
        {
                char **hp;
                if (hosts)
                {
                        for (hp = hosts; *hp; hp++)
                        {
                                if (matchExp (ll->host, *hp, 1, 0))
                                {
                                        if (!group)
                                                ll_last_match = ll;
                                        else
                                        {
                                                if (!matchExp (ll->group, group, 1, 0))
                                                        continue;
                                                ll_last_match = ll;
                                        }
                                }
                        }
                } else
                {
                        /*
                         * only look for match all hosts
                         */
                        if (strEq (ll->host, "*") &&
                             matchExp (ll->group, group, 1, 0))
                                ll_last_match = ll;
                }
                if (ll_last_match && ll_last_match->quick)
                        return ll_last_match;
        }
        return ll_last_match;
}

EXPORT bool getAuth (int sock, char *themgood, char *them, char *themlocal, char *rfc931them, char *rfc931themlocal, char *themaddr, char *rfc931themaddr, int themlen, char *us, struct sockaddr_in *themsockaddr)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct sockaddr_in our_sin, remote_sin;
        struct hostent *hp;
        int sinlen = sizeof (struct sockaddr_in);
        char *user = NULL;
        char buf[MAX_LINE];
        char *host1 = NULL;
        *themgood = *them = *themlocal = *rfc931them = *rfc931themlocal = *themaddr = *rfc931themaddr = '\0';

        if (getpeername (sock, (struct sockaddr *) &remote_sin, &sinlen) < 0)
        {
                if (isatty (sock))      /* debugging */
                {
                        strcpy (themgood, "<STDIN>");
                        return TRUE;
                } else
                {
                        strcpy (themgood, "unknown");
                        logw (("couldn't getpeername()"));
                        loginne (("? cant getpeername"));
                        return FALSE;
                }
        }
        else if (remote_sin.sin_family != AF_INET)
        {
                logen (("remote connection not an AF_INET connection"));
                emitf ("%d Not an INET connection. Goodbye", NNTP_ACCESS_VAL);
                strcpy (themgood, "unknown");
                return FALSE;
        }
        if (getsockname (sock, (struct sockaddr *) &our_sin, &sinlen) < 0)
        {
                logw (("couldn't getsockname()"));
        } else
        {
                if (con->rfc931)
                {
                        user = rfc931_lookup(&our_sin, &remote_sin);
                        if (user)
                                user[64] = '\0';
                }
                if (!user)
                        user = "unknown";
        }
        *themsockaddr = remote_sin;
        if ((hp = gethostbyaddr ((char *) &remote_sin.sin_addr, sizeof (remote_sin.sin_addr), AF_INET)))
        {
                struct hostent *h;
                host1 = Sstrdup ((char *) hp->h_name);
                if (strspn(host1, ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-") != strlen(host1))
                {
                        logwn (("suspect address ignored: gethostbyaddr(\"%s\") = '%.128s'", inet_ntoa (remote_sin.sin_addr), host1));
                        goto ret;
                }
                h = gethostbyname (host1);
                if (h)
                {
                        char **addr;
                        if (h->h_length != 4)
                        {
                                logen (("gethostbyname('%.128s')->h_length = %d != 4", host1, h->h_length));
                                goto ret;
                        }
                        if (h->h_addrtype != AF_INET)
                        {
                                logen (("gethostbyname('%.128s')->h_addrtype = %d != AF_INET", host1, h->h_addrtype));
                                goto ret;
                        }
                        for (addr = h->h_addr_list; *addr; addr++)
                        {
                                if (memcmp(&remote_sin.sin_addr, *addr, 4) == 0)
                                {
                                        if (strlen (host1) >= themlen)
                                        {
                                                logwn (("host name length limited reached. ignored."));
                                                continue;
                                        }
                                        strcpy (them, host1);
                                        strLower (them);
                                        strcpy (themlocal, them);
                                        striplocaldomain (themlocal, us);
                                        sprintf (buf, "%.127s@%.127s", user, them);
                                        strcpy (rfc931them, buf);
                                        if (*themlocal)
                                                sprintf (buf, "%.127s@%.127s", user, themlocal);
                                        break;
                                }
                        }
                        if (!*addr)
                            {
                                loginnw (("gethostbyaddr: %.128s != %s", host1, inet_ntoa (remote_sin.sin_addr)));
                            }
                                
                }
        } else
        {
                loginn (("? cant gethostbyaddr %s %s", inet_ntoa (remote_sin.sin_addr), strerror(errno)));
        }
ret:
        strcpy (themaddr, inet_ntoa (remote_sin.sin_addr));
        if (!*them)
                strcpy(them, themaddr);
        sprintf (rfc931themaddr, "%.127s@%.127s", user, themaddr);
        if (host1)
                free (host1);
        if (*rfc931them)
                strcpy(themgood, rfc931them);
        else
                strcpy(themgood, rfc931themaddr);
        return TRUE;
}
static bool
authWrap(int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
#ifdef HAVE_LIBWRAP
        struct request_info req;
        request_init(&req, RQ_DAEMON, "nntpcached", RQ_FILE, fd, NULL);
        fromhost(&req);
        return (hosts_access(&req) != 0);
#else
        return TRUE;
#endif
}

EXPORT bool fillAuth (int fd, char *what)
/* [<][>][^][v][top][bottom][index][help] */
{
   if (con->useLibWrap && !authWrap(fd))
        return FALSE;
    if (!getAuth (fd, ClientHost, ClientHostNormal, ClientHostLocal, ClientHostRFC931, ClientHostLocalRFC931, ClientHostAddr, ClientHostAddrRFC931, sizeof (ClientHost), Host, &ClientRemoteAddr))
        return FALSE;
    strncpy (Task->ti_client_host, ClientHost, sizeof Task->ti_client_host);
    if (!(ConnectAuth = authorise (RemoteHosts, what)) || ConnectAuth->deny)
        return FALSE;
    return TRUE;
}

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