Milán Major

netscape-revival

1 branch
Code

security/cmd/rsh/sslrcmd.c

/*
 * Copyright (c) 1983, 1993, 1994
 *	The Regents of the University of California.  All rights 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, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 */

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)rcmd.c	8.3 (Berkeley) 3/26/94";
#endif /* LIBC_SCCS and not lint */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/signal.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <signal.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "sslrcmd.h"
#ifdef sgi
#include <bstring.h>
#endif
#ifdef LINUX
#include <sys/time.h>
#endif

#if defined (__sun)
#undef sigmask
#define sigmask(m) (m > 32 ? 0 : ( 1 << ((m)-1)))
#endif


#define DEF_SOCKD_PORT 1080

#ifdef NO_STRERROR
char errbuf[10];

char *
strerror(int err)
{
	sprintf(errbuf, "%d", err);
	return(errbuf);
}
#endif
extern int rresvport( int *port );

static char *defaultCertDir = "/usr/etc/ssl";

static int usingSecurity = 0;

SECKEYPrivateKey *privkey = NULL;

int
ssl_ParseFlags(char *argstring, int *pencrypt, int *pproxy)
{
    int c;
    int encryptflag;
    int proxyflag;

    if ( !argstring || ( strlen(argstring) != 2 ) ) {
        return(-1);
    }

    c = argstring[0];
    if ( c == 'e' ) {
        encryptflag = SSL_ENCRYPT;
	usingSecurity = 1;
    } else {
        encryptflag = SSL_DONT_ENCRYPT;
    }

    c = argstring[1];
    if ( c == 'p' ) {
        proxyflag = SSL_PROXY;
    } else {
        proxyflag = SSL_NO_PROXY;
    }

    if ( pencrypt ) {
        *pencrypt = encryptflag;
    }

    if ( pproxy ) {
        *pproxy = proxyflag;
    }

    return( encryptflag | proxyflag );
}

static int
ssl_InitWithDirectory(const char *dir)
{
    char *newdir;
    CERTCertDBHandle *certHandle;

    if (dir) {
	newdir = SECU_ConfigDirectory(dir);
    } else {
	newdir = SECU_DefaultSSLDir();
	if (!newdir)
	    newdir = defaultCertDir;
	else
	    SECU_ConfigDirectory(newdir);
    }
    
    certHandle = (CERTCertDBHandle *)PORT_ZAlloc(sizeof(CERTCertDBHandle));
    if (!certHandle) {
	fprintf(stderr, "unable to allocate cert database handle\n");
	return -1;
    }

    /* open up database for authenticating server */
    if (CERT_OpenCertDB(certHandle, SECU_DatabaseFileName(xpCertDB),TRUE))
	if (CERT_OpenVolatileCertDB(certHandle)) {
	    fprintf(stderr, "unable to open cert database\n");
	    return -1;
	}

    CERT_SetDefaultCertDB(certHandle);

    return 0;
}

static int
ssl_SetupSockd(int fd)
{
    int rv;
    unsigned short port;
    unsigned long host;
    char *hostname;
    struct hostent *hent;
    struct servent *sp;

    sp = getservbyname("socks", "tcp");
    if (sp) {
	port = sp->s_port;
    }
    if (!port) {
	port = DEF_SOCKD_PORT;
    }

    if (hostname = getenv("SOCKS_HOST")) {
	hent = gethostbyname(hostname);
	if (hent) {
	    host = *(unsigned long *)hent->h_addr_list[0];
	} else {
	    errno = EINVAL;
	    return -1;
	}
    } else {
	errno = EINVAL;
	return -1;
    }

    rv = SSL_ConfigSockd(fd, host, port);
    return rv;
}

static int
ssl_EnableWithFlags(int fd, int createflags)
{
    int rv;

    /* set up socks and/or security as requested */
    switch (createflags & SSL_PROXY_MASK) {
        case SSL_NO_PROXY:
	    rv = 0;
	    break;
        case SSL_PROXY:
	    rv = SSL_Enable(fd, SSL_SOCKS, 1);
	    if (rv < 0)
		return rv;
	    rv = ssl_SetupSockd(fd);
	    if (rv < 0)
		return rv;
	    break;
        case SSL_SECURE_PROXY:
	    rv = SSL_Enable(fd, SSL_SECURITY, 1);
	    if (rv < 0)
		return rv;
	    rv = SSL_Enable(fd, SSL_SOCKS, 1);
	    if (rv < 0)
		return rv;
	    rv = ssl_SetupSockd(fd);
	    if (rv < 0)
		return rv;
	    break;
        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
	    return -1;
    }
    
    switch (createflags & SSL_ENCRYPT_MASK) {
        case SSL_ENCRYPT:
	    rv = SSL_Enable(fd, SSL_SECURITY, 1);
	    if (rv < 0)
		return rv;
	    break;
        case SSL_DONT_ENCRYPT:
	    rv = 0;
	    break;
        default:
	    PORT_SetError(SEC_ERROR_INVALID_ARGS);
	    return -1;
    }

    rv = SSL_Enable(fd, SSL_HANDSHAKE_AS_CLIENT, 1);
    
    if (usingSecurity) {
	SSL_AuthCertificateHook(fd, SSL_AuthCertificate,
				(void *)CERT_GetDefaultCertDB());
    }

    return rv;
}


