/*-
 * SSLsplit - transparent SSL/TLS interception
 * https://www.roe.ch/SSLsplit
 *
 * Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER 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 COPYRIGHT HOLDER 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.
 */

#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/file.h>
#include <sys/user.h>

#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_var.h>
#include <arpa/inet.h>
#endif /* __FreeBSD__ */

#include "proc.h"

#include "log.h"
#include "attrib.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#ifdef HAVE_DARWIN_LIBPROC
#include <libproc.h>
#endif /* HAVE_DARWIN_LIBPROC */


/*
 * Local process lookup.
 */


#ifdef __FreeBSD__

/*
 * Get the list of open files from the kernel and do basic consistency checks.
 * If successful, returns 0, and *pxfiles will receive a pointer to the
 * received xfiles structure and *pnxfiles the number of file records in it.
 * If unsuccessful, returns -1 and *pxfiles will be NULL.
 * Caller is responsible to free() *pxfiles after use.
 */
static int
proc_freebsd_getfiles(struct xfile **pxfiles, int *pnxfiles)
{
	int mib[4];
	size_t sz;

	mib[0] = CTL_KERN;
	mib[1] = KERN_FILE;
	mib[2] = mib[3] = 0;

	for (;;) {
		if (sysctl(mib, 2, NULL, &sz, NULL, 0) < 0) {
			*pxfiles = NULL;
			return -1;
		}
		if (!(*pxfiles = malloc(sz))) {
			return -1;
		}
		if (sysctl(mib, 2, *pxfiles, &sz, NULL, 0) < 0) {
			free(*pxfiles);
			if (errno == ENOMEM)
				continue;
			*pxfiles = NULL;
			return -1;
		}
		break;
	}

	if (sz > 0 && (*pxfiles)->xf_size != sizeof **pxfiles) {
		log_err_printf("struct xfile size mismatch\n");
		return -1;
	}
	*pnxfiles = sz / sizeof **pxfiles;

	return 0;
}

/*
 * Get the list of active TCP connections and do basic consistency checks.
 * If successful, returns 0, and *pxig will receive a pointer to the
 * received data structure, *pexig a pointer to the end of the buffer.
 * If unsuccessful, returns -1 and *pxig will be NULL.
 * Caller is responsible to free() *pxig after use.
 */
static int
proc_freebsd_gettcppcblist(struct xinpgen **pxig, struct xinpgen **pexig)
{
	int mib[4];
	size_t sz;
	int retry = 5;

	mib[0] = CTL_NET;
	mib[1] = PF_INET;
	mib[2] = IPPROTO_TCP;
	mib[3] = TCPCTL_PCBLIST;
	do {
		for (;;) {
			if (sysctl(mib, 4, NULL, &sz, NULL, 0) < 0) {
				*pxig = NULL;
				return -1;
			}
			if (!(*pxig = malloc(sz))) {
				return -1;
			}
			if (sysctl(mib, 4, *pxig, &sz, NULL, 0) < 0) {
				free(*pxig);
				if (errno == ENOMEM)
					continue;
				*pxig = NULL;
				return -1;
			}
			break;
		}

		*pexig = (struct xinpgen *)(void *)
		         ((char *)(*pxig) + sz - sizeof(**pexig));
		if ((*pxig)->xig_len != sizeof(**pxig) ||
		    (*pexig)->xig_len != sizeof(**pexig)) {
			log_err_printf("struct xinpgen size mismatch\n");
			free(*pxig);
			*pxig = NULL;
			return -1;
		}
	} while ((*pxig)->xig_gen != (*pexig)->xig_gen && retry--);

	/* check if first and last record are from same generation */
	if ((*pxig)->xig_gen != (*pexig)->xig_gen) {
		log_err_level_printf(LOG_WARNING, "Data inconsistent "
		               "(xig->xig_gen != exig->xig_gen)\n");
	}

	return 0;
}

