/*
 * Copyright (c) 1998,1999,2000  Kazushi (Jam) Marukawa
 * All rights of my changes are reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice in the documentation and/or other materials provided with
 *    the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* $Orig-Id: util.c,v 1.22 1997/07/23 18:35:18 agulbra Exp $ */
/*

Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
22646949.

Use, modification and distribution is allowed without limitation,
warranty, or liability of any kind. */

/*
This code is derived from only leafnode+ by using same structure
of Cornelius's leafnode to prepare for merging with Cornelius's code.
*/

#ifdef SOCKS
#include <socks.h>
#endif

#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <dirent.h>

#include "leafnode.h"

FILE *nntpin = NULL;
FILE *nntpout = NULL;

static jmp_buf timeout;

char last_command[1025];
char lineout[1025];
int authenticated;
static int authenticate(void);

static void timer(int sig)
{
    longjmp(timeout, 1);
    exit(sig);
}

static void sigint(int sig)
{
    fprintf(stderr, "I got SIGINT.  I'm closing nntp connection.\n");
    nntpclose();
}

static void sigpipe(int sig)
{
    fprintf(stderr, "I got SIGPIPE.  I'm closing nntp connection.\n");
    nntpclose();
}

/*
05/26/97 - T. Sweeney - Send a string out, keeping a copy in reserve.
*/
void putaline(void)
{
    strcpy(last_command, lineout);
    fprintf(nntpout, "%s", lineout);
    fflush(nntpout);
    if (verbose > 5) {
	printf("%s", lineout);
    }
}

/* originally from Tim Sweeney
 *
 * Returns TRUE if authentication succeeds, FALSE if it does not.
 *
 *  Precondition: servers->username != 0.
 */
static int authenticate(void)
{
    int reply;

    fprintf(nntpout, "authinfo user %s\r\n", servers->username);
    fflush(nntpout);

    reply = nntpreply();
    if (reply == 281) {
	return TRUE;
    } else if (reply != 381) {
	mysyslog(LOG_ERR, "Username rejected: %03d", reply);
	return FALSE;
    }

    if (servers->password == NULL) {
	mysyslog(LOG_ERR, "Password needed for authentication");
	return FALSE;
    }
    fprintf(nntpout, "authinfo pass %s\r\n", servers->password);
    fflush(nntpout);

    reply = nntpreply();

    if ( reply != 281) {
	mysyslog(LOG_ERR, "Password failed: %03d", reply);
	return FALSE;
    }
    return TRUE;
}

/*
 * decode an NNTP reply number
 * reads a line from the server and returns an integer
 *
 * 498 is used to mean "protocol error", like smail
 *
 * the text returned is discarded
 *
 * from Tim Sweeney: retry in case of authinfo failure.
 */
int nntpreply(void)
{
    char* response;
    int r = 0;
    int c = 1;

    while (c) {
	response=getaline(nntpin);
	if (!response) {
	    mysyslog(LOG_ERR, "NNTP server went away");
	    return 498;
	}
	if (strlen(response) > 2 &&
	    isdigit(response[0]) && isdigit(response[1]) &&
	    isdigit(response[2]) &&
	    (response[3] == ' ' || response[3] == '\0' ||
	     response[3] == '-')) {
	    int rl;
	    rl = atoi(response);
	    if (r > 0 && r != rl)
		r = 498;    /* protocol error */
	    else
		r = rl;
	    c = (response[3] == '-');
	} else {
	    c = 0;
	    r = 498;	/* protocol error */
	}
    }

    return r;
}

extern int h_errno;
extern struct state _res;

#ifndef INET6
#define incopy(a)       *((struct in_addr*)a)
#endif

/*
 * Connect to a server.
 * Return TRUE if succeeds.  Otherwise, return FALSE.
 */
