/*
 * NAME:
 *	netio - interface stdio to read_net and write_net
 *
 * SYNOPSIS:
 *	#include <netio.h>
 *
 *      int     net_setvbuf(struct net_iob *p,
 *				char *buf, int  type, size_t  size);
 *      struct net_iob *        net_getiob(int  fd, int force);
 *      struct net_iob *        net_fgetiobt(FILE *fp, int (*test)(int));
 *      struct net_iob *        net_fgetiob(FILE *fp);
 *      int     net_filbuf(struct net_iob *p);
 *      int     net_flsbuf(int  c, struct net_iob *p);
 *      int     net_fflush(FILE *fp);
 *      int     net_fclose(FILE *fp);
 *      int     net_fprintf(FILE *fp, const char *fmt, ...);
 *      int     net_fgetc(FILE *fp);
 *      int     net_fputc(int  c, FILE *fp);
 *      char *  net_fgets(char *buf, int  nbytes, FILE *fp);
 *      int     net_read(int fd, char *buf, int nbytes);
 *      int     net_write(int fd, char *buf, int nbytes);
 *
 *
 * DESCRIPTION:
 *	These routines simply allow encryption hooks to be used with
 *	applications that use stdio.
 *
 *	'netio.h' contains macro definitions which override fprintf(),
 *	vfprintf(), fgets(), fgetc(), getc(), putc() and fputc().
 *
 *	Each of the net_*() functions, checks whether the FILE * is
 *	associated with an SSL connection and if not calls the normal
 *	stdio function, otherwise it implements the appropriate
 *	buffered I/O behaviour with net_filbuf() and net_flsbuf()
 *	calling read_net() and write_net() respectively.
 *	
 * SEE ALSO:
 *	rwnet.c
 *
 * BUGS:
 *	The implementation of net_vfprintf() is worth noting...
 *	We simply sprintf() into a big buffer, and if the result is
 *	too big we abort().  Ugly, but very portable. 
 *	 
 * AUTHOR:
 *	Simon J. Gerraty <sjg@quick.com.au>
 *	
 *	This is a re-work of a stdio to socket implementation for DOS
 *	I did back in the late 1980s.
 */
/*
 *      @(#)Copyright (c) 1989,1996 Simon J. Gerraty.
 *      
 *      This is free software.  It comes with NO WARRANTY.
 *      Permission to use, modify and distribute this source code 
 *      is granted subject to the following conditions.
 *      1/ that the above copyright notice and this notice 
 *      are preserved in all copies and that due credit be given 
 *      to the author.  
 *      2/ that any changes to this code are clearly commented 
 *      as such so that the author does not get blamed for bugs 
 *      other than his own.
 *      
 *      Please send copies of changes and bug-fixes to:
 *      sjg@quick.com.au
 */
#ifndef lint
static char RCSid[] = "$Id: netio.c,v 1.6 1997/02/08 07:13:00 sjg Exp $";
#endif

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <syslog.h>
#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <assert.h>

#ifdef USE_SSL
# include "ssl.h"
# include "sslfd.h"
#endif

#ifndef NUMFDS
# define NUMFDS 64
#endif

#ifdef HAVE_BCOPY
# define BCOPY bcopy
#else
# define BCOPY(s,d,n) memcpy(d,s,n)
#endif


/*
 * Sadly, _iob internals are not as standard as they once were
 */
struct net_iob {
	int   _cnt;				/* space left */
	unsigned char *_ptr;
	unsigned char *_base;
	int   _bufsiz;
	int   _flag;
	int   _file;
};

#ifndef _IOREAD
# define _IOFBF  0
# define _IOREAD 01
# define _IOWRT  02
# ifndef _IONBF
#   define _IONBF  04
# endif
# define _IOMYBUF        010
# define _IOEOF  020
# define _IOERR  040
# define _IOSTRG 0100
# ifndef _IOLBF
#   define _IOLBF  0200
# endif
# define _IORW   0400
#endif

#define net_getc(p) (--(p)->_cnt < 0 ? net_filbuf(p) : (int)*(p)->_ptr++)
#define net_putc(x, p) (--(p)->_cnt < 0 ? \
			net_flsbuf((unsigned char) (x), (p)) \
                                : (int)(*(p)->_ptr++ = (x)))

#ifndef NEW
# define NEW(x) (x *)malloc(sizeof(x))
#endif

#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
# define MAX(a, b) (((a) < (b)) ? (b) : (a))
#endif

static struct net_iob *net_iobs[NUMFDS];


static int
_fflush(p)
	struct net_iob *p;
{
	int cnt = -1;
	int n;
	char *cp;
		
	if (!p)
		return 0;

	if ((p->_flag & (_IORW | _IOWRT)) == 0)
		return EOF;
	
	if (p->_file >= 0 && !(p->_flag & (_IOREAD | _IOERR | _IOEOF))) {
		if ((cp = p->_base) == NULL)
			return 0;
		
		n = p->_ptr - p->_base;
		p->_ptr = p->_base;
		p->_cnt = (p->_flag & (_IONBF|_IOLBF)) ? 0 : p->_bufsiz;

		for ( ; n > 0; n -= cnt, cp += cnt) {
			if ((cnt = write_net(p->_file, cp, n)) < 0) {
				p->_flag |= _IOERR;
				return EOF;
			}
		}
	}
	return 0;
}


