
/*
  $Id: iface.c,v 1.13 2005/08/18 17:26:05 ldv Exp $

  Copyright (C) 2001-2003  Dmitry V. Levin <ldv@altlinux.org>

  Utempter library interface.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#define _GNU_SOURCE	1

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

extern int getresgid (gid_t *rgid, gid_t *egid, gid_t *sgid);

#include "utempter.h"

#define	UTEMPTER_DEFAULT_PATHNAME	LIBEXECDIR "/utempter/utempter"

static const char *utempter_pathname;
static int saved_fd = -1;

static void
__attribute__ ((__noreturn__))
do_child (int master_fd, const char *path, char *const *argv)
{
	if ((dup2 (master_fd, STDIN_FILENO) != STDIN_FILENO)
	    || (dup2 (master_fd, STDOUT_FILENO) != STDOUT_FILENO))
	{
#ifdef	UTEMPTER_DEBUG
		fprintf (stderr, "libutempter: dup2: %s\n", strerror (errno));
#endif
		_exit (EXIT_FAILURE);
	}

	execv (path, argv);
#ifdef	UTEMPTER_DEBUG
	fprintf (stderr, "libutempter: execv: %s\n", strerror (errno));
#endif

	while (EACCES == errno)
	{
		/* try saved group ID */
		gid_t rgid, egid, sgid;

		if (getresgid (&rgid, &egid, &sgid))
			break;

		if (sgid == egid)
			break;

		if (setgid (sgid))
			break;

		execv (path, argv);
		break;
	}

	_exit (EXIT_FAILURE);
}

static int
execute_helper (int master_fd, const char *const argv[])
{
	struct sigaction saved_action, action;

	action.sa_handler = SIG_DFL;
	action.sa_flags = SA_RESTART;
	sigemptyset (&action.sa_mask);

	if (sigaction (SIGCHLD, &action, &saved_action) < 0)
	{
#ifdef	UTEMPTER_DEBUG
		fprintf (stderr, "libutempter: sigaction: %s\n",
			 strerror (errno));
#endif
		return 0;
	} else
	{
		pid_t   child;
		int     status = 1;

		child = fork ();
		if (!child)
		{
			do_child (master_fd, argv[0], (char *const *) argv);
		} else if (child < 0)
		{
#ifdef	UTEMPTER_DEBUG
			fprintf (stderr, "libutempter: fork: %s\n",
				 strerror (errno));
#endif
			goto ret;
		}

		if (TEMP_FAILURE_RETRY (waitpid (child, &status, 0)) < 0)
		{
#ifdef	UTEMPTER_DEBUG
			fprintf (stderr, "libutempter: waitpid: %s\n",
				 strerror (errno));
#endif
			status = 1;
		}

	      ret:
		sigaction (SIGCHLD, &saved_action, 0);
		return !status;
	}
}

/* New interface. */

int
utempter_add_record (int master_fd, const char *hostname)
{
	const char *const args[] =
		{ utempter_pathname ? : UTEMPTER_DEFAULT_PATHNAME, "add",
		  hostname, 0 };
	int     status = execute_helper (master_fd, args);

	if (status)
		saved_fd = master_fd;

	return status;
}

int
utempter_remove_record (int master_fd)
{
	const char *const args[] =
		{ utempter_pathname ? : UTEMPTER_DEFAULT_PATHNAME, "del", 0 };
	int     status = execute_helper (master_fd, args);

	if (master_fd == saved_fd)
		saved_fd = -1;

	return status;
}

int
utempter_remove_added_record (void)
{
	if (saved_fd < 0)
		return 0;
	else
		return utempter_remove_record (saved_fd);
}

void
utempter_set_helper (const char *pathname)
{
	utempter_pathname = pathname;
}

/* Old interface. */

void
addToUtmp (const char *pty, const char *hostname, int master_fd)
{
	utempter_add_record (master_fd, hostname);
}

void
removeFromUtmp (void)
{
	utempter_remove_added_record ();
}

void
removeLineFromUtmp (const char *pty, int master_fd)
{
	utempter_remove_record (master_fd);
}