/* 
** This is our callback for client auth, when server wants our cert & key
*/
int
GetClientAuthData(void *arg, int fd, struct CERTDistNamesStr *caNames,
                      struct CERTCertificateStr **pRetCert,
                      struct SECKEYPrivateKeyStr **pRetKey)
{
    SECKEYKeyDBHandle *handle;
    CERTCertificate *cert;
    int rv, errsave;
    static PRBool dbOpen = PR_FALSE;
    SECKEYPrivateKey *key;

    if (arg == NULL) {
        fprintf(stderr, "SSL_Rcmd: no key/cert name specified for server's request for client auth\n");
        return -1;
    }

    if (dbOpen == PR_FALSE) {
	handle = SECKEY_OpenKeyDB(SECU_DatabaseFileName(xpKeyDB));
	if (handle == NULL) {
	    fprintf(stderr, "SSL_Rcmd: unable to open key database for client auth\n");
	    return -1;
	}
	dbOpen = PR_TRUE;
    }

    if (!privkey) {
	key = SECU_GetPrivateDongleKey(handle, arg,
				       SECU_ConfigDirectory(NULL));
	errsave = PORT_GetError();
	privkey = SECKEY_CopyPrivateKey(key);
    } else {
	key = SECKEY_CopyPrivateKey(privkey);
    }

    /* XXX this seems to irrevocably zero out privKeyDB  in keydb.c */
    /*
    SECKEY_CloseKeyDB();
    */

    if (!key) {
        if (errsave == SEC_ERROR_BAD_PASSWORD)
            fprintf(stderr, "Bad password\n");
        else if (errsave > 0)
            fprintf(stderr, "Unable to read key (error %d)\n", errsave);
        else if (errsave == SEC_ERROR_BAD_DATABASE)
            fprintf(stderr, "Unable to get key from database (%d)\n", errsave);
        else
            fprintf(stderr, "SECKEY_FindKeyByName: internal error %d\n", errsave);
        return -1;
    }

    cert = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), arg);
    if (!cert) {
        fprintf(stderr, "Unable to get certificate (%s)\n",
		SECU_ErrorString(PORT_GetError()));
        return -1;
    }

    *pRetCert = cert;
    *pRetKey = key;

    return 0;
}

int HandshakeDone(int fd, void *data)
{
    if (privkey)
	SECKEY_DestroyPrivateKey(privkey);
    privkey = NULL;
    return 0;
}

