/*++
/* NAME
/*	sys_compat 3
/* SUMMARY
/*	compatibility routines
/* SYNOPSIS
/*	#include <sys_defs.h>
/*
/*	void	closefrom(int lowfd)
/*	int	lowfd;
/*
/*	const char *strerror(err)
/*	int	err;
/*
/*	int	setenv(name, value, clobber)
/*	const char *name;
/*	const char *value;
/*	int	clobber;
/*
/*	int	seteuid(euid)
/*	uid_t	euid;
/*
/*	int	setegid(egid)
/*	gid_t	euid;
/*
/*	int	mkfifo(path, mode)
/*	char	*path;
/*	int	mode;
/*
/*	int	waitpid(pid, statusp, options)
/*	int	pid;
/*	WAIT_STATUS_T *statusp;
/*	int	options;
/*
/*	int	setsid()
/*
/*	void	dup2_pass_on_exec(int oldd, int newd)
/*
/*	char	*inet_ntop(af, src, dst, size)
/*	int	af;
/*	const void *src;
/*	char	*dst;
/*	size_t	size;
/*
/*	int	inet_pton(af, src, dst)
/*	int	af;
/*	const char *src;
/*	void	*dst;
/* DESCRIPTION
/*	These routines are compiled for platforms that lack the functionality
/*	or that have broken versions that we prefer to stay away from.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#define SYS_COMPAT_INTERNAL
#include "sys_defs.h"

 /*
  * ANSI strerror() emulation
  */
#ifdef MISSING_STRERROR

extern int errno;
extern char *sys_errlist[];
extern int sys_nerr;

#include <vstring.h>

/* strerror - print text corresponding to error */

const char *strerror(int err)
{
    static VSTRING *buf;

    if (err < 0 || err >= sys_nerr) {
	if (buf == 0)
	    buf = vstring_alloc(10);
	vstring_sprintf(buf, "Unknown error %d", err);
	return (vstring_str(buf));
    } else {
	return (sys_errlist[errno]);
    }
}

#endif

 /*
  * setenv() emulation on top of putenv().
  */
#ifdef MISSING_SETENV

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

/* setenv - update or insert environment (name,value) pair */

int     setenv(const char *name, const char *value, int clobber)
{
    char   *cp;

    if (clobber == 0 && getenv(name) != 0)
	return (0);
    if ((cp = malloc(strlen(name) + strlen(value) + 2)) == 0)
	return (1);
    sprintf(cp, "%s=%s", name, value);
    return (putenv(cp));
}

#endif

 /*
  * seteuid() and setegid() emulation, the HP-UX way
  */
#ifdef MISSING_SETEUID
#ifdef HAVE_SETRESUID
#include <unistd.h>

int     seteuid(uid_t euid)
{
    return setresuid(-1, euid, -1);
}

#else
#error MISSING_SETEUID
#endif

#endif

#ifdef MISSING_SETEGID
#ifdef HAVE_SETRESGID
#include <unistd.h>

int     setegid(gid_t egid)
{
    return setresgid(-1, egid, -1);
}

#else
#error MISSING_SETEGID
#endif

#endif

 /*
  * mkfifo() emulation - requires superuser privileges
  */
#ifdef MISSING_MKFIFO

#include <sys/stat.h>

int     mkfifo(char *path, int mode)
{
    return mknod(path, (mode & ~_S_IFMT) | _S_IFIFO, 0);
}

#endif

 /*
  * waitpid() emulation on top of Berkeley UNIX wait4()
  */
#ifdef MISSING_WAITPID
#ifdef HAS_WAIT4

#include <sys/wait.h>
#include <errno.h>

int     waitpid(int pid, WAIT_STATUS_T *status, int options)
{
    if (pid == -1)
	pid = 0;
    return wait4(pid, status, options, (struct rusage *) 0);
}

#else
#error MISSING_WAITPID
#endif

#endif

 /*
  * setsid() emulation, the Berkeley UNIX way
  */
#ifdef MISSING_SETSID

#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifdef TIOCNOTTY

#include <msg.h>

