src/sockets.c

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

DEFINITIONS

This source file includes following functions.
  1. nonblock
  2. block
  3. server_up
  4. server_down
  5. scfg_connect
  6. detachServer
  7. attachServer
  8. Sread
  9. Stest
  10. Sfree
  11. Smore
  12. Salloc
  13. Cfget
  14. getArt
  15. getHostAddr
  16. logFromClient
  17. logToClient
  18. logFromServer
  19. logToServer
  20. writeClient
  21. fwriteClient
  22. emit
  23. emitrn
  24. emitf
  25. Cemitf
  26. Cfemitf
  27. flush
  28. Cfflush
  29. Cflush
  30. Cfemit
  31. Cfemitrn
  32. Cemit
  33. Cemitrn
  34. Cget
  35. Get

/* $Id: sockets.c,v 1.12 2002/04/04 11:14:54 proff Exp $
 * $Copyright$
 */

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

#include "sockets.h"

EXPORT FILE *clientin;
EXPORT FILE *clientout;

#if defined(F_GETFL) && defined(O_NONBLOCK)
EXPORT int nonblock (int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
        int flags = O_NONBLOCK | fcntl (fd, F_GETFL);
        return fcntl (fd, F_SETFL, flags);
}

EXPORT int block (int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
        int flags = (~O_NONBLOCK) & fcntl (fd, F_GETFL);
        return fcntl (fd, F_SETFL, flags);
}
#else
#  error gee. get a real system. this one doesnt have O_NONBLOCK
#endif

static void server_up (struct server_cfg *scfg)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (scfg->share->server_up <= scfg->share->server_down)
        {
                time_t now = time(NULL);
                if (scfg->share->server_down != 0)
                        scfg->share->server_down_time += now - scfg->share->server_down;
                scfg->share->server_up = now;
        }
}

static void server_down (struct server_cfg *scfg)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (scfg->share->server_down <= scfg->share->server_up)
        {
                time_t now = time(NULL);
                if (scfg->share->server_up  != 0)
                        scfg->share->server_up_time += now - scfg->share->server_up;
                scfg->share->server_down = now;
        }
}

static int scfg_connect (struct server_cfg *scfg)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct sockaddr_in *in;
        struct sockaddr_in *in_us;
        int sock;
        int yes = 1;
        sock = socket (AF_INET, SOCK_STREAM, 0);
        if (sock == -1)
        {
                loge (("socket() failed"));
                        return -1;
        }
        if (!(in_us = getHostAddr (scfg->us)))
        {
                loge (("could not convert '%s' to an address", scfg->us));
                return -1;
        }
        if (bind (sock, (struct sockaddr *) in_us, sizeof *in_us) == -1)
                loge (("couldn't bind to %s", scfg->us));
        if (!(in = getHostAddr (scfg->host)))
        {
                loge (("could not convert '%s' to an address", scfg->host));
                return -1;
        }
        if (in->sin_port == 0)
                in->sin_port = htons(119);
        if (connect (sock, (struct sockaddr *) in, sizeof *in) == -1)
        {
                logw (("could not connect to %s as %s", scfg->host, scfg->us));
                close (sock);
                return -1;
        }
#ifdef SO_KEEPALIVE
        if (setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, (char*) &yes, sizeof yes))
                logd(("keepalive setsockopt failed on %s", scfg->host));
#endif
        return sock;
}

EXPORT void detachServer (struct server_cfg *scfg)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (scfg->fd >= 0)
        {
                fclose(scfg->fh);
                scfg->fd = -1;
                if (scfg->group_actual)
                {
                        free(scfg->group_actual);
                        scfg->group_actual = NULL;
                }
                if (scfg->group)
                {
                        free(scfg->group);
                        scfg->group= NULL;
                }
        }
}

