/* NAME:
 *	bindport - bind an open descriptor 
 *
 * SYNOPSIS:
 *	int bind_sock4(int "fd", struct sockaddr_in *"sa", int "port", int "tries");
 *	int bind_port4(int "fd", int "port", char *"addr", int "tries");
 *	int bind_sock(int "fd", struct sockaddr_in *"sa", int "port");
 *	int bind_port(int "fd", int "port", char *"addr");
 *	
 *	bindport -p "port" -f "fd" [-a "addr"] [-t "tries"]
 *
 * DESCRIPTION:
 *	This file produces both an executable program 'bindport' and
 *	the library functions bind_sock4() and bind_port4().  The
 *	functions bind_sock() and bind_port() are for backwards
 *	compatability and uses a default "tries" value.
 *	
 *	The 2nd function is simply a different interface to the first
 *	which will do the binding directly if the effective uid is 0,
 *	otherwise it will invoke 'bindport' to do the job.  Obviously
 *	'bindport' must be set-uid root.
 *
 *	Thus bind_port() can be called from root and non-root
 *	processes to bind a reserved port.  If "port" is greater than
 *	zero and less than IPPORT_RESERVED, bind_port() attempts to
 *	bind to that port only.	 Otherwise it will try for any
 *	reserved port.   It will try up to "tries" time, sleeping "n"
 *	seconds between tries when "n" is the try number
 *	(0 <= "n" < "tries").
 *
 *	If the "port" given to bind_sock() is less than zero, "sa" is
 *	expected to contain the desired port.
 *
 *	If the "port" is zero or otherwise outside of the reserved
 *	range we simply bind it.  This allows the one routine to cater
 *	for reserved and non-reserved uses.
 *	
 *	The notion of execing an external entity to bind reserved
 *	ports is based on a concept described by Cheswick &
 *	Bellovin in "Firewalls and Internet Security".
 *	
 * AUTHOR:
 *	Simon J. Gerraty <sjg@crufty.net>
 */
/*
 *      @(#)Copyright (c) 1994-2001, Simon J. Gerraty.
 *      
 *      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@crufty.net
 */

#ifndef lint
static char RCSid[] = "$Id: bindport.c,v 1.11 2000/04/06 11:31:06 sjg Exp $";
#endif
#include <stdio.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include "wait.h"

#ifndef WAIT_T
#define WAIT_T int
#endif
#ifndef _PATH_BINDPORT
# define _PATH_BINDPORT "/usr/local/libexec/bindport"
#endif
#ifndef BINDPORT_TRIES
# define BINDPORT_TRIES 8
#endif
#ifdef UNIT_TEST
# define DEBUG
#endif


static int Debug = 0;

/*
 * just in case we are to be run chrooted, allow
 * caller to set this...
 */
char *_path_bindport = _PATH_BINDPORT;
int bindport_tries = BINDPORT_TRIES;

static int
bind_a_port(s, sa, port)
	int s;
	struct sockaddr_in *sa;
	int port;
{
	sa->sin_port = htons(port);
	return bind(s, (struct sockaddr *) sa, sizeof (struct sockaddr_in));
}


