/*	tcp.c
 *	V1.0			29-Sep-1996	IfN/Mey
 *
 *	Copyright E. Meyer <meyer@ifn.ing.tu-bs.de>
 *+
 * Samba VMS TCP/IP routines.
 *-
 */

#define const
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#if __DECC_VER < 50200000  ||  __VMS_VER < 70000000
#include <unixio.h>
#endif
#include <string.h>
#include <errno.h>
#include <socket.h>
#include <in.h>
#include <ioctl.h>

#include <starlet.h>
#include <iodef.h>
#include <lib$routines.h>
#include <descrip.h>

/* SAMBA debugging */
extern int DEBUGLEVEL;
#define DEBUG(level,body) ((DEBUGLEVEL>=(level))?(Debug1 body):0)
int Debug1();

struct itm_list_2 {
	unsigned short len;
	unsigned short code;
	void *bufadr;
};

struct itm_list_3 {
	unsigned short len;
	unsigned short code;
	void *bufadr;
	short int *retadr;
};

/*
 * We support UCX and SOCKETSHR here.
 */ 

/*
 * get_tcpip_package
 *
 * Since the SOCKETSHR V0.9 does not support setsockopt()
 * and connection to a server socket, we have to implement the different
 * code here. The how-to is from NETLIB V2. When SOCKETSHR eventually
 * supports NETLIB V2, the following routines will become obsolete.
 *
 * We support 3 different interfaces here: UCX, CMUIP and the SRI qio
 * interface (MultiNet, Pathway, TCPware).
 */
#define TCPIP_UCX 1
#define TCPIP_CMU 2
#define TCPIP_SRI 3	/* if not one of the above */

int get_tcpip_package()
{
	char *p;

	p = getenv("SOCKETSHR");
	if (p != NULL) {
		if (strstr(p,"NETLIB")) {
			p = getenv("NETLIB_SHR");
		}
		if (p != NULL) {
			DEBUG(3,("SOCKETSHR Package logical: %s\n",p));
			if (strstr(p,"UCX")) return(TCPIP_UCX);
			else if (strstr(p,"CMU")) return(TCPIP_CMU);
			else return(TCPIP_SRI);
		}
	}
	if (p == NULL) {
		p = getenv("UCX$DEVICE");
		if (p != NULL) return(TCPIP_UCX);	/* Native UCX */
	}
	if (p == NULL) return(-1);
}

#ifndef SOCKETSHR
#ifdef __DECC
#define get_sdc decc$get_sdc
#else
#define get_sdc vaxc$get_sdc
#endif
#endif

/*
 * UCX definitions
 * from ucx$inetdef.h
 */
#define UCX$C_SOCKOPT 1
#define UCX$C_AUXS 127
#define UCX$C_TCP 6
#define UCX$C_IOCTL 2
#define INET_PROTYP$C_STREAM 1

/*
 * CMU definitions
 */
#define IO__OPEN IO$_CREATE
#define CMU_K_PROTO_TCP	0
#define word_swap(x) ((((x)>>8)&0xff)|(((x)&0xff)<<8 ))

/*
 * SRI qio definitions (MultiNet, Pathway, TCPware)
 */
#define IO$S_FCODE	6
#define IO$_SETSOCKOPT	(IO$_ACCESS | (5 << IO$S_FCODE))

/*
 * setsockopt
 */
