/* Copyright (c) 1998 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998.

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

   The GNU C 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the GNU C Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA. */

#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <libintl.h>
#include <locale.h>
#include <pthread.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/un.h>

#include "nscd.h"
#include "dbg_log.h"

/* Socket 0 in the array is named and exported into the file namespace
   as a connection point for clients.  */
static int sock[MAX_NUM_CONNECTIONS];
static int socks_active;
static fd_set read_set;
static pthread_mutex_t sock_lock = PTHREAD_MUTEX_INITIALIZER;


/* Cleanup.  */
void
close_sockets (void)
{
  int i;

  if (debug_flag)
    dbg_log (_("close_sockets called"));

  pthread_mutex_lock (&sock_lock);

  /* Close sockets.  */
  for (i = 0; i < MAX_NUM_CONNECTIONS; ++i)
    if (sock[i] != 0)
      {
	if (close (sock[i]))
	  dbg_log (_("socket [%d|%d] close: %s"), strerror (errno));

	sock[i] = 0;
	--socks_active;
      }

  pthread_mutex_unlock (&sock_lock);
}

void
close_socket (int conn)
{
  if (debug_flag > 2)
    dbg_log (_("close socket (%d|%d)"), conn, sock[conn]);

  pthread_mutex_lock (&sock_lock);

  close (sock[conn]);
  sock[conn] = 0;
  --socks_active;

  pthread_mutex_unlock (&sock_lock);
}

/* Local rountine, assigns a socket to a new connection request.  */
static void
handle_new_connection (void)
{
  int i;

  if (debug_flag > 2)
    dbg_log (_("handle_new_connection"));

  pthread_mutex_lock (&sock_lock);

  if (socks_active < MAX_NUM_CONNECTIONS)
    /* Find a free socket entry to use.  */
    for (i = 1; i < MAX_NUM_CONNECTIONS; ++i)
      {
	if (sock[i] == 0)
	  {
	    if ((sock[i] = accept (sock[0], NULL, NULL)) < 0)
	      {
		dbg_log (_("socket accept: %s"), strerror (errno));
		return;
	      }
	    ++socks_active;
	    FD_SET (sock[i], &read_set);
	    if (debug_flag > 2)
	      dbg_log (_("handle_new_connection used socket %d|%d"), i,
		       sock[i]);
	    break;
	  }
      }
  else
    {
      int black_widow_sock;
      dbg_log (_("Supported number of simultainious connections exceeded"));
      dbg_log (_("Ignoring client connect request"));
      /* There has to be a better way to ignore a connection request,..
	 when I get my hands on a sockets wiz I'll modify this.  */
      black_widow_sock  = accept (sock[0], NULL, NULL);
      close (black_widow_sock);
    }
  pthread_mutex_unlock (&sock_lock);
}

/* Local routine, reads a request off a socket indicated by a selectset.  */
static int
handle_new_request (fd_set read_selects, int **connp, request_header **reqp,
		    char **key)
{
  ssize_t nbytes;
  int i;

  if (debug_flag)
    dbg_log ("handle_new_request");

  /* Find the descriptor.  */
  for (i = 1; i < MAX_NUM_CONNECTIONS; ++i)
    if (FD_ISSET(sock[i], &read_selects))
      break;

  if (debug_flag > 2)
    dbg_log (_("handle_new_request uses socket %d"), i);

  /* Read from it.  */
  nbytes = read (sock[i], *reqp, sizeof (request_header));
  if (nbytes != sizeof (request_header))
    {
      /* Handle non-data read cases.  */
      if (nbytes == 0)
	{
	  /* Close socket down.  */
	  if (debug_flag > 2)
	    dbg_log (_("Real close socket %d|%d"), i, sock[i]);

	  pthread_mutex_lock (&sock_lock);
	  FD_CLR (sock[i], &read_set);
	  close (sock[i]);
	  sock[i] = 0;
	  --socks_active;
	  pthread_mutex_unlock (&sock_lock);
	}
      else
	if (nbytes < 0)
	  {
	    dbg_log (_("Read(%d|%d) error on get request: %s"),
		     i, sock[i], strerror (errno));
	    exit (1);
	  }
	else
	  dbg_log (_("Read, data < request buf size, ignoring data"));

      return -1;
    }
  else
    {
      *key = malloc ((*reqp)->key_len + 1);
      /* Read the key from it */
      nbytes = read (sock[i], *key, (*reqp)->key_len);
      if (nbytes != (*reqp)->key_len)
	{
	  /* Handle non-data read cases.  */
	  if (nbytes == 0)
	    {
	      /* Close socket down.  */
	      if (debug_flag > 2)
		dbg_log (_("Real close socket %d|%d"), i, sock[i]);

	      pthread_mutex_lock (&sock_lock);
	      FD_CLR (sock[i], &read_set);
	      close (sock[i]);
	      sock[i] = 0;
	      --socks_active;
	      pthread_mutex_unlock (&sock_lock);
	    }
	  else
	    if (nbytes < 0)
	      {
		perror (_("Read() error on get request"));
		return 0;
	      }
	    else
	      fputs (_("Read, data < request buf size, ignoring data"),
		     stderr);

	  free (*key);
	  return -1;
	}
      else
	{
	  /* Ok, have a live one, A real data req buf has been obtained.  */
	  (*key)[(*reqp)->key_len] = '\0';
	  **connp = i;
	  return 0;
	}
    }
}

