/*
 * NAME
 *   NNTPStream.c
 * COPYRIGHT
 *   Skim - Off-line news reading package optimized for slow lines.
 *   Copyright (C) 1995  Rene W.J. Pijlman
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 * 
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * VERSION
 *   Skim version 0.6.
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>

#include "VarBuf.h"
#include "Skim.h"


FILE_ID("/home/rene/sys/CVS_MasterSourceRepository/skim/NNTPStream.c,v 1.19 1995/08/15 19:33:36 rpijlman Exp");

/*
 * Return a socket with a TCP/IP connection to the specified service (port)
 * on the specified host.
 */
static int TCP_ConnectionOpen( char * HostName, char * ServiceName )
{
    int Status;
    struct protoent * Protocol;
    struct hostent * Host;
    struct servent * Service;
    struct sockaddr_in SocketAddress;
    int Socket;

    Protocol = getprotobyname( "tcp" );
    if ( Protocol != NULL )
    {
	Host = gethostbyname( HostName );
	if ( Host != NULL )
	{
	    Service = getservbyname( (char *)ServiceName, "tcp" );
	    if ( Service != NULL )
	    {
		Socket = socket( AF_INET, SOCK_STREAM, Protocol->p_proto );
		if ( Socket != NotASocket )
		{
		    memset( &SocketAddress, 0, sizeof SocketAddress );
		    SocketAddress.sin_family = AF_INET;
		    memmove( (char *)&SocketAddress.sin_addr,
			     Host->h_addr, Host->h_length );
		    SocketAddress.sin_port = Service->s_port;

		    Status = connect( Socket,
				      (struct sockaddr *)&SocketAddress, 
				      sizeof( SocketAddress ) );
		    if ( Status == -1 )
		    {
		        perror( "connect" );
		        exit( EXIT_FAILURE );
		    }
		}
		else
		{
		    perror( "socket" );
		    exit( EXIT_FAILURE );
		}
	    }
	    else
	    {
		fprintf( stderr,
		     "Cannot translate service name '%s' to port number\n",
		     ServiceName );
	        exit( EXIT_FAILURE );
	    }
	}
	else
	{
	    herror( "gethostbyname" );
	    exit( EXIT_FAILURE );
	}
    }
    else
    {
       fprintf( stderr, "Cannot translate protocol name 'tcp' to number\n" );
       exit( EXIT_FAILURE );
    }

    return Socket;
}

static void TCP_ConnectionClose( int Socket )
{
    close( Socket );
}

/* Return a bidirectional standard I/O stream to the NNTP news server. */
FILE * NNTPStreamOpen( void )
{
    int Socket;
    char * NNTP_Server;
    FILE * NewsServer;

    if ( ( NNTP_Server = getenv( "NNTPSERVER" ) ) == NULL )
    {
        fprintf( stderr, "Environment variable NNTPSERVER is not set\n" );
        exit( EXIT_FAILURE );
    }

    Socket = TCP_ConnectionOpen( NNTP_Server, "nntp" );

    NewsServer = fdopen( Socket, "r+" );
    if ( NewsServer == NULL )
    {
        perror( "fdopen" );
        exit( EXIT_FAILURE );
    }

    CheckStatusResponse( NewsServer, NULL, "2" );

    return NewsServer;
}

void NNTPStreamClose( FILE * NewsServer )
{
    if ( NewsServer != NULL )
    {
	if ( fprintf( NewsServer, "quit\r\n" ) == EOF ||
	     fflush( NewsServer ) == EOF )
	{
	    fprintf( stderr, "Error writing to NewsServer.\n" );
	    exit( EXIT_FAILURE );
	}

	TCP_ConnectionClose( fileno( NewsServer ) );
    }
}


void CheckStatusResponse(
    FILE * NewsServer,
    const char * Group,
    const char * MustStartWithStatusResponse )
{
    VarBuf StatusResponse = VBCreate();

    VBReadLine( StatusResponse, NewsServer, WITHOUT_NEWLINE );

    if ( strncmp( VBAsString(StatusResponse), MustStartWithStatusResponse,
                  strlen(MustStartWithStatusResponse) ) )
    {
	if ( Group != NULL )
	{
	    fprintf( stderr, "On group %s:\n", Group );
	}

	fprintf( stderr, "Error from NNTP server:\n%s\n",
	         VBAsString(StatusResponse) ); 
	exit( EXIT_FAILURE );
    }

    VBDestroy( StatusResponse );
}


/*
 * Read a text response from the NNTP server, convert it from RFC-977 format
 * to UNIX text file format, and write it to `ResponseFile'. The conversions 
 * which are applied are:
 *
 *   - CR-LF pairs are converted to LF.
 *   - Double periods on the first character of a line are collapsed to a 
 *     single period.
 *   - The terminating line with only a period is removed.
 */
void TextResponseToFile( FILE * NewsServer, FILE * ResponseFile )
{
    Boolean Finished = False;
    VarBuf LineOfTextResponse = VBCreate();

    while ( !Finished &&
            VBReadLine( LineOfTextResponse, NewsServer, WITH_NEWLINE ) )
    {
	if ( !strcmp( VBAsString(LineOfTextResponse), ".\r\n" ) )
	{
	    Finished = True;
	}
	else if ( !strncmp( VBAsString(LineOfTextResponse), "..", 2 ) )
	{
	    VBShiftLeft( LineOfTextResponse, 1 );
	}

	if ( !Finished )
	{
	    if ( VBSize(LineOfTextResponse) >= 2 &&
		 !strcmp( VBAsString(LineOfTextResponse) +
		          VBSize(LineOfTextResponse) - 2, "\r\n" ) )
	    {
		VBTruncate(LineOfTextResponse, VBSize(LineOfTextResponse) - 2);
	    }
	    else
	    {
	        fprintf( stderr,
	                 "Line from news server not terminated with CR-NL\n" );
	        exit( EXIT_FAILURE );
	    }

	    fprintf( ResponseFile, "%s\n", VBAsString(LineOfTextResponse) );
	}

	VBReset( LineOfTextResponse );
    }

    VBDestroy(LineOfTextResponse);
}