int
proc_freebsd_pid_for_addr(pid_t *result, struct sockaddr *src_addr,
                          UNUSED socklen_t src_addrlen)
{
	struct xfile *xfiles;
	int nxfiles;
	struct xfile *xf;

	struct xinpgen *xig, *exig, *txig;
	struct xtcpcb *xtp;
#if __FreeBSD_version >= 1200026
	struct xinpcb *inp;
#else
	struct inpcb *inp;
#endif
	struct xsocket *so;

	if (proc_freebsd_getfiles(&xfiles, &nxfiles) == -1) {
		return -1;
	}

	if (proc_freebsd_gettcppcblist(&xig, &exig) == -1) {
		free(xfiles);
		return -1;
	}

	for (txig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len);
	     txig < exig;
	     txig = (struct xinpgen *)(void *)((char *)txig + txig->xig_len)) {
		xtp = (struct xtcpcb *)txig;
		if (xtp->xt_len != sizeof *xtp) {
			free(xfiles);
			free(xig);
			return -1;
		}
		inp = &xtp->xt_inp;
#if __FreeBSD_version >= 1200026
		so = &inp->xi_socket;
#else
		so = &xtp->xt_socket;
#endif

		if (!(so->so_state & SS_ISCONNECTED))
			/* we are only interested in connected sockets */
			continue;

		if ((inp->inp_vflag & INP_IPV4) &&
		    (src_addr->sa_family == AF_INET)) {
			struct sockaddr_in *src_sai =
					(struct sockaddr_in *)src_addr;

			if (src_sai->sin_addr.s_addr != inp->inp_laddr.s_addr) {
				continue;
			}

			if (src_sai->sin_port != inp->inp_lport) {
				continue;
			}
		} else if ((inp->inp_vflag & INP_IPV6) &&
		          (src_addr->sa_family == AF_INET6)) {
			struct sockaddr_in6 *src_sai =
					(struct sockaddr_in6 *)src_addr;

			if (memcmp(src_sai->sin6_addr.s6_addr, inp->in6p_laddr.s6_addr, 16) != 0) {
				continue;
			}

			if (src_sai->sin6_port != inp->inp_lport) {
				continue;
			}
		} else {
			/* other address family */
			continue;
		}

		/* valid match */

		/* only do this if we have a match */
		xf = NULL;
		for (int i = 0; i < nxfiles; ++i) {
			if (so->xso_so == xfiles[i].xf_data) {
				/* there can be several processes sharing a
				 * connected socket file descriptor */
				xf = &xfiles[i];
			}
		}
		if (!xf)
			continue;
		*result = xf->xf_pid;
		break;
	}

	free(xfiles);
	free(xig);
	return 0;
}

int
proc_freebsd_get_info(pid_t pid, char **path, uid_t *uid, gid_t *gid) {
	static struct kinfo_proc proc;
	size_t len;
	int mib[4];
	char buf[PATH_MAX];

	mib[0] = CTL_KERN;
	mib[1] = KERN_PROC;
	mib[2] = KERN_PROC_PATHNAME;
	mib[3] = (int)pid;
	len = sizeof(buf);
	if (sysctl(mib, 4, buf, &len, NULL, 0) == -1) {
		if (errno != ESRCH) {
			log_err_printf("Failed to get proc pathname: %s (%i)",
			               strerror(errno), errno);
		}
		*path = NULL;
	} else {
		*path = strdup(buf);
	}

	mib[0] = CTL_KERN;
	mib[1] = KERN_PROC;
	mib[2] = KERN_PROC_PID;
	mib[3] = (int)pid;
	len = sizeof proc;
	if (sysctl(mib, 4, &proc, &len, NULL, 0) == -1) {
		if (errno != ESRCH) {
			log_err_printf("Failed to get proc info: %s (%i)",
			               strerror(errno), errno);
		}
		*uid = -1;
		*gid = -1;
	} else {
		if (*path == NULL)
			*path = strdup(proc.ki_comm);
		*uid = proc.ki_uid;
		*gid = proc.ki_groups[0];
	}

	return 0;
}

#endif /* __FreeBSD__ */


#ifdef HAVE_DARWIN_LIBPROC