int     setsid(void)
{
    int     p = getpid();
    int     fd;

    if (setpgrp(p, p))
	return -1;

    fd = open("/dev/tty", O_RDONLY, 0);
    if (fd >= 0 || errno != ENXIO) {
	if (fd < 0) {
	    msg_warn("open /dev/tty: %m");
	    return -1;
	}
	if (ioctl(fd, TIOCNOTTY, 0)) {
	    msg_warn("ioctl TIOCNOTTY: %m");
	    return -1;
	}
	close(fd);
    }
    return 0;
}

#else
#error MISSING_SETSID
#endif

#endif

 /*
  * dup2_pass_on_exec() - dup2() and clear close-on-exec flag on the result
  */
#ifdef DUP2_DUPS_CLOSE_ON_EXEC

#include "iostuff.h"

int     dup2_pass_on_exec(int oldd, int newd)
{
    int     res;

    if ((res = dup2(oldd, newd)) >= 0)
	close_on_exec(newd, PASS_ON_EXEC);

    return res;
}

#endif

#ifndef HAS_CLOSEFROM

#include <unistd.h>
#include <errno.h>
#include <iostuff.h>

/* closefrom() - closes all file descriptors from the given one up */

int     closefrom(int lowfd)
{
    int     fd_limit = open_limit(0);
    int     fd;

    /*
     * lowfrom does not have an easy to determine upper limit. A process may
     * have files open that were inherited from a parent process with a less
     * restrictive resource limit.
     */
    if (lowfd < 0) {
	errno = EBADF;
	return (-1);
    }
    if (fd_limit > 500)
	fd_limit = 500;
    for (fd = lowfd; fd < fd_limit; fd++)
	(void) close(fd);

    return (0);
}

#endif

#ifdef MISSING_INET_NTOP

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/* inet_ntop - convert binary address to printable address */