int
net_setvbuf(p, buf, type, size)
	struct net_iob   *p;
	char   *buf;
	int     type;
	size_t  size;
{
	if (!p)
		return -1;
	_fflush(p);
	if ((p->_flag & _IOMYBUF) && p->_base != NULL)
		(void) free(p->_base);
	p->_flag &= ~(_IOMYBUF | _IONBF | _IOFBF | _IOLBF);
	switch (type) {
	case _IONBF:
		size = 0;
		buf = NULL;
		break;
	case _IOLBF:
	case _IOFBF:
		if (size == 0)
			size = BUFSIZ;
		if (buf == NULL) {
			buf = (unsigned char *) malloc(size);
			if (buf == NULL)
				return -1;
			p->_flag |= _IOMYBUF;
		}
		break;
	default:
		return -1;
	}
	p->_flag |= type;
	p->_base = p->_ptr = buf;
	p->_cnt = 0;
	p->_bufsiz = size;
	return 0;
}

struct net_iob *
net_getiob(fd, force)
	int fd;
	int force;
{
	static int once = 1;
	struct net_iob *p = 0;
	int i;
	
	if (once) {
		memset((char *)net_iobs, 0, sizeof(net_iobs));
		once = 0;
	}
	if (fd >= 0 && fd < NUMFDS) {
		if ((p = net_iobs[fd]) == 0) {
			if (!force)
				return 0;
			
			p = net_iobs[fd] = NEW(struct net_iob);
			if (p) {
				p->_file = -1;
				p->_base = 0;
				p->_bufsiz = 0;
				p->_flag = _IOMYBUF;
			}
		}
		if (p && p->_file < 0) {
			p->_cnt = 0;
			p->_file = fd;
			p->_flag &= _IOMYBUF;
			p->_ptr = p->_base;
		}
	}
	return p;
}

struct net_iob *
net_fgetiobt(fp, test)
	FILE *fp;
	int (*test)();
{
	int fd = (fp) ? fileno(fp) : -1;
	int force = (test && test(fd));

	return net_getiob(fd, force);
}

struct net_iob *
net_fgetiob(fp)
	FILE *fp;
{
#ifdef USE_SSL
	return net_fgetiobt(fp, get_sslcon);
#else
	return net_fgetiobt(fp, 0);
#endif
}


int
net_filbuf(p)
	struct net_iob *p;
{
	static unsigned char ch;
	register int i = -1;

	if (!p)
		return EOF;
	
	if (p->_file >= 0 && !(p->_flag & (_IOEOF | _IOERR))) {
		if (p->_base == NULL) {
			if (p->_flag & _IONBF) {
				p->_cnt = 0;
				if (read_net(p->_file, &ch, 1) < 0) {
					p->_flag |= (_IOEOF | _IOERR);
					return EOF;
				}
				p->_flag &= ~(_IOEOF | _IOERR);
				return ch;
			} else
				i = net_setvbuf(p, NULL, _IOFBF, 0);
		} else
			i = 0;
	}
	if (i < 0)
		return EOF;

	if ((p->_cnt = read_net(p->_file, p->_base, p->_bufsiz)) <= 0) {
		p->_flag |= (_IOEOF | _IOERR);
		return EOF;
	}
	p->_ptr = p->_base;
	return net_getc(p);
}

int
net_flsbuf(c, p)
	int     c;
	struct net_iob 	*p;
{
	static unsigned char ch;
	register int done = 0;
	register int i = -1;
	int n;

	if (!p)
		return EOF;

	p->_flag |= _IOWRT;
	
	if (p->_file >= 0 && !(p->_flag & (_IOERR | _IOEOF))) {
		if (p->_base == NULL) {
			if (p->_flag & _IONBF) {
				p->_cnt = 0;
				ch = (unsigned char) c;
				if (write_net(p->_file, &ch, 1) < 0) {
					p->_flag |= (_IOEOF | _IOERR);
					return EOF;
				}
				return ch;
			} else 
				i = net_setvbuf(p, NULL, _IOFBF, 0);
		} else
			i = 0;
	}
	if (i < 0)
		return EOF;

	n = p->_ptr - p->_base;
	if (n >= p->_bufsiz) {
		if (_fflush(p) < 0)
			return EOF;
		n = 0;
	}
	if (n == 0)
		p->_cnt = p->_bufsiz;
	c = net_putc(c, p);
	if (++n == p->_bufsiz || (p->_flag & _IOLBF && c == '\n'))
		if (_fflush(p) < 0)
			return EOF;
	return c;
}

/*
 * PUBLIC functions
 */

int
net_fflush(fp)
	FILE *fp;
{
	struct net_iob *p = net_fgetiob(fp);
	
	if (p)
		return _fflush(p);
	return fflush(fp);
}


int
net_flush(fd)
	int fd;
{
	struct net_iob *p = net_getiob(fd, 0);
	
	if (p)
		return _fflush(p);
	switch(fd) {
	case 1: return fflush(stdout);
	case 2: return fflush(stderr);
	}
	return 0;
}