EXPORT struct server_cfg *attachServer (struct server_cfg *scfg)
/* [<][>][^][v][top][bottom][index][help] */
{
        int res;
        char buf[MAX_LINE];
        time_t now;
        assert(scfg);
        /*
         * we do not use Cfemitf as it may call this routine!
         */

        /*
         * see if we are already attached
         */
        if (scfg->fd >= 0)
        {
                struct sockaddr_in sa;
                int slen = sizeof sa;
                fd_set rdfs;
                struct timeval tv;


                FD_ZERO (&rdfs);
                FD_SET (scfg->fd, &rdfs);

                tv.tv_sec = 0;
                tv.tv_usec = 0;

                if ((select (scfg->fd + 1, &rdfs, NULL, NULL, &tv) == 0) &&
                   (getpeername (scfg->fd, (struct sockaddr *) (struct sockaddr *)&sa, &slen) == 0))
               {
                        /* we have delayed group binding. make sure group is bound, but only
                         * if this server is the current server. non-current servers do not
                         * need group binding, however one day they may, and this routine will
                         * need to be de-optimised accordingly
                         */
                        if (CurrentGroupScfg == scfg && (scfg->group && (!scfg->group_actual || !strEq(scfg->group, scfg->group_actual))))
                                goto dogroup;
                        return scfg;
               }
        }
        if (scfg->fh)
                fclose (scfg->fh);
        scfg->fd = -1;
        now = time (NULL);
        if (now - scfg->share->server_down < con->serverDownRecheck)
        {
                logd (("server attach bypassed (server tagged down) %d seconds remaining before retry",
                       (int)(con->serverDownRecheck - (now - scfg->share->server_down))));
                return NULL;
        }
        scfg->share->server_check = now;
        if ((scfg->fd = scfg_connect (scfg)) < 0)
        {
                logw (("couldn't connect to %s as %s", scfg->host, scfg->us));
        bad:
                scfg->fd = -1;
                server_down(scfg);
                scfg->share->connect_fail++;
                if (con->statistics)
                        Stats->serverConnectsFailed++;
                return NULL;
        }
        scfg->fh = fdopen (scfg->fd, "r+");
        logd (("connected to NNTP server %s as %s", scfg->host, scfg->us));
        if (!Cfget (scfg, buf, sizeof buf))
        {
        dropped:
                logw (("server %s dropped during initial connect phase", scfg->host));
                fclose (scfg->fh);
                goto bad;
        }
        strStripEOL (buf);
        res = strToi (buf);
        if (res != NNTP_POSTOK_VAL &&
            res != NNTP_NOPOSTOK_VAL)
        {
                logw (("<- %.128s", buf));
                goto dropped;
        }
        scfg->post_ok = (res==NNTP_POSTOK_VAL)? TRUE: FALSE;
        
        /*  use "authinfo" login here -bradf */
        if (scfg->user && scfg-> pass)
        {
                fprintf (scfg->fh, "authinfo user %s\r\n", scfg->user);
                fflush (scfg->fh);
                if (!Cfget(scfg, buf, sizeof buf))
                        goto dropped;
                fprintf (scfg->fh, "authinfo pass %s\r\n", scfg->pass);
                fflush (scfg->fh);
                if (!Cfget(scfg, buf, sizeof buf))
                        goto dropped;
        }
                
        if (ModeReader)
        {
                fprintf (scfg->fh, "mode reader\r\n");
                fflush (scfg->fh);
                if (!Cfget (scfg, buf, sizeof buf))
                        goto dropped;
        }
        if (scfg->group_actual)
        {
                free(scfg->group_actual);
                scfg->group_actual = NULL;
        }
        if (scfg->group)
        {
dogroup:
                if (!attachGroupTalk (scfg->group, scfg, FALSE))
                        goto dropped;
               /* set pointer back to article we were looking at   -an */
                if (strEq(scfg->group, CurrentGroup) && CurrentGroupArtNum != scfg->artno) {
                    fprintf (scfg->fh, "stat %d\r\n", CurrentGroupArtNum);
                    if (fflush(scfg->fh) || !Cfget(scfg, buf, sizeof buf))
                        goto dropped;
                    if (strToi (buf) == NNTP_NOTHING_FOLLOWS_VAL) {
                        scfg->artno = CurrentGroupArtNum;
                    }
                }
        }
        server_up(scfg);
        scfg->share->connect_good++;
        if (con->statistics)
                Stats->serverConnects++;
        return scfg;
}

EXPORT int Sread (struct server_cfg *scfg, char *buf, int len)
/* [<][>][^][v][top][bottom][index][help] */
{
        fd_set rdfs;
        struct timeval tv;
        int cc;
        int fd = scfg->fd;

        FD_ZERO (&rdfs);
        FD_SET (fd, &rdfs);

        tv.tv_sec = con->networkTimeout;
        tv.tv_usec = 0;

        if (select (fd + 1, &rdfs, NULL, NULL, &tv) == 0)
        {
                logw (("news server read timed out"));
                return 0;
        }
      redo:
        errno = 0;
        if ((cc = read (fd, buf, len - 1)) < 1)
        {
                if (cc<0 && errno == EINTR)
                        goto redo;
                logw (("error reading from news server"));
                        return 0;
        }
        buf[cc] = '\0';
        server_up (scfg);
        scfg->share->bytes_from += cc;
        if (con->statistics)
            {
                Stats->serverFromBytes += cc;
                CS->serverFromBytes+=len;
            }
        return cc;
}