int vms_setsockopt (int sd, int level, int optname, char *optval, int optlen)
{
	int package;
	int ef;			/* Event flag number */
	int sdc;			/* Socket device channel */
	unsigned short fun; 	/* Qiow function code  */
	unsigned short iosb[4];	/* Io status block */
	struct itm_list_2 sockopt;
	struct itm_list_2 opt_list; /* Qiow setsockopt commands descriptor */
	int status;
	int get_sdc(int __fd);

	if (optval == NULL) {
		errno = EFAULT;
		return(-1);
	}
/* 
 * Get the socket device channel number.
 */
	sdc = get_sdc(sd);
	if (sdc == 0) {
	/* Not an open socket descriptor. */
		errno = EBADF;
		return (-1);
	}
/* 
 * Get an event flag for qio
 */
	status = lib$get_ef(&ef);
	if (!(status&1)) {
		ef = 0;		/* No ef available. Use 0 */
	}

	package = get_tcpip_package();
	DEBUG(3,("Used TCP/IP package: %d\n",package));

	switch(package) {

/* 
 * UCX	- Fill in opt descriptor and sockopt descriptor.
 */
	case TCPIP_UCX:
		opt_list.code = (level == SOL_SOCKET) ? UCX$C_SOCKOPT : level;
		opt_list.len = sizeof(sockopt);
		opt_list.bufadr = &sockopt;
		sockopt.code = optname;
		sockopt.len = optlen;
		sockopt.bufadr = optval;
		status = sys$qiow(ef, sdc, IO$_SETMODE, iosb, 0, 0,
		      0, 0, 0, 0, &opt_list, 0);
		break;

/*
 * CMU	- does not support any settings.
 */
	case TCPIP_CMU:
		status = 1;
		iosb[0] = 1;
		break;

/*
 * SRI	- the qio interface is more straight forward.
 */
	case TCPIP_SRI:
		status = sys$qiow(ef, sdc, IO$_SETSOCKOPT, iosb, 0, 0,
		      level, optname, optval, optlen, 0, 0);
		break;

	default:
		errno = ENOTSOCK;
		status = lib$free_ef(&ef);
		return(-1);
	}
	lib$free_ef(&ef);
	if (!(status&1)) {
		DEBUG(0,("setsockopt qio error: %08X\n",status));
		errno = EVMSERR;
		vaxc$errno = status;
		return(-1);
	}
	if (!(iosb[0]&1)) {
		DEBUG(0,("setsockopt I/O error: %08X\n",iosb[0]));
		errno = EVMSERR;
		vaxc$errno = iosb[0];
		return(-1);
	}
	return(0);
}

/*
 * socket
 *
 * Here is the server support not included currently in SOCKETSHR.
 */