int
bind_sock4(s, sa, port, tries)
	int s;
	struct sockaddr_in *sa;
	int port;
	int tries;
{
	char fbuf[16], pbuf[16], tbuf[16];
	char *av[10];
	int i, pid, sts = -1;
	WAIT_T status;

	i = 1;
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
		   (void *)&i, sizeof (int));

	if (tries < 0)
		tries = bindport_tries;
	if (tries < 1)
		tries = 1;
	if (port < 0)				/* use port in sa */
		port = ntohs(sa->sin_port);

	sa->sin_family = AF_INET;	/* all we support right now */

	if (port == 0 || port > IPPORT_RESERVED)
		return bind_a_port(s, sa, port);
	
	if (geteuid() == 0) {
		for (i = 0; sts < 0 && i < tries; ++i) {
			if (port > 0 && port <= IPPORT_RESERVED) {
				sts = bind_a_port(s, sa, port);
			} else {
				for (port = 600;
				     sts < 0 && port < IPPORT_RESERVED;
				     ++port) {
					sts = bind_a_port(s, sa, port);
				}
			}
			if (sts < 0)
				sleep(i);
				
		}
		return sts;
	} else {
		sprintf(fbuf, "-f%d", s);
		sprintf(pbuf, "-p%d", port);
		sprintf(tbuf, "-t%d", tries);
		av[0] = "bindport";
		av[1] = fbuf;
		av[2] = pbuf;
		av[3] = tbuf;
		if (sa->sin_addr.s_addr > 0) {
			av[4] = "-a";
			av[5] = inet_ntoa(sa->sin_addr);
			av[6] = NULL;
		} else
			av[4] = NULL;

		if (Debug)
			fprintf(stderr, "exec(%s,%s,%s,%s)\n",
				_path_bindport, av[0], av[1], av[2]);
		else
			syslog(LOG_DEBUG, "exec(%s,%s,%s,%s)\n",
			       _path_bindport, av[0], av[1], av[2]);
		
		switch (pid = fork()) {
		case 0:
			execv(_path_bindport, av);
			syslog(LOG_ERR, "exec(%s,%s,%s,%s): %m",
			       _path_bindport, av[0], av[1], av[2]);
			exit(1);
		case -1:
			syslog(LOG_ERR, "fork(): %m");
			break;
		default:
			waitpid(pid, &status, 0);
			if (WIFEXITED(status))
				return WEXITSTATUS(status);
			break;
		}
	}
	return -1;
}


int
bind_port4(s, port, addr, tries)
	int s;
	int port;
	char *addr;
	int tries;
{
	struct sockaddr_in sa;

	memset((char *)&sa, 0, sizeof (sa));
	if (addr)
		sa.sin_addr.s_addr = inet_addr(addr);
	
	return bind_sock4(s, &sa, port, tries);
}

/*
 * These are for backwards compatability.
 */

int
bind_sock(s, sa, port)
	int s;
	struct sockaddr_in *sa;
	int port;
{
	return bind_sock4(s, sa, port, bindport_tries);
}

int
bind_port(s, port, addr)
	int s;
	int port;
	char *addr;
{
	return bind_port4(s, port, addr, bindport_tries);
}

#ifdef MAIN
int
main(argc, argv)
	int argc;
	char **argv;
{
	extern int optind, getopt();
	extern char *optarg;
	struct sockaddr_in sa;
	char *addr = NULL;
	int port = -1;
	int fd = -1;
	int i, sts;
	int tries = -1;
	
	while ((i = getopt(argc, argv, "a:df:p:t:")) != EOF) {
		switch (i) {
		case 'a':
			addr = optarg;
			break;
		case 'd':
#ifdef DEBUG
			fd = socket(AF_INET, SOCK_STREAM, 0);
#endif
			Debug++;
			break;
		case 'f':
			fd = atoi(optarg);
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 't':
			tries = atoi(optarg);
			break;
		}
	}
	if (fd < 0) {
		fprintf(stderr, "bindport: need a descriptor!\n");
		exit(1);
	}
#ifdef DEBUG
	if (!Debug)
#endif
		if (geteuid() != 0) {
			fprintf(stderr, "bindport: should be set-uid root!\n");
			exit(1);
		}
	
	i = sizeof (struct sockaddr_in);
	if (getsockname(fd, (struct sockaddr *) &sa, &i) < 0) {
		fprintf(stderr, "bindport: %d is not a socket!\n", fd);
		exit(1);
	}
	sts = bind_port4(fd, port, addr, tries);
#ifdef DEBUG
	if (Debug) {
		i = sizeof(sa);
		if (getsockname(fd, (struct sockaddr *)&sa, &i) == 0) {
			printf("bind_port(%d,%d,%s) -> %s\n",
			       fd, port, addr ? addr : "null",
			       ip_caller_id(&sa, 0));
		} else {
			perror("getsockname");
			exit(1);
		}
	}
#endif
	exit (sts < 0 ? 1 : sts);
}
#endif