const char *inet_ntop(int af, const void *src, char *dst, size_t size)
{
    const unsigned char *addr;
    char    buffer[sizeof("255.255.255.255")];
    int     len;

    if (af != AF_INET) {
	errno = EAFNOSUPPORT;
	return (0);
    }
    addr = (const unsigned char *) src;
#if (CHAR_BIT > 8)
    sprintf(buffer, "%d.%d.%d.%d", addr[0] & 0xff,
	    addr[1] & 0xff, addr[2] & 0xff, addr[3] & 0xff);
#else
    sprintf(buffer, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
#endif
    if ((len = strlen(buffer)) >= size) {
	errno = ENOSPC;
	return (0);
    } else {
	memcpy(dst, buffer, len + 1);
	return (dst);
    }
}

#endif

#ifdef MISSING_INET_PTON

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif

/* inet_pton - convert printable address to binary address */

int     inet_pton(int af, const char *src, void *dst)
{
    struct in_addr addr;

    /*
     * inet_addr() accepts a wider range of input formats than inet_pton();
     * the former accepts 1-, 2-, or 3-part dotted addresses, while the
     * latter requires dotted quad form.
     */
    if (af != AF_INET) {
	errno = EAFNOSUPPORT;
	return (-1);
    } else if ((addr.s_addr = inet_addr(src)) == INADDR_NONE
	       && strcmp(src, "255.255.255.255") != 0) {
	return (0);
    } else {
	memcpy(dst, (char *) &addr, sizeof(addr));
	return (1);
    }
}

#endif

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

#include "msg.h"

/*
 * Provide hostname as FQDN
 */
int cygwin_gethostname(char *name, size_t size)
{
    if (gethostname(name, size))
	return -1;
    if (strchr(name, '.'))
	return 0;
    size_t hlen = strlen(name);
    if (hlen + 32 > size)
	return 0;

    if (getdomainname(name+hlen+1, size-hlen-1))
	return 0;
    if (!name[hlen+1])
	return 0;
    name[hlen] = '.';
    return 0;
}

/*
 * Disable SO_PEERCRED handshake in Cygwin AF_UNIX emulation
 */
int cygwin_socket(int domain, int type, int protocol)
{
    int sd = socket(domain, type, protocol);
    if (sd != -1 && domain == AF_UNIX && type == SOCK_STREAM) {
	if (setsockopt(sd, SOL_SOCKET, SO_PEERCRED, NULL, 0))
	    sd = -1;
    }
    return sd;
}

/*
 * Cygwin root uid/gid emulation
 */

static uid_t sw_root_uid = 18;
static gid_t sw_root_gid = 544;
static uid_t pw_root_uid = 18;
static uid_t pw_root_gid = 544;
static uid_t emul_ruid = 0, emul_euid = 0;
static uid_t emul_rgid = 0, emul_egid = 0;
static int emul_mode = 0;

static int is_init = 0;
static int cygwin_debug = 0;

#define MSG_DEBUG(args...)	(cygwin_debug     || msg_verbose > 1 ? msg_info("CYGWIN: " args) : (void)0)
#define MSG_DEBUGX(args...)	(cygwin_debug > 1 || msg_verbose > 1 ? msg_info("CYGWIN: " args) : (void)0)

#define CYGWIN_POSTFIX_DEBUG_ENV "CYGWIN_POSTFIX_DEBUG"
#define CYGWIN_POSTFIX_UGID_ENV  "CYGWIN_POSTFIX_UGID"

static void get_debug_env()
{
    const char *env = getenv(CYGWIN_POSTFIX_DEBUG_ENV);
    if (!env)
	return;
    cygwin_debug = atoi(env);
    if (cygwin_debug < 0)
	cygwin_debug = 0;
}

static int get_ugid_env(const char *path)
{
    const char *env = getenv(CYGWIN_POSTFIX_UGID_ENV);
    if (!env)
	return 0;

    MSG_DEBUGX("%s: " CYGWIN_POSTFIX_UGID_ENV "='%s'", path, env);

    unsigned uid, gid, uid_mode, gid_mode;
    int nc = -1;
    sscanf(env, "%u,%u,%u,%u%n", &uid, &gid, &uid_mode, &gid_mode, &nc);

    if (!(nc == (int)strlen(env) && uid && gid && uid_mode <= 2 && gid_mode <= 2))
	msg_fatal("Syntax error in environment variable CYGWIN_POSTFIX_UGID='%s'", env);

    sw_root_uid = uid;
    sw_root_gid = gid;
    emul_ruid = (uid_mode <= 1 ? 0 : uid);
    emul_euid = (uid_mode == 0 ? 0 : uid);
    emul_rgid = (gid_mode <= 1 ? 0 : gid);
    emul_egid = (gid_mode == 0 ? 0 : gid);
    return 1;
}

static void set_ugid_env()
{
    int saved_errno = errno;
    /*
     * Use static buffer with putenv() to avoid memory leak from setenv().
     * Call putenv() each time because 'import_environment' filter may
     * remove the entry between calls.
     * Adding the variable to 'import_environment' is not needed then.
     * Filter 'export_environment' is only used for external commands.
     */
    static char env[100] = CYGWIN_POSTFIX_UGID_ENV "=";
    unsigned offs = sizeof(CYGWIN_POSTFIX_UGID_ENV);

    snprintf(env+offs, sizeof(env), "%u,%u,%u,%u", sw_root_uid, sw_root_gid,
	    (!emul_ruid ? (!emul_euid ? 0 : 1) : 2),
	    (!emul_rgid ? (!emul_egid ? 0 : 1) : 2) );
    putenv(env);
    errno = saved_errno;
}

static int find_admin_gid()
{
    int size = getgroups(0, NULL);
    if (size <= 0)
	return -1;
    gid_t groups[size];
    getgroups(size, groups);
    int i;
    for (i = 0; i < size; i++) {
	if (!groups[i] || groups[i] == 544)
	    return groups[i];
    }
    return -1;
}

static int is_postfix_master(const char *path)
{
    const char suffix[] = "/master";
    unsigned suflen = sizeof(suffix)-1;
    unsigned len = strlen(path);
    if (len <= suflen)
	return 0;
    if (strcmp(path + len - suflen, suffix))
	return 0;
    return 1;
}

static void do_init()
{
    int saved_errno = errno;
    is_init = 1;
    get_debug_env();

    char path[256] = "";
    if (readlink("/proc/self/exe", path, sizeof(path)) < 0)
	msg_fatal("/proc/self/exe: readlink() failed: %s", strerror(errno));

    int admin_gid;
    if (get_ugid_env(path)) {
	// Inherited root <> mail_owner switch emulation state from parent
	emul_mode = 2;
    }
    else if ((admin_gid = find_admin_gid()) >= 0) {
	// Do actual root <> user switch
	sw_root_uid = pw_root_uid = getuid();
	if (setgid(admin_gid))
	    msg_fatal("setgid(%d) failed: %m", admin_gid);
	sw_root_gid = pw_root_gid = getgid();
    }
    else if (is_postfix_master(path)) {
	// Master without admin rights, emulate root <> mail_owner switch
	sw_root_uid = getuid();
	sw_root_gid = getgid();
	// Inherit to child processes via environment
	emul_mode = 1;
	set_ugid_env();
    }

    MSG_DEBUGX("%s: sw/pw_root_uid=%d/%d, sw/pw_root_gid=%d/%d",
	    path, sw_root_uid, pw_root_uid, sw_root_gid, pw_root_gid);
    MSG_DEBUGX("%s: emul_r/euid=%d/%d, emul_r/egid=%d/%d",
	    path, emul_ruid, emul_euid, emul_rgid, emul_egid);
    MSG_DEBUGX("%s: emul_mode=%d, r/euid=%d/%d, r/egid=%d/%d", path, emul_mode,
	    cygwin_getuid(), cygwin_geteuid(), cygwin_getgid(), cygwin_getegid());
    errno = saved_errno;
}

static inline void init()
{
    if (!is_init)
	do_init();
}

static inline void update_ugid_env()
{
    if (emul_mode)
	set_ugid_env();
}

uid_t cygwin_getuid(void)
{
    init();
    int uid = getuid();
    if (uid == sw_root_uid)
	return emul_ruid;
    return uid;
}

uid_t cygwin_geteuid(void)
{
    init();
    int uid = geteuid();
    if (uid == sw_root_uid)
	return emul_euid;
    return uid;
}

gid_t cygwin_getgid(void)
{
    init();
    int gid = getgid();
    if (gid == sw_root_gid)
	return emul_rgid;
    return gid;
}

gid_t cygwin_getegid(void)
{
    init();
    int gid = getegid();
    if (gid == sw_root_gid)
	return emul_egid;
    return gid;
}

int cygwin_setuid(uid_t uid)
{
    init();
    if (!uid && emul_ruid) {
	MSG_DEBUG("setuid(%u): EPERM", uid);
	errno = EPERM;
	return -1;
    }
    if (!uid || uid == sw_root_uid) {
	MSG_DEBUG("setuid(%u=>%u)", uid, sw_root_uid);
	if (setuid(sw_root_uid))
	    return -1;
	emul_ruid = emul_euid = uid;
	update_ugid_env();
	return 0;
    }
    MSG_DEBUG("setuid(%u)", uid);
    return setuid(uid);
}

int cygwin_seteuid(uid_t uid)
{
    init();
    if (!uid && emul_ruid) {
	MSG_DEBUG("seteuid(%u): EPERM", uid);
	errno = EPERM;
	return -1;
    }
    if (!uid || uid == sw_root_uid) {
	MSG_DEBUG("seteuid(%u=>%u)", uid, sw_root_uid);
	if (seteuid(sw_root_uid))
	    return -1;
	emul_euid = uid;
	update_ugid_env();
	return 0;
    }
    MSG_DEBUG("seteuid(%u)", uid);
    return seteuid(uid);
}

int cygwin_setgid(gid_t gid)
{
    init();
    if (!gid && emul_rgid) {
	MSG_DEBUG("setgid(%u): EPERM", gid);
	errno = EPERM;
	return -1;
    }
    if (!gid || gid == sw_root_gid) {
	MSG_DEBUG("setgid(%u=>%u)", gid, sw_root_gid);
	if (setgid(sw_root_gid))
	    return -1;
	emul_rgid = emul_egid = gid;
	update_ugid_env();
	return 0;
    }
    MSG_DEBUG("setgid(%u)", gid);
    return setgid(gid);
}

int cygwin_setegid(gid_t gid)
{
    init();
    if (!gid && emul_rgid) {
	MSG_DEBUG("setegid(%u) EPERM", gid);
	errno = EPERM;
	return -1;
    }
    if (!gid || gid == sw_root_gid) {
	MSG_DEBUG("setegid(%u=>%u)", gid, sw_root_gid);
	if (setegid(sw_root_gid))
	    return -1;
	emul_egid = gid;
	update_ugid_env();
	return 0;
    }
    MSG_DEBUG("setegid(%u)", gid);
    return setegid(gid);
}

int cygwin_setgroups(int size, const gid_t *groups)
{
    init();
    if (size != 1 || *groups != cygwin_getegid()) {
	MSG_DEBUG("setgroups(%d, {%u, ...}): EPERM", size, *groups);
	errno = EPERM;
	return -1;
    }
    if (!*groups || *groups == sw_root_gid) {
	MSG_DEBUG("setgroups(1, {%u=>%u})", *groups, sw_root_gid);
	return setgroups(1, &sw_root_gid);
    }
    MSG_DEBUG("setgroups(1, {%u})", *groups);
    return setgroups(1, groups);
}

static inline void map_root_uid(uid_t *uid)
{
    if (*uid == 18 || *uid == 544 || *uid == pw_root_uid)
	*uid = 0;
}

static inline void map_root_gid(gid_t *gid)
{
    if (*gid == 18 || *gid == 544 || *gid == pw_root_gid)
	*gid = 0;
}

struct passwd *cygwin_getpwnam(const char *name)
{
    init();
    struct passwd *pwd = getpwnam(name);
    if (!pwd)
	return NULL;
    map_root_uid(&pwd->pw_uid);
    map_root_gid(&pwd->pw_gid);
    return pwd;
}

struct passwd *cygwin_getpwuid(uid_t uid)
{
    init();
    if (!uid)
	uid = pw_root_uid;
    struct passwd *pwd = getpwuid(uid);
    if (!pwd)
	return NULL;
    map_root_uid(&pwd->pw_uid);
    map_root_gid(&pwd->pw_gid);
    return pwd;
}

struct group *cygwin_getgrnam(const char *name)
{
    init();
    struct group *grp = getgrnam(name);
    if (!grp)
	return NULL;
    map_root_gid(&grp->gr_gid);
    return grp;
}

struct group *cygwin_getgrgid(gid_t gid)
{
    init();
    if (!gid)
	gid = pw_root_gid;
    struct group *grp = getgrgid(gid);
    if (!grp)
	return NULL;
    map_root_gid(&grp->gr_gid);
    return grp;
}

int cygwin_stat(const char *path, struct stat *st)
{
    init();
    if (stat(path, st))
	return -1;
    map_root_uid(&st->st_uid);
    map_root_gid(&st->st_gid);
    return 0;
}

int cygwin_lstat(const char *path, struct stat *st)
{
    init();
    if (lstat(path, st))
	return -1;
    map_root_uid(&st->st_uid);
    map_root_gid(&st->st_gid);
    return 0;
}

int cygwin_fstat(int fd, struct stat *st)
{
    init();
    if (fstat(fd, st))
	return -1;
    map_root_uid(&st->st_uid);
    map_root_gid(&st->st_gid);
    return 0;
}

#ifdef ALLOW_POSTSUPER_RUN_AS_ROOT

void try_set_ugid(uid_t uid, gid_t gid)
{
    if (cygwin_geteuid() != 0)
	msg_fatal("try_set_ugid(): euid = %u", cygwin_geteuid());
    if (cygwin_setgid(gid) || cygwin_setuid(uid)) {
	MSG_DEBUG("try_set_ugid(%u, %u) failed, continue with euid=%u, egid=%u", uid, gid,
		  cygwin_geteuid(), cygwin_getegid());
	return;
    }
    cygwin_setgroups(1, &gid);
    MSG_DEBUG("try_set_ugid(%u, %u)", uid, gid);
}

#endif /* ALLOW_POSTSUPER_RUN_AS_ROOT */

#endif /* CYGWIN2x */