#define TCPSRV 127
int vms_socket (int af, int mess_type, int prot_type)
{
	int package;
	int ef;			/* Event flag number */
	int sd;			/* socket descriptor */
	short int sdc1, sdc2;
	unsigned short iosb[4];	/* Io status block */
	int status;
	struct {
		unsigned short protocol;
		unsigned char  type;
		unsigned char  domain;
	} sockdef;
	static $DESCRIPTOR(ucx_device, "SYS$NET");
	static $DESCRIPTOR(sri_device, "SYS$INPUT");
	int get_sdc(int __fd);

	if (af != TCPSRV) {
		return(socket(af, mess_type, prot_type));
	}
/* 
 * Get an event flag for qio
 */
	status = lib$get_ef(&ef);
	if (!(status&1)) {
		ef = 0;		/* No ef available. Use 0 */
	}
/*
 * Allocate a socket.
 */
	sd = socket(AF_INET, SOCK_STREAM, 0);
printf("sd = %d\n",sd);
	if (sd < 0) return(-1);
/* 
 * Get the socket device channel number.
 */
	sdc1 = get_sdc(sd);
printf("sdc1 = %04X\n",sdc1);
	if (sdc1 == 0) {
	/* Not an open socket descriptor. */
		close(sd);
		errno = EBADF;
		return (-1);
	}
	sdc2 = 0;


	package = get_tcpip_package();
	switch(package) {
/* 
 * UCX	- connect to auxilliary server UCX$C_AUXS
 */
	case TCPIP_UCX:
		sys$dassgn(sdc1);	/* replace channel connect */
		status = sys$assign(&ucx_device, &sdc2, 0, 0);
printf("sdc2 = %04X, status = %08X\n",sdc2,status);
		if (!(status&1)) break;
		sockdef.protocol = UCX$C_TCP;
		sockdef.type = INET_PROTYP$C_STREAM;
		sockdef.domain = UCX$C_AUXS;
		status = sys$qiow(ef, sdc2, IO$_SETMODE, iosb, 0, 0,
			&sockdef, 0, 0, 0, 0, 0);
printf("sts = %08X\n",status);
printf("iosb = %08X\n",iosb[0]);
		break;

/*
 * CMU	- open port
 */
	case TCPIP_CMU:
		sdc2 = sdc1;
		status = sys$qiow(ef, sdc2, IO__OPEN, iosb, 0, 0,
/*    	    	    	0, 0, word_swap(sa->sin_w_port), /**/
			0, 0, 0,
    	    	    	0, CMU_K_PROTO_TCP, 0);
		break;

/*
 * SRI	- assign channel to SYS$INPUT
 */
	case TCPIP_SRI:
		sys$dassgn(sdc1);	/* replace channel connect */
		status = sys$assign(&sri_device, &sdc2, 0, 0);
		break;
/*
 * Others not supported
 */
	default:
		close(sd);
		errno = ENOTSOCK;
		lib$free_ef(&ef);
		return(-1);
	}
/*
 * common exit handling
 */
	lib$free_ef(&ef);
	if (sdc1 != sdc2) {
		DEBUG(0,("socket not opened on same channel: %04X - %04X\n",
			sdc1, sdc2));
		status = 2;
	}
	if (!(status&1)) {
		DEBUG(0,("socket qio error: %08X\n",status));
		close(sd);
		errno = ENOTSOCK;
		vaxc$errno = status;
		return(-1);
	}
	if (!(iosb[0]&1)) {
		DEBUG(0,("socket I/O error: %08X\n",iosb[0]));
		close(sd);
		errno = ENOTSOCK;
		vaxc$errno = iosb[0];
		return(-1);
	}
	return(sd);
}


#ifdef SOCKETSHR

#if 0
#undef setsockopt
/*
 * setsockopt
 */
int vms_setsockopt (int sd, int level, int optname, const char *optval, int optlen)
{
	int i;
	i = setsockopt(get_sdc(sd),level,optname,optval,optlen);
DEBUG (2, ("setsockopt: errno = %d, vaxc$errno = %08X\n",errno,vaxc$errno));
	return(i);
}
#endif

#else	/* means UCX */

#define F_GETFL 3
#define VMSOK(s) (s & 1)

/*
 * fcntl
 *
 * Standard C RTL or UCX does not have fcntl().
 *
 * This one is good for setting blocking state (FIONBIO) only.
 */