void
get_request (int *conn, request_header *req, char **key)
{
  int i, nr, done = 0;
  fd_set read_selects;

  if (debug_flag)
    dbg_log ("get_request");

  /* loop, processing new connection requests until a client buffer
     is read in on an existing connection.  */
  while (!done)
    {
      /* Set up the socket descriptor mask for the select.
	 copy read_set into the local copy.  */

      FD_ZERO (&read_selects);
      pthread_mutex_lock (&sock_lock);
      for (i = 0; i < MAX_NUM_CONNECTIONS; ++i)
	{
	  if (FD_ISSET (sock[i], &read_set))
	    FD_SET (sock[i], &read_selects);
	}
      pthread_mutex_unlock (&sock_lock);
      /* Poll active connections using select().  */
      nr = select (FD_SETSIZE, &read_selects, NULL, NULL, NULL);
      if (nr <= 0)
	{
	  perror (_("Select new reads"));
	  exit (1);
	}
      if (FD_ISSET (sock[0], &read_selects))
	/* Handle the case of a new connection request on the named socket.  */
	handle_new_connection ();
      else
	{
	  /* Read data from client specific descriptor.  */
	  if (handle_new_request (read_selects, &conn, &req, key) == 0)
	    {
	      FD_CLR (sock[*conn], &read_set);
	      done = 1;
	    }
	}
    } /* While not_done.  */
}

void
init_sockets (void)
{
  struct sockaddr_un sock_addr;

  /* Initialize the connections db.  */
  socks_active = 0;
  FD_ZERO (&read_set);

  /* Create the socket.  */
  sock[0] = socket (AF_UNIX, SOCK_STREAM, 0);
  if (sock[0] < 0)
    {
      perror (_("cannot create socket"));
      exit (1);
    }
  /* Bind a name to the socket.  */
  sock_addr.sun_family = AF_UNIX;
  strcpy (sock_addr.sun_path, _PATH_NSCDSOCKET);
  if (bind (sock[0], (struct sockaddr *) &sock_addr, sizeof (sock_addr)) < 0)
    {
      dbg_log ("%s: %s", _PATH_NSCDSOCKET, strerror (errno));
      exit (1);
    }
  /* Set permissions for the socket.  */
  chmod (_PATH_NSCDSOCKET, 0666);

  /* Set the socket up to accept connections.  */
  if (listen (sock[0], MAX_NUM_CONNECTIONS) < 0)
    {
      perror (_("cannot enable socket to accept connections"));
      exit (1);
    }

  /* Add the socket to the server's set of active sockets.  */
  FD_SET (sock[0], &read_set);
  ++socks_active;
}

void
pw_send_answer (int conn, struct passwd *pwd)
{
  pw_response_header resp;

  resp.version = NSCD_VERSION;
  if (pwd != NULL)
    {
      resp.found = 1;
      resp.pw_name_len = strlen (pwd->pw_name);
      resp.pw_passwd_len = strlen (pwd->pw_passwd);
      resp.pw_uid = pwd->pw_uid;
      resp.pw_gid = pwd->pw_gid;
      resp.pw_gecos_len = strlen (pwd->pw_gecos);
      resp.pw_dir_len = strlen (pwd->pw_dir);
      resp.pw_shell_len = strlen (pwd->pw_shell);
    }
  else
    {
      resp.found = 0;
      resp.pw_name_len = 0;
      resp.pw_passwd_len = 0;
      resp.pw_uid = -1;
      resp.pw_gid = -1;
      resp.pw_gecos_len = 0;
      resp.pw_dir_len = 0;
      resp.pw_shell_len = 0;
    }
  if (sock[conn] == 0)
    {
      dbg_log (_("bad connection id on send response [%d|%d]"),
	       conn, sock[conn]);
      return;
    }

  /* Send response header.  */
  if (write (sock[conn], &resp, sizeof (pw_response_header)) !=
      sizeof (pw_response_header))
    {
      dbg_log (_("write incomplete on send response: %s"), strerror (errno));
      return;
    }

  if (resp.found)
    {
      struct iovec vec[5];

      /* Send pw_name.  */
      vec[0].iov_base = pwd->pw_name;
      vec[0].iov_len = resp.pw_name_len;
      /* Send pw_passwd.  */
      vec[1].iov_base = pwd->pw_passwd;
      vec[1].iov_len = resp.pw_passwd_len;
      /* Send pw_gecos.  */
      vec[2].iov_base = pwd->pw_gecos;
      vec[2].iov_len = resp.pw_gecos_len;
      /* Send pw_dir.  */
      vec[3].iov_base = pwd->pw_dir;
      vec[3].iov_len = resp.pw_dir_len;
      /* Send pw_shell.  */
      vec[4].iov_base = pwd->pw_shell;
      vec[4].iov_len = resp.pw_shell_len;

      if (writev (sock[conn], vec, 5) != (resp.pw_name_len + resp.pw_passwd_len
					  + resp.pw_gecos_len + resp.pw_dir_len
					  + resp.pw_shell_len))
	dbg_log (_("write incomplete on send passwd answer: %s"),
		 strerror (errno));
    }
}