int
proc_darwin_pid_for_addr(pid_t *result, struct sockaddr *src_addr,
                         UNUSED socklen_t src_addrlen)
{
	pid_t *pids = NULL;
	struct proc_fdinfo *fds = NULL;
	int ret = -1;

	/* default result if no pid matches */
	*result = -1;

	/* iterate over all pids to find a matching socket */
	int pid_count = proc_listallpids(NULL, 0);
	if (pid_count <= 0)
		goto errout1;
	pids = malloc(sizeof(pid_t) * pid_count);
	if (!pids) {
		goto errout1;
	}

	pid_count = proc_listallpids(pids, sizeof(pid_t) * pid_count);
	if (pid_count <= 0)
		goto errout2;

	for (int i = 0; i < pid_count; i++) {
		pid_t pid = pids[i];

		/* fetch fd info for this pid */
		int fd_count = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
		if (fd_count <= 0) {
			/* failed to fetch pidinfo; process may have exited */
			continue;
		}

		if (fds) {
			free(fds);
		}
		fds = malloc(PROC_PIDLISTFD_SIZE * fd_count);
		if (!fds) {
			goto errout2;
		}
		fd_count = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds,
		                        sizeof(fds[0]) * fd_count);

		/* look for a matching socket file descriptor */
		for (int j = 0; j < fd_count; j++) {
			struct proc_fdinfo *fd = &fds[j];
			struct socket_fdinfo sinfo;

			if (fd->proc_fdtype != PROX_FDTYPE_SOCKET) {
				continue;
			}

			if (proc_pidfdinfo(pid, fd->proc_fd,
			                   PROC_PIDFDSOCKETINFO,
			                   &sinfo,
			                   sizeof(struct socket_fdinfo)) <= 0) {
				/* process may have exited or socket may have
				 * been released. */
				continue;
			}

			if (sinfo.psi.soi_kind != SOCKINFO_TCP) {
				continue;
			}

			uint16_t sock_lport = sinfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport;
			if (sinfo.psi.soi_family == AF_INET &&
			    src_addr->sa_family == AF_INET) {
				struct sockaddr_in *src_sai = (struct sockaddr_in *)src_addr;

				if (src_sai->sin_addr.s_addr != sinfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_46.i46a_addr4.s_addr) {
					continue;
				}

				if (src_sai->sin_port != sock_lport) {
					continue;
				}
			} else if (sinfo.psi.soi_family == AF_INET6 &&
			           src_addr->sa_family == AF_INET6) {
				struct sockaddr_in6 *src_sai = (struct sockaddr_in6 *)src_addr;

				if (memcmp(src_sai->sin6_addr.s6_addr, sinfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_6.s6_addr, 16) != 0) {
					continue;
				}

				if (src_sai->sin6_port != sock_lport) {
					continue;
				}
			} else {
				/* other address family */
				continue;
			}

			/* valid match */
			*result = pid;
			goto success;
		}
	}

success:
	ret = 0;
	free(fds);
errout2:
	free(pids);
errout1:
	return ret;
}

/*
 * Fetch process info for the given pid.
 * On success, returns 0 and fills in path, uid, and gid.
 * Caller must free returned path string.
 * Returns -1 on failure, or if unsupported on this platform.
 */
int
proc_darwin_get_info(pid_t pid, char **path, uid_t *uid, gid_t *gid) {
	/* fetch process structure */
	struct proc_bsdinfo bsd_info;
	if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsd_info,
	                 sizeof(bsd_info)) <= 0) {
		return -1;
	}

	*uid = bsd_info.pbi_uid;
	*gid = bsd_info.pbi_gid;

	/* fetch process path */
	*path = malloc(PROC_PIDPATHINFO_MAXSIZE);
	if (!*path) {
		return -1;
	}
	int path_len = proc_pidpath(pid, *path, PROC_PIDPATHINFO_MAXSIZE);
	if (path_len <= 0) {
		free(*path);
		*path = NULL;
		return -1;
	}

	return 0;
}

#endif /* HAVE_DARWIN_LIBPROC */

/* vim: set noet ft=c: */