int vms_fcntl   (int fd, int cmd, int val)
{
    int argp;
    int ef;			/* Event flag number */
    int sdc;			/* Socket device channel */
    unsigned short fun; 	/* Qiow function code  */
    unsigned short iosb[4];	/* Io status block */
    char *p5, *p6;			/* Args p5 & p6 of qiow */
    struct comm
    {
	int command;
	char *addr;
    } ioctl_comm;		/* Qiow ioctl commands. */
    struct it2 
    {
	unsigned short len;
	unsigned short opt;
	struct comm *addr;
    } ioctl_desc;		/* Qiow ioctl commands descriptor */
    int status;
    int get_sdc(int __fd);
    
    /* 
     * Gets an event flag for qio
     */
    status = lib$get_ef(&ef);
    if (!VMSOK(status))
    {
	/* No ef available. Use 0 */
	ef = 0;
    }

    /* 
     * Get the socket device channel number.
     */
    sdc = get_sdc(fd);
    if (sdc == 0)
    {
	/* Not an open socket descriptor. */
	errno = EBADF;
	status = lib$free_ef(&ef);
	return (-1);
    }
    
    /* 
     * Fill in ioctl descriptor.
     */
    ioctl_desc.opt = UCX$C_IOCTL;
    ioctl_desc.len = sizeof(struct comm);
    ioctl_desc.addr = &ioctl_comm;
    
    /* 
     * Decide qio function code and in/out parameter.
     */
    if (cmd == F_GETFL)
    {
/*
	fun = IO$_SENSEMODE;
	p5 = 0;
	p6 = (char *) &ioctl_desc;
*/
	return(0);
    }
    else
    {
	fun = IO$_SETMODE;
	p5 = (char *) &ioctl_desc;
	p6 = 0;
    }

    /* 
     * Fill in ioctl command.
     */
    ioctl_comm.command = FIONBIO;	/* we only support this call */
    ioctl_comm.addr = (char *)&argp;
    if (val == 0) {
	argp = 0;
    } else {
	argp = 1;
    }
    
    /* 
     * Do ioctl.
     */
    status = sys$qiow(ef, sdc, fun, iosb, 0, 0,
		      0, 0, 0, 0,		/* p1 - p4: not used*/
		      p5, p6);
    
    if (!VMSOK(status))
    {
DEBUG (0,("ioctl failed: status = %d\n", status));
	errno = status;
	status = lib$free_ef(&ef);
	return (-1);
    }
    
    if (!VMSOK(iosb[0]))
    {
DEBUG (0,("ioctl failed: status = %x, %x, %x%x\n", iosb[0], iosb[1],
iosb[3], iosb[2]));
	errno = iosb[0];
    	status = lib$free_ef(&ef);
	return (-1);
    }
    
    status = lib$free_ef(&ef);
    return (0);
}
#endif	/* not SOCKETSHR */

/* TESTESTESTEST */
int vms_getpeername (int sd, struct sockaddr *name, int *namelen)
{
	int package;
	int ef;			/* Event flag number */
	int sdc;			/* Socket device channel */
	unsigned short fun; 	/* Qiow function code  */
	unsigned short iosb[4];	/* Io status block */
	struct itm_list_3 rem_list;
	int status;
	int get_sdc(int __fd);

/* 
 * Get the socket device channel number.
 */
	sdc = get_sdc(sd);
	if (sdc == 0) {
	/* Not an open socket descriptor. */
		errno = EBADF;
		return (-1);
	}
/* 
 * Get an event flag for qio
 */
	status = lib$get_ef(&ef);
	if (!(status&1)) {
		ef = 0;		/* No ef available. Use 0 */
	}

	package = get_tcpip_package();
	DEBUG(3,("Used TCP/IP package: %d\n",package));
Debug1("getpeername: sd = %d, sdc = %04X\n",sd,sdc);

	switch(package) {

/* 
 * UCX	- Fill in opt descriptor and sockopt descriptor.
 */
	case TCPIP_UCX:
		rem_list.code = 0;
		rem_list.len = sizeof(struct sockaddr);
		rem_list.bufadr = name;
		rem_list.retadr = (short int *)namelen;
		status = sys$qiow(ef, sdc, IO$_SENSEMODE, iosb, 0, 0,
		      0, 0, 0, &rem_list, 0, 0);
		break;

/*
 * CMU
 */
	case TCPIP_CMU:
		status = 1;
		iosb[0] = 1;
		break;

/*
 * SRI
 */
	case TCPIP_SRI:
		status = 1;
		iosb[0] = 1;

	default:
		errno = ENOTSOCK;
		status = lib$free_ef(&ef);
		return(-1);
	}
	lib$free_ef(&ef);
Debug1("sts = %08X, iosb = %08X\n",status,iosb[0]);
	if (!(status&1)) {
		DEBUG(0,("getpeername qio error: %08X\n",status));
		errno = EVMSERR;
		vaxc$errno = status;
		return(-1);
	}
	if (!(iosb[0]&1)) {
		DEBUG(0,("getpeername I/O error: %08X\n",iosb[0]));
		errno = EVMSERR;
		vaxc$errno = iosb[0];
		return(-1);
	}
Debug1("Addr: %08X\n",*(int *)&(((struct sockaddr_in *)name)->sin_addr));
	return(0);
}