/*
 * consider turning this into a ll. I vote against for performance reasons at the moment.
 */

static int Spos[FD_HIGH];
static char *Sbuf[FD_HIGH];

static bool Sinitalised = FALSE;

inline static int Stest (int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
        assert (fd<FD_HIGH);
        if (fd<0)
            {
                logw (("Stest(%d) -- server down?", fd));
                return FALSE;
            }
        if (!Sinitalised)       /* apparently some sun4.1.3 GCC -O system breaks static arrays */
        {
                memset (Spos, 0, FD_HIGH * (sizeof *Spos));
                memset (Sbuf, 0, FD_HIGH * (sizeof *Sbuf));
        }
        Sinitalised = TRUE;
        return TRUE;
}

EXPORT bool Sfree (int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (!Stest (fd))
            return FALSE;
        if (Sbuf[fd])
                free (Sbuf[fd]);
        Sbuf[fd] = NULL;
        Spos[fd] = 0;
        return TRUE;
}

EXPORT bool Smore (int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (fd<0) /* not even connected yet */
                return FALSE;
        if (!Stest (fd))
                return FALSE;
        if (Spos[fd])
                return TRUE;
        {
                fd_set fs;
                struct timeval tv;
                tv.tv_sec = 0;
                tv.tv_usec = 0;
                FD_ZERO (&fs);
                FD_SET (fd, &fs);
                if (select (fd + 1, &fs, NULL, NULL, &tv) == 1 &&
                    FD_ISSET (fd, &fs))
                        return TRUE;
                else
                        return FALSE;
        }
}

inline static char *Salloc (int fd)
/* [<][>][^][v][top][bottom][index][help] */
{
        Sbuf[fd] = Smalloc (MAX_BFR);
        Spos[fd] = 0;
        return Sbuf[fd];
}

EXPORT int Cfget (struct server_cfg *scfg, char *line, int len)
/* [<][>][^][v][top][bottom][index][help] */
{
        char *buf;
        int pos;
        int got_r = 0;
        int n;
        assert (scfg);
        if (!Stest (scfg->fd))
                return 0;
        if (!(buf = Sbuf[scfg->fd]))
                buf = Salloc (scfg->fd);
        pos = Spos[scfg->fd];
        for (n = 0;;)
        {
                char c;
                if (!pos && Sread (scfg, buf + pos, MAX_BFR - pos) < 1)
                        return 0;
                for (; n < len - 1; line[n++] = c, pos++)
                {
                        c = buf[pos];
                        if (c == '\n')
                        {
#if 0 /* lots of lame servers do this */
                                if (!got_r)
                                        logw (("saw '\\n' before '\\r'"));
#endif
                                line[n++] = c;
                                if (!buf[++pos])
                                        Spos[scfg->fd] = 0;
                                else
                                        Spos[scfg->fd] = pos;
                                goto ret;
                        }
                        if (c == '\r')
                        {
#if 0 /* lots of lame servers do this */
                                if (got_r++)
                                        logw (("saw more than one '\\r'"));
#endif
                                continue;
                        }
                        if (c == '\0')
                        {
                                pos = Spos[scfg->fd] = 0;
                                break;

                        }
                }
                if (n >= len - 1)
                        goto ret;
        }
ret:
        line[n] = '\0';
        if (con->logFromServer && *line)
                logFromServer (scfg->host, line);
        return n;
}

EXPORT bool getArt (struct server_cfg *srvr, FILE *fout)
/* [<][>][^][v][top][bottom][index][help] */
{
        char line[MAX_LINE]="";

        while (Cfget (srvr, line, sizeof line) && !EL (line))
                fputs (line, fout);
        fputs (".\r\n", fout);
        return EL(line);
}