void
pw_send_disabled (int conn)
{
  pw_response_header resp;

  resp.version = NSCD_VERSION;
  resp.found = -1;
  resp.pw_name_len = 0;
  resp.pw_passwd_len = 0;
  resp.pw_uid = -1;
  resp.pw_gid = -1;
  resp.pw_gecos_len = 0;
  resp.pw_dir_len = 0;
  resp.pw_shell_len = 0;

  if (sock[conn] == 0)
    {
      dbg_log (_("bad connection id on send response [%d|%d]"),
	       conn, sock[conn]);
      return;
    }

  /* Send response header.  */
  if (write (sock[conn], &resp, sizeof (pw_response_header))
      != sizeof (pw_response_header))
    dbg_log (_("write incomplete on send response: %s"), strerror (errno));
}

void
gr_send_answer (int conn, struct group *grp)
{
  gr_response_header resp;

  resp.version = NSCD_VERSION;
  if (grp != NULL)
    {
      resp.found = 1;
      resp.gr_name_len = strlen (grp->gr_name);
      resp.gr_passwd_len = strlen (grp->gr_passwd);
      resp.gr_gid = grp->gr_gid;
      resp.gr_mem_len = 0;
      while (grp->gr_mem[resp.gr_mem_len])
	++resp.gr_mem_len;
    }
  else
    {
      resp.found = 0;
      resp.gr_name_len = 0;
      resp.gr_passwd_len = 0;
      resp.gr_gid = -1;
      resp.gr_mem_len = 0;
    }
  if (sock[conn] == 0)
    {
      dbg_log (_("bad connection id on send response [%d|%d]"),
	       conn, sock[conn]);
      return;
    }

  /* Send response header.  */
  if (write (sock[conn], &resp, sizeof (gr_response_header))
      != sizeof (gr_response_header))
    {
      dbg_log (_("write incomplete on send response: %s"), strerror (errno));
      return;
    }

  if (resp.found)
    {
      unsigned int l = 0;

      /* Send gr_name.  */
      if (write (sock[conn], grp->gr_name, resp.gr_name_len)
	  != resp.gr_name_len)
	{
	  dbg_log (_("write incomplete on send response: %s"),
		   strerror (errno));
	  return;
	}
      /* Send gr_passwd.  */
      if (write (sock[conn], grp->gr_passwd, resp.gr_passwd_len)
	  != resp.gr_passwd_len)
	{
	  dbg_log (_("write incomplete on send response: %s"),
		   strerror (errno));
	  return;
	}

      while (grp->gr_mem[l])
	{
	  size_t len = strlen (grp->gr_mem[l]);

	  if (write (sock[conn], &len, sizeof (len)) != sizeof (len))
	    {
	      dbg_log (_("write incomplete on send response: %s"),
		       strerror (errno));
	      return;
	    }
	  if (write (sock[conn], grp->gr_mem[l], len) != len)
	    {
	      dbg_log (_("write incomplete on send response: %s"),
		       strerror (errno));
	      return;
	    }
	  ++l;
	}
    }
}

void
gr_send_disabled (int conn)
{
  gr_response_header resp;

  resp.version = NSCD_VERSION;
  resp.found = -1;
  resp.gr_name_len = 0;
  resp.gr_passwd_len = 0;
  resp.gr_gid = -1;
  resp.gr_mem_len = 0;

  if (sock[conn] == 0)
    {
      dbg_log (_("bad connection id on send gr_disabled response [%d|%d]"),
	       conn, sock[conn]);
      return;
    }

  /* Send response header.  */
  if (write (sock[conn], &resp, sizeof (gr_response_header))
      != sizeof (gr_response_header))
    dbg_log (_("write incomplete on send gr_disabled response: %s"),
	     strerror (errno));
}

void
stat_send (int conn, stat_response_header *resp)
{
  if (sock[conn] == 0)
    {
      dbg_log (_("bad connection id on send stat response [%d|%d]"),
	       conn, sock[conn]);
      return;
    }

  /* send response header.  */
  if (write (sock[conn], resp, sizeof (stat_response_header))
      != sizeof (stat_response_header))
    dbg_log (_("write incomplete on send stat response: %s"),
	     strerror (errno));
}