int
SSL_Rcmd(char **ahost,
	 int rport,
	 char *locuser,
	 char *remuser,
	 char *cmd,
	 int *fd2p,
	 int createflags,
	 char *certDir,
	 char *nickname)
{
    struct hostent *hp;
    struct sockaddr_in sin, from;
    fd_set reads;
    long oldmask;
    pid_t pid;
    int s, s3, lport, timo, rv;
    char c;
	
    PR_Init("rsh", 1, 1, 0);
    
    /* get all hosts and IPs squared away */
    pid = getpid();
    hp = gethostbyname(*ahost);
    if (hp == NULL) {
	fprintf(stderr, "SSL_Rcmd: gethostbyname: %s\n", *ahost);
	return (0);
    }

    sin.sin_family = hp->h_addrtype;
    PORT_Memcpy(&sin.sin_addr, hp->h_addr_list[0], hp->h_length);
    sin.sin_port = rport;

    *ahost = hp->h_name;
    oldmask = sigblock(sigmask(SIGURG));

    /* get ready to use libsec */
    SEC_Init();

    if (usingSecurity) {
	rv = ssl_InitWithDirectory(certDir);
	if (rv < 0) {
	    fprintf(stderr, "SSL_Rcmd: unable to intialize security\n");
	    sigsetmask(oldmask);
	    return 0;
	}
    }

    for (timo = 1, lport = IPPORT_RESERVED - 1;;) {
	s = rresvport(&lport);
	if (s < 0) {
	    if (errno == EAGAIN)
		(void)fprintf(stderr, "SSL_Rcmd: socket: All ports in use\n");
	    else
		(void)fprintf(stderr, "SSL_Rcmd: socket: %s\n", 
			      strerror(errno));
	    sigsetmask(oldmask);
	    return (0);
	}
#ifdef __hpux
	ioctl(s, SIOCSPGRP, pid);
#else
	fcntl(s, F_SETOWN, pid);
#endif

	/* prepare this new fd for SSL */
	rv = SSL_Import(s);
	if (rv < 0) {
	    (void)fprintf(stderr, "SSL_Rcmd: unable to import ssl sock\n");
	}

	/* set up socks and/or security as requested */
	rv = ssl_EnableWithFlags(s, createflags);
	if (rv < 0) {
	    fprintf(stderr, "SSL_Rcmd: unable to enable socket %d\n", s);
	}

	if (usingSecurity) {
	    SSL_GetClientAuthDataHook(s, GetClientAuthData,
				      (void *)nickname);
	}

	/* now connect */
	rv = SSL_Connect(s, (struct sockaddr *)&sin, sizeof(sin));
	if (rv >= 0)
	    break;

	if (PORT_GetError() == XP_ERRNO_EIO) {
	    fprintf(stderr, "SSL_Rcmd: unable to connect\n");
	    goto bad;
	}

	SSL_Close(s);

	if (errno == EADDRINUSE) {
	    lport--;
	    continue;
	}
	if (errno == ECONNREFUSED && timo <= 16) {
	    (void)sleep(timo);
	    timo *= 2;
	    continue;
	}
	if (hp->h_addr_list[1] != NULL) {
	    int oerrno = errno;
	    
	    (void)fprintf(stderr, "connect to address %s: ",
			  inet_ntoa(sin.sin_addr));
	    errno = oerrno;
	    perror(0);
	    hp->h_addr_list++;
	    memcpy(&sin.sin_addr, hp->h_addr_list[0], hp->h_length);
	    (void)fprintf(stderr, "Trying %s...\n", inet_ntoa(sin.sin_addr));
	    continue;
	}
	(void)fprintf(stderr, "%s: %s\n", hp->h_name, strerror(errno));
	sigsetmask(oldmask);
	return (0);
    }

    lport--;
    if (fd2p == 0) {
	SSL_Write(s, "", 1);
	lport = 0;
    } else {
	char num[8];
	int s2;
	int len = sizeof(from);
	struct sockaddr_in backsin;
	int sinlen = sizeof(backsin);
	
	s2 = rresvport(&lport);
	
	if (s2 < 0)
	    goto bad;
	
	/* import s2 for ssl */
	rv = SSL_Import(s2);
	if (rv < 0) {
	    fprintf(stderr, "SSL_Rcmd: unable to import control ssl\n");
	}

	rv = ssl_EnableWithFlags(s2, createflags);
	if (rv < 0) {
	    fprintf(stderr, "SSL_Rcmd: unable to enable ctrl sock %d\n", s2);
	}

	SSL_GetClientAuthDataHook(s2, GetClientAuthData,
				  (void *)nickname);

	if (usingSecurity) {
	    rv = SSL_HandshakeCallback(s2, HandshakeDone, NULL);
	    if (rv)
		fprintf(stderr, "SSL_Rcmd: handshake callback failed on %d\n", s2);
	}

	/* send a bind message to sockd if necessary */
	if ( (createflags & SSL_PROXY_MASK ) != SSL_NO_PROXY ) {
	    if (SSL_CheckDirectSock(s2) == 0) {
		rv = SSL_BindForSockd(s2, &backsin, sizeof(backsin),
				      sin.sin_addr.s_addr);
		if (rv) {
		    fprintf(stderr, "SSL_Rcmd: SSL_BindForSockd: %s\n", 
			    SECU_ErrorString(PORT_GetError()));
		    SSL_Close(s2);
		    goto bad;
		}
	    }
	}

	if( SSL_GetSockName(s2, (struct sockaddr *)&backsin, &sinlen) < 0) {
	    fprintf(stderr, "SSL_Rcmd: SSL_GetSockName: %s\n", 
			    SECU_ErrorString(PORT_GetError()));
	    SSL_Close(s2);
	    goto bad;
	}
	
	rv = SSL_Listen(s2, 1);
	if (rv < 0) {
	    fprintf(stderr, "SSL_Rcmd: SSL_Listen failed, %s",
		    SECU_ErrorString(PORT_GetError()));
	}
	(void)sprintf(num, "%u", ntohs(backsin.sin_port));
	
	/* if usingSecurity, we have to finish handshake on s before listening
	**   on next port s2
	*/
	if (usingSecurity) {
	    FD_ZERO(&reads);
	    FD_SET(s, &reads);
	    while (select(32, &reads, 0, 0, 0) > 0) {
		rv = SSL_ForceHandshake(s);
		if (rv == SECSuccess)
		    break;

		if (rv == SECFailure) {
		    fprintf(stderr, "SSL_Rcmd: unable to finish handshake on %d: %s\n", s, SECU_ErrorString(PORT_GetError()));
		    SSL_Close(s2);
		    goto bad;
		}
	    }
	}

	rv = SSL_Write(s, num, strlen(num)+1);
	if (rv != strlen(num)+1) {
	    if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE)
		fprintf(stderr, "SSL_Rcmd: bad certificate from server\n");
	    else
		fprintf(stderr, 
			"SSL_Rcmd: (sending port number on stderr): %s\n",
			SECU_ErrorString(PORT_GetError()));
	    SSL_Close(s2);
	    goto bad;
	}
	FD_ZERO(&reads);
	FD_SET(s, &reads);
	FD_SET(s2, &reads);
	errno = 0;
	if (select(32, &reads, 0, 0, 0) < 1 || !FD_ISSET(s2, &reads)) {
	    if (errno != 0)
		(void)fprintf(stderr,
			      "SSL_Rcmd: select failed (for stderr): %s\n",
			      strerror(errno));
	    else
		(void)fprintf(stderr,
			      "SSL_Rcmd: protocol failure (data ready on %d before %d)\n", s, s2);
		SSL_Close(s2);
		goto bad;
	}
	s3 = SSL_Accept(s2, (struct sockaddr *)&from, &len);
	SSL_Close(s2);
	if (s3 < 0) {
	    (void)fprintf(stderr, "SSL_Rcmd: SSL_Accept: %s\n", 
			  SECU_ErrorString(PORT_GetError()));
	    lport = 0;
	    goto bad;
	}

	/* XXX We have to force the handshake from the client side */
	do {
	    rv = SSL_ForceHandshake(s3);
	    if (rv == -1) {
		fprintf(stderr, "SSL_Rcmd: could not finish handshake on %d\n", s3);
		SSL_Close(s3);
		goto bad;
	    }
	} while (rv == -2);

	*fd2p = s3;
	from.sin_port = ntohs((u_short)from.sin_port);
	rv = SSL_CheckDirectSock(s3);
	if ( ( !rv ) && 
	     (from.sin_family != AF_INET ||
	      from.sin_port >= IPPORT_RESERVED ||
	      from.sin_port < IPPORT_RESERVED / 2) ) {
	    (void)fprintf(stderr,
			  "socket:2: protocol failure in circuit setup.\n");
	    goto bad2;
	}
    }
    rv = SSL_Write(s, locuser, strlen(locuser)+1);
    if (rv >= 0)
	rv = SSL_Write(s, remuser, strlen(remuser)+1);
    if (rv >= 0)
	rv = SSL_Write(s, cmd, strlen(cmd)+1);
    if (rv < 0) {
	(void)fprintf(stderr, "SSL_Rcmd: error writing to ssl %d\n", s);
    }

    if (SSL_Read(s, &c, 1) != 1) {
	fprintf(stderr, "SSL_Rcmd: %s: %s\n", *ahost, 
		SECU_ErrorString(PORT_GetError()));
	goto bad2;
    }
    /* rshd returned an error */
    if (c != 0) {
	while (rv = SSL_Read(s, &c, 1)) {
	    if (rv < 0) {
		if (PORT_GetError() == EWOULDBLOCK) {
		    continue;
		} else {
		    fprintf(stderr, "SSL_Rcmd: SSL_Read: %s\n",
			    SECU_ErrorString(PORT_GetError()));
		    goto bad2;
		}
	    }
	    (void)write(STDERR_FILENO, &c, 1);
	    if (c == '\n')
		break;
	}
	goto bad2;
    }
    sigsetmask(oldmask);
    return (s);
bad2:
    if (lport)
	SSL_Close(s3);
    if (privkey) {
	SECKEY_DestroyPrivateKey(privkey);
	privkey = NULL;
    }
bad:
    SSL_Close(s);
    sigsetmask(oldmask);
    if (privkey)
	SECKEY_DestroyPrivateKey(privkey);
    return (0);
}