int nntpconnect(void)
{
#ifdef INET6
    struct addrinfo hints, *res0, *res;
    char serv[NI_MAXSERV];
    int gai;
#else
    struct hostent* hp;
    struct servent* sp;
    struct servent sp_def;
    struct sockaddr_in s_in;
#endif
    int sock;
    int i;

#ifdef INET6
    if (servers->port != 0)
	snprintf(serv, sizeof(serv), "%d", servers->port);
    else
	strcpy(serv, "nntp");
#else
    memset((void*)&s_in, 0, sizeof(s_in));
    if (servers->port == 0) {
	sp = getservbyname("nntp", "tcp");
	if (sp == NULL) {
	    mysyslog(LOG_ERR, "Unable to find service NNTP");
	    return FALSE;
	}
    } else {
	sp = &sp_def;
	sp->s_port = htons(servers->port);
    }
#endif

    /* Fetch the ip addresses of the given host. */
#ifdef INET6
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    res = res0 = NULL;
    gai = getaddrinfo(servers->viahost ? servers->viahost : servers->servername,
		      serv, &hints, &res0);
    if (gai || !res0){
	mysyslog(LOG_ERR, "getaddrinfo() failed: %s", gai_strerror(gai));
	return FALSE;
    }
#else
    if (servers->viahost)
	hp = gethostbyname(servers->viahost);
    else
	hp = gethostbyname(servers->servername);
#endif

#ifdef INET6
    if (res0) {
#else
    if (hp) {
#endif
	/* Execute preconnect */
	if (servers->preconnect) {
	    uid_t euid = geteuid();
	    gid_t egid = getegid();
	    int ret;
	    if (setreuid(-1, 0) != 0)
		perror("setreuid");
	    if (setregid(-1, 0) != 0)
		perror("setregid");
	    ret = system(servers->preconnect);
	    if (setregid(-1, egid) != 0)
		perror("setregid");
	    if (setreuid(-1, euid) != 0)
		perror("setreuid");
	    if (ret != 0) {
		mysyslog(LOG_ERR, "System(%s) failed: %s",
			 servers->preconnect, strerror(errno));
		return FALSE;
	    }
	}

	/* Try to make connection to each of the addresses in turn. */
#ifdef INET6
	for (res=res0; res; res=res->ai_next) {
#else
	for (i = 0; (int*)(hp->h_addr_list)[i]; i++) {
#endif
#ifdef INET6
	    sock = socket(res->ai_family, res->ai_socktype, 0);
#else
	    s_in.sin_family = hp->h_addrtype;
	    s_in.sin_port = sp->s_port;
	    s_in.sin_addr = incopy(hp->h_addr_list[i]);

	    sock = socket(AF_INET, SOCK_STREAM, 0);
#endif
	    if (sock < 0)
		break;

	    if (setjmp(timeout) != 0) {
		(void)close(sock);
		continue;
	    }

	    (void)signal(SIGINT, sigint);
	    (void)signal(SIGPIPE, sigpipe);
	    (void)signal(SIGALRM, timer);
	    (void)alarm(servers ? servers->timeout_open : TIMEOUT_OPEN);
#ifdef INET6
	    if (connect(sock, res->ai_addr, res->ai_addrlen) < 0)
#else
	    if (connect(sock, (struct sockaddr*)&s_in, sizeof(s_in)) < 0)
#endif
		break;
	    (void)alarm(0U);

	    nntpout = fdopen(sock, "w");
	    if (nntpout == NULL)
		break;

	    nntpin  = fdopen(dup(sock), "r");
	    if (nntpin == NULL)
		break;

	    switch(nntpreply()) {
	    case 200:
	    case 201:
		return TRUE;
	    }
	    shutdown(fileno(nntpout), 0);
	}/* end of IP-addresses for loop */
#ifdef INET6
	if (res0)
	    freeaddrinfo(res0);
#endif
    }
    return FALSE;
}/* end of connect function */

/*
 * connect to a server with authentification if it is needed.
 * login server with username and password if those are specified.
 */
int nntpconnectauth(void)
{
    if (nntpconnect()) {
	if (servers->username && !authenticated) { /* need to authenticate */
	    authenticated = TRUE;
	    if (authenticate()) {
		return TRUE;
	    } else {
		nntpclose();
	    }
	} else {
	    return TRUE;
	}
    }
    return FALSE;
}

/*
 * Close nntp connection.
 * - close nntpin and set NULL nntpin because it is accessed by only sfgets()
 *   and guarded by sfgets() also.
 * - don't close nntpout because it may be accessed by fprintf().
 */
void nntpclose(void)
{
    int fd = -1;

    if (nntpout) {
	fd = fileno(nntpout);
    }
    if (nntpin) {
	fclose(nntpin);
    }
    if (fd >= 0)
	shutdown(fd, 0);
    nntpin = NULL;

    authenticated = FALSE;

    nntpout = fopen("/dev/null", "w");
    nntpin  = fopen("/dev/null", "r");
}

int nntpreconnect(void)
{
    int fd = -1;

    if (nntpout) {
	fd = fileno(nntpout);
	fclose(nntpout);
    }
    if (nntpin) {
	fclose(nntpin);
    }
    if (fd >= 0)
	shutdown(fd, 0);
    nntpout = nntpin = NULL;

    authenticated = FALSE;

    return nntpconnectauth();
}

/*
 * read size bytes into buf from file fp,
 * with four hours timeout
 * return a pointer to buf, or NULL in case of error
 */
static char* sfgets(char* buf, size_t size, FILE* fp)
{
    char* p;

    if (fp == NULL)
	return NULL;

    if (setjmp(timeout)) {
	nntpclose();
	return(NULL);
    }

    (void) signal(SIGALRM, timer);
    (void) alarm(servers ? servers->timeout_read : TIMEOUT_READ);
    p = fgets(buf, size, fp);
    if (errno == EINTR)
	errno = ETIMEDOUT;
    (void) alarm(0U);
    return p;
}


/*
 * Lars Wirzenius: read a line into memory, with no max length
 * return a pointer to the line, or NULL in case of error
 *
 * strip \r at EOL
 */
char* getaline(FILE* f)
{
    static char* buf;       /* buffer for line */
    static size_t size;     /* size of buffer */
    size_t len;             /* # of chars stored into buf before '\0' */
    char* p;

    len = 0;
    if (!size)
	size = 256;
    if (!buf)
	buf = critmalloc(size, "reading line");

    while ((p = sfgets(buf + len, size - len, f)) != NULL) {
	len += strlen(buf + len);
        if (len > 0 && buf[len-1] == '\n')
	    break;          /* the whole line has been read */

	if (len + 5 > size) {
	    size += size + 100;
	    buf = critrealloc(buf, size, "reading line");
	}
    }

    if (len == 0)
	return NULL;

    if (len && (buf[len - 1] == '\n')) { /* go back on top of the newline */
	--len;
	if (len && (buf[len - 1] == '\r')) /* also delete CR */
	    --len;
    }

    buf[len] = '\0';        /* unconditionally terminate string,
                               possibly overwriting newline */

    if (verbose > 5) {
	printf("%s\n", buf);
    }

    return buf;
}


syntax highlighted by Code2HTML, v. 0.9.1