/*
 * Copyright (c) 2000-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*	OpenFWTK project - TIS fwtk legacy API
 *	(c) ArkanoiD 2000,2002
 */

#include "firewall.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <syslog.h>
#include <grp.h>
#include <pwd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <inttypes.h>

#include "auth.h"
#include "fwfunc.h"

#ifndef F_ULOCK
#define F_ULOCK         0
#define F_LOCK          1
#define F_TLOCK         2
#endif

#ifndef INADDR_NONE
#define INADDR_NONE (in_addr_t) -1
#endif

static char* moduleId ATTR_UNUSED = "$Id: sysapi.c,v 1.15 2007/09/24 23:05:07 arkenoi Exp $";

extern	char *ip2name();
extern	int isalldigits();
extern  size_t strlcpy(char*,const char*,size_t);
extern  size_t strlcat(char*,const char*,size_t);
extern	int proxy_nodns;

int
str_to_port(char *portstr)
{
	struct	servent	*sp;

	if (isalldigits(portstr))
		return(atoi(portstr));

	if ( (sp = getservbyname(portstr,"tcp")) )
		return(ntohs(sp->s_port));

	syslog(LLEV,"fwtkcfgerr: cannot decode %.100s as port",portstr);
	exit(1);
}

int
mapgid(char *gnam)
{
	struct	group	*gp;

	if (isalldigits(gnam))
		return(atoi(gnam));

	if ( (gp = getgrnam(gnam)) )
		return(gp->gr_gid);

	return(-1);
}

int
mapuid(char *user)
{
	struct	passwd	*pwd;

	if (isalldigits(user))
		return(atoi(user));

	if ( (pwd = getpwnam(user)) )
		return(pwd->pw_uid);

	return(-1);
}

/*
 *      Return peer name and address or unix/unix if socket is AF_UNIX
 *      This code relies on some assumptions valid for all today's unix systems:
 *      1) sizeof(sockaddr_un) > sizeof(sockaddr_in), since it stores quite long
 *      sun_path[UNIX_PATH_MAX].
 *      2) sockaddr_un.sun_family, sockaddr_in.sin_family all have the same offset
 *      (zero to sockaddr struct beginning)
 *      Those are checked externally by structchk.c program before library build.
 */

int
peername(int fd,char *lname,char *sname,int z)
{
	char			optbuf[256];
	socklen_t		optsize;
	char			*optp;
	char			logbuf[MAX_STR];
	socklen_t		addrlen;
	struct	sockaddr_un	sua;
    struct sockaddr        *sa  = (struct sockaddr *)   &sua;
    struct sockaddr_in     *sia = (struct sockaddr_in *)&sua;
	char			*x;
	int			protoval;
	socklen_t		protolen;
	char			tbuf[1500];

	addrlen = sizeof(struct sockaddr_un);
	memset(sname,0,z);
	memset(lname,0,z);

	protolen = sizeof(int);

	if (getsockopt(fd,SOL_SOCKET,SO_TYPE,(char *)&protoval,&protolen)) {
		syslog(LLEV,"fwtksyserr: cannot get protocol id, %s", strerror(errno));
		return(1);
	}

	switch (protoval) {
		case SOCK_STREAM:
			if (getpeername(fd,(struct sockaddr *)sa,&addrlen)<0) {
				if (errno!=ENOTCONN)
					syslog(LLEV,"fwtksyserr: getpeername failed: %s", strerror(errno));
				return(1);
			}
			break;
		case SOCK_DGRAM:
			if (recvfrom(fd,tbuf,sizeof(tbuf),MSG_PEEK,
				     (struct sockaddr *)sa,&addrlen) < 0) {
				syslog(LLEV,"fwtksyserr: UDP socket error %s", strerror(errno));
				return(1);
			}
			if (connect(fd,(struct sockaddr *)sa,sizeof(sa))) {
				syslog(LLEV,"fwtksyserr: UDP socket connect error %s", strerror(errno));
				return(1);
			}
			break;
		default:
			syslog(LLEV,"fwtksyserr: unknown protocol %d",protoval);
			return(1);
	}

    if(sia->sin_family == AF_INET) {

        strlcat(sname,inet_ntoa(sia->sin_addr),z);

        if (!getsockopt(fd,IPPROTO_IP,IP_OPTIONS,optbuf,&optsize) && optsize) {

            memset(logbuf,0,sizeof(logbuf));

            for (optp = optbuf; optsize > 0; optp++, optsize--)
                snprintf(logbuf,sizeof(logbuf),"%s %2.2x",logbuf,*optp);

            syslog(LLEV,"securityalert: Connection received using IP options (ignored):%.100s",logbuf);

            if (setsockopt(fd,IPPROTO_IP,IP_OPTIONS,NULL,0)) {
                syslog(LLEV,"fwtksyserr: setsockopt IP_OPTIONS NULL: %s", strerror(errno));
                strlcpy(lname,"unknown",z);
                return(0);
            }
        }

	if (proxy_nodns) {
            strlcpy(lname,"unknown",z);
            return(0);
	}

        if (strlen(x=ip2name(sia->sin_addr)) >= z) {
            syslog(LLEV,"securityalert: %.512s/%.20s host name buffer overrun",x,sname);
            strlcpy(lname,"unknown",z);
            return(0);
        }

        lname[0] = '\0';
        strlcat(lname,x,z);
    }
    else if(sua.sun_family == AF_UNIX) {

            strlcat(sname, "unix", z);
            strlcat(lname, "unix", z);
    }

	return(0);

}