int
net_fclose(fp)
	FILE *fp;
{
	int fd = (fp) ? fileno(fp) : -1;
	struct net_iob *p = 0;

	if (p = net_getiob(fd, 0)) {
		_fflush(p);
#ifdef USE_SSL
		ssl_close(fd);
#endif
		p->_file = -1;
	}
	return fclose(fp);
}

int
net_vfprintf(fp, fmt, ap)
        FILE *fp;
        const char *fmt;
        va_list ap;
{
	struct net_iob *p = net_fgetiob(fp);
        int ret, n;
	static char buf[8192];

	if (!p)
		return vfprintf(fp, fmt, ap);

	/*
	 * This is the simplest way to do it
	 */
	p->_flag |= _IOWRT;
	vsprintf(buf, fmt, ap);
	n = strlen(buf);
	
#ifdef ASSERT
	ASSERT(n < sizeof(buf));
#else
	if (n >= sizeof(buf)) {
		syslog(LOG_ERR, "assertion failed %d < %d", n, sizeof(buf));
		abort();
	}
#endif
	for (ret = 0; ret < n; ret++)
		/*
		 * Interesting, using the macro here trashed the stack!
		 */
		if (net_fputc(buf[ret], fp) == EOF)
			return EOF;
	return ret;
}


int
#if __STDC__
net_fprintf(FILE *fp, const char *fmt, ...)
#else
net_fprintf(fp, fmt, va_alist)
        FILE *fp;
        char *fmt;
        va_dcl
#endif
{
        int ret;
        va_list ap;
 
#if __STDC__
        va_start(ap, fmt);
#else
        va_start(ap);
#endif
        ret = net_vfprintf(fp, fmt, ap);
        va_end(ap);
        return (ret);
}



int
net_fgetc(fp)
	FILE *fp;
{
	struct net_iob *p = net_fgetiob(fp);
	
	if (p)
		return net_getc(p);
	return getc(fp);
}


int
net_ungetc(c, fp)
	int c;
	FILE *fp;
{
	struct net_iob *p;

	if (c == EOF)
		return c;
	
	p = net_fgetiob(fp);
	
	if (!p)
		return ungetc(c, fp);
	if (p->_base > 0) {
		if (p->_ptr >= p->_base) {
			*--p->_ptr = c;
			p->_cnt++;
			if (p->_ptr < p->_base)
				p->_ptr = p->_base;
		}
	}
	return c;
}



int
net_fputc(c, fp)
	int c;
	FILE *fp;
{
	struct net_iob *p = net_fgetiob(fp);

	if (p) {
		p->_flag |= _IOWRT;
		return net_putc(c, p);
	}
	return putc(c, fp);
}

char *
net_fgets(buf, nbytes, fp)
	char *buf;
	int nbytes;
	FILE *fp;
{
	struct net_iob *p = net_fgetiob(fp);
	char *s = buf;
	int c;
	
	if (buf == 0 || nbytes <= 0 || fp == 0)
		return 0;

	if (!p)
		return fgets(buf, nbytes, fp);
	
	nbytes--;
	while (nbytes) {
		c = net_getc(p);
		if (c == EOF) {
			if (s == buf)
				return 0;
			break;
		}
		*s++ = c;
		if (c == '\n')
			break;
	}
	*s = 0;
	return buf;
}

/*
 * Ok, this is perhaps getting silly,
 * but these provide read(2) and write(2) semantics without breaking netio.
 * Be sure to call net_getiob(fd, 1) to ensure a net_iob struct is
 * setup before * calling any of getc(), fgetc(), fgets() etc. 
 */

/*
 * If we do not have a net_iob set up, we call read_net(),
 * or it is empty, we call net_getc(),net_ungetc() to populate it, 
 * then we grab up to nbytes from the net_iob.
 */

int
net_read(fd, buf, nbytes)
	int fd;
	unsigned char *buf;
	int nbytes;
{
	struct net_iob *p = net_getiob(fd, 0);
	int n;
	
	if (!p)
		return read_net(fd, buf, nbytes);
	if (p->_cnt <= 0 || p->_ptr == p->_base) { /* empty */
		n = net_getc(p);
		net_ungetc(n, p);
	}
	n = MIN(nbytes, p->_cnt);
	n = MIN(n, p->_bufsiz);
	memcpy(buf, p->_ptr, n);
	p->_cnt -= n;
	if (p->_cnt <= 0) {
		p->_cnt = 0;
		p->_ptr = p->_base;
	} else {
		p->_ptr += n;
	}
	return n;
}

/*
 * if we have a net_iob, flush it, then return write_net()
 */
int
net_write(fd, buf, nbytes)
	int fd;
	unsigned char *buf;
	int nbytes;
{
	struct net_iob *p = net_getiob(fd, 0);
	int n;
	
	if (p)
		_fflush(p);
	else {
		switch(fd) {
		case 1: fflush(stdout); break;
		case 2: fflush(stderr); break;
		}
	}
	return write_net(fd, buf, nbytes);
}