EXPORT struct sockaddr_in *getHostAddr (char *hostname)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct hostent *host;
        static struct sockaddr_in in;
        char *p=strchr(hostname, ':');
        bzero(&in, sizeof in);
        in.sin_family = AF_INET;
        if (p)
        {
                *p='\0';
                in.sin_port = htons(strToi(p+1));
        } else
                in.sin_port = 0;
        if (strEq(hostname, "DEFAULT"))
        {
                in.sin_addr.s_addr=0;
        } else
        {
                if ((in.sin_addr.s_addr = inet_addr (hostname)) == (unsigned int)-1)
                {
                        if (!(host = gethostbyname (hostname)))
                        {
                                if (p)  
                                        *p=':';
                                return NULL;
                        }
                        memcpy (&in.sin_addr, host->h_addr, host->h_length);
                }
        }
        if (p)
                *p=':';
        return &in;
}


EXPORT void logFromClient (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        char buf[MAX_SYSLOG]="";
        if (!con->logFromClient)
                return;
        strncpy (buf, s, MAX_SYSLOG-1);
        buf[MAX_SYSLOG-1]='\0';
        strStripEOL(buf);
        if (*buf)
                logt (("<- %s", buf));
}

EXPORT void logToClient (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        char buf[MAX_SYSLOG]="";
        if (!con->logToClient)
                return;
        strncpy (buf, s, MAX_SYSLOG-1);
        buf[MAX_SYSLOG-1]='\0';
        strStripEOL(buf);
        if (*buf)
                logt (("-> %s", buf));
}

EXPORT void logFromServer (char *host, char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        char buf[MAX_SYSLOG]="";
        if (!con->logFromServer)
                return;
        strncpy (buf, s, MAX_SYSLOG-1);
        buf[MAX_SYSLOG-1]='\0';
        strStripEOL(buf);
        if (*buf)
                logt (("<= [%s] %s", host, buf));
}

EXPORT void logToServer (char *host, char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        char buf[MAX_SYSLOG]="";
        if (!con->logToServer)
                return;
        strncpy (buf, s, MAX_SYSLOG-1);
        buf[MAX_SYSLOG-1]='\0';
        strStripEOL(buf);
        if (*buf)
                logt (("=> [%s] %s", host, buf));
}

EXPORT int writeClient (char *s, int len)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (con->logToClient)
        {
                char *p;
                for (p = s; p - s < len; )
                {
                        char *p2;
                        for (p2 = p; *p2 != '\n' && *p2 && p2-s < len; p2++) {} 
                        logt (("-> %.*s", (int)MIN(p2-p, MAX_SYSLOG-1), p));
                        p = p2+1;
                }
        }
        if (slaveClient)
        {
                strnStackAdd(slaveClient, s, len);
                return len;
        }
        if (con->statistics)
            {
                Stats->clientToBytes +=len;
                CS->clientToBytes+=len;
                ClientBytes += len;
            }
        return write (fileno(clientout), s, len);
}       

EXPORT int fwriteClient (char *s, int len)
/* [<][>][^][v][top][bottom][index][help] */
{
        if (con->logToClient)
        {
                char *p;
                for (p = s; p - s < len; )
                {
                        char *p2;
                        for (p2 = p; *p2 != '\n' && *p2 && p2-s < len; p2++) {} 
                        logt (("-> %.*s", (int)MIN(p2-p, MAX_SYSLOG-1), p));
                        p = p2+1;
                }
        }
        if (slaveClient)
        {
                strnStackAdd(slaveClient, s, len);
                return len;
        }
        if (con->statistics)
            {
                Stats->clientToBytes +=len;
                CS->clientToBytes+=len;
                ClientBytes += len;
            }
        return fwrite (s, len, 1, clientout);
}       

EXPORT bool emit (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        int i = 0;
        logToClient (s);
        if (con->statistics || slaveClient)
                i = strlen(s);
        if (con->statistics)
            {
                Stats->clientToBytes += i;
                CS->clientToBytes+=i;
                ClientBytes += i;
            }
        if (slaveClient)
        {
                strnStackAdd(slaveClient, s, i);
                return TRUE;
        }
        return fputs (s, clientout)!=EOF;
}

EXPORT int emitrn (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        logToClient (s);
        if (con->statistics || slaveClient)
            {
                int i = strlen(s);
                if (slaveClient)
                    {
                        strnStackAdd(slaveClient, s, i);
                        strnStackAdd(slaveClient, "\r\n", 2);
                        return TRUE;
                    }
                fputs (s, clientout);
                Stats->clientToBytes += i + 2;
                CS->clientToBytes += i + 2;
                ClientBytes += i + 2;
            }
        else
            fputs (s, clientout);
        return fputs ("\r\n", clientout)!=EOF;
}