int
conn_server(char *srv,int portnum,int priv,char *rbuf)
{
	struct	hostent		*hp;
	struct	sockaddr_in	sa;
	unsigned	char	*p;
	int			lport = IPPORT_RESERVED - 1;
	int			s;

	bzero(&sa,sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(portnum);

	/*	used to have port selection routine here but
		checking current rresvport implementations shows
		that this functions does that pretty well	*/

	if (priv) 
		s = rresvport(&lport);
	else 
		s = socket(AF_INET, SOCK_STREAM, 0);

	if (s<0) {
		syslog(LLEV,"fwtksyserr: socket: %.50s",strerror(errno));
		if (rbuf)
			snprintf(rbuf,MAX_STR,"socket: %.50s",strerror(errno));
		return(-2);
	}

	for (p = (unsigned char *) srv; *p ; p++) {
		if ((*p != '.') && !isdigit(*p))
			break; 
	}

	if (!*p) {
		if ((sa.sin_addr.s_addr = inet_addr(srv)) == INADDR_NONE) {
			if (rbuf)
				strlcpy(rbuf,"hostname unparsable",MAX_STR);
			return(-1);
		}
		if (connect(s,(struct sockaddr *)&sa,sizeof(sa)) <0) {
			if (rbuf)
				snprintf(rbuf,MAX_STR,"connect: %.50s",strerror(errno));
			return(-3);
		}
		return(s);
	} else {
		if((hp = gethostbyname(srv)) == (struct hostent *)0) {
			if (rbuf)
				strlcpy(rbuf,"hostname unknown",MAX_STR);
			return(-1);
		}
		if(hp->h_length != sizeof(sa.sin_addr.s_addr)) {
			syslog(LLEV,"securityalert: invalid host address length (%d) hostname %.512s",hp->h_length, srv);
			strlcpy(rbuf, "hostname invalid",MAX_STR);
			return (-1);
		}
		for ( ; *(hp->h_addr_list) ; hp->h_addr_list++ ) {
			sa.sin_addr.s_addr = *(uint32_t*)*(hp->h_addr_list); 
			if (connect(s,(struct sockaddr *)&sa,sizeof(sa)) >=0) 
				return(s);
		}
		if (rbuf)
			snprintf(rbuf,MAX_STR,"connect: %.50s",strerror(errno));
		return(-3);
	}
}

int conn_server_unixsock(char *srv, char *rbuf)
{
	int			s;

    struct	sockaddr_un	sa;
    char srv_path[sizeof(sa.sun_path)];
    char* sock_path;

    s = socket(PF_UNIX, SOCK_STREAM, 0);
    if (s<0) {
        syslog(LLEV,"fwtksyserr: socket: %.50s",strerror(errno));
        if (rbuf)
            snprintf(rbuf,MAX_STR,"socket: %.50s",strerror(errno));
        return(-2);
    }

    strncpy(srv_path, srv, sizeof(srv_path));
    strtrm(srv_path);

    /* Trim spaces and possible "unix:" prefix */
    sock_path = (!strncasecmp(srv_path, "unix:", strlen("unix:")) ? 
		  srv_path + strlen("unix:") : srv_path);

    strlcpy(sa.sun_path, sock_path,  sizeof(sa.sun_path));
    sa.sun_family = AF_UNIX;

    if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        if (rbuf)
            snprintf(rbuf,MAX_STR,"connect: %.50s",strerror(errno));
        return(-3);
    }
    return(s);
}


char*
maphostname(char *name)
{
	struct	hostent		*hp;
		char		*p;

	for (p = name; *p ; p++)
		if ((*p != '.') && !isdigit(*p))
			break;

	if(!*p)
		return(name);

	if(!(hp = gethostbyname(name)))
		return(name);

	if (hp->h_length != sizeof(struct in_addr)) {
		syslog(LLEV,"securityalert: invalid host address length (%d) hostname %.200s", hp->h_length, name);
		return ("invalid");
	}
	return(inet_ntoa(*(struct in_addr *)(hp->h_addr)));
}

int
lock_fd(int f)
{
	return(lockf(f,F_LOCK,0));
}

int
lockun_fd(int f)
{
	return(lockf(f,F_ULOCK,0));
}

int
locktest_fd(int f)
{
	return(lockf(f,F_TLOCK,0));
}