EXPORT int emitf (char *fmt, ...) EXP_(GNUC_EXT(__attribute__ ((format (printf, 1, 2)))))
/* [<][>][^][v][top][bottom][index][help] */
{
        int i;
        va_list ap;
        va_start(ap, fmt);
        if (slaveClient)
        {
                char buf[MAX_BFR];
                i = vsnprintf(buf, sizeof buf, fmt, ap);
                strnStackAdd(slaveClient, buf, i);
        }
        else
            {
                if (con->logToClient)
                    {
                        char buf[MAX_SYSLOG]; /* should be more than enough */
                        vsnprintf(buf, MAX_SYSLOG-1, fmt, ap);
                        logToClient (buf);
                    }
                i=vfprintf(clientout, fmt, ap);
            }
        va_end(ap);
        if (con->statistics)
            {
                Stats->clientToBytes += i;
                CS->clientToBytes+=i;
                ClientBytes += i;
            }
        return i;
}

EXPORT int Cemitf (char *fmt, ...) EXP_(GNUC_EXT(__attribute__ ((format (printf, 1, 2)))))
/* [<][>][^][v][top][bottom][index][help] */
{
        int i;
        struct server_cfg *scfg;
        va_list ap;
        va_start(ap, fmt);
        scfg=attachServer(CurrentScfg);
        if (!scfg)
                return 0;
        if (con->logToServer)
        {
                char buf[MAX_SYSLOG];
                vsnprintf(buf, MAX_SYSLOG-1, fmt, ap);
                logToServer (scfg->host, buf);
        }
        i=vfprintf(scfg->fh, fmt, ap);
        va_end(ap);
        scfg->share->bytes_to+=i;
        if (con->statistics)
            {
                CS->serverToBytes+=i;
                Stats->serverToBytes += i;
            }
        return i;
}

EXPORT int Cfemitf (struct server_cfg *scfg, char *fmt, ...) EXP_(GNUC_EXT(__attribute__ ((format (printf, 2, 3)))))
/* [<][>][^][v][top][bottom][index][help] */
{
        int i;
        va_list ap;
        va_start(ap, fmt);
        scfg=attachServer(scfg);
        if (!scfg)
                return 0;
        if (con->logToServer)
        {
                char buf[MAX_SYSLOG];
                vsnprintf(buf, MAX_SYSLOG-1, fmt, ap);
                logToServer (scfg->host, buf);
        }
        i=vfprintf(scfg->fh, fmt, ap);
        va_end(ap);
        scfg->share->bytes_to+=i;
        if (con->statistics)
            {
                Stats->serverToBytes += i;
                CS->serverToBytes+=i;
            }
        return i;
}

EXPORT int flush ()
/* [<][>][^][v][top][bottom][index][help] */
{
        return fflush (clientout);
}

EXPORT int Cfflush (struct server_cfg *cf)
/* [<][>][^][v][top][bottom][index][help] */
{
        return fflush(cf->fh);
}

EXPORT int Cflush ()
/* [<][>][^][v][top][bottom][index][help] */
{
        return Cfflush(CurrentScfg);
}

EXPORT int Cfemit (struct server_cfg *cf, char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        int i;
        cf=attachServer(cf);
        if (!cf)
                return 0;
        logToServer (cf->host, s);
        i = strlen(s);
        cf->share->bytes_to += i;
        if (con->statistics)
            {
                Stats->serverToBytes += i;
                CS->serverToBytes+=i;
            }
        return fwrite(s, 1, i, cf->fh);
}

EXPORT int Cfemitrn (struct server_cfg *cf, char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        return Cfemitf(cf, "%s\r\n", s);
}

EXPORT int Cemit (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        return Cfemit (CurrentScfg, s);
}

EXPORT int Cemitrn (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        return Cfemitrn (CurrentScfg, s);
}

EXPORT int Cget (char *s, int n)
/* [<][>][^][v][top][bottom][index][help] */
{
        return Cfget (CurrentScfg, s, n);
}

EXPORT int Get (char *s, int n)
/* [<][>][^][v][top][bottom][index][help] */
{
        char *p=fgets (s, n, clientin);
        int len;
        if (!p)
                return 0;
        logFromClient (s);
        if (con->statistics)
        {
                len = strlen(p);
                Stats->clientFromBytes += len;
                CS->clientFromBytes +=len;
        }
        else
                len = 1;
        return len;
}

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