/* Authentication server
   Copyright (C) 1992, 1993, 1994 Free Software Foundation

This file is part of the GNU Hurd.

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

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

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

#include "auth_S.h"
#include "auth_reply_U.h"
#include <hurd/startup.h>
#include <mach/error.h>
#include <errno.h>
#include <mach/mig_errors.h>
#include <mach/notify.h>
#include <stdlib.h>
#include <hurd.h>
#include <mach.h>
#include <string.h>

/* This represents a request from a call to auth_user_authenticate
   which is waiting for the corresponding call.  */
struct saved_user
{
  struct saved_user *next;	/* hash link */
  
  mach_port_t rendezvous;	/* rendezvous port */
  mach_port_t rendezvous2;	/* secondary rendezvous port */

  struct apt *userid;		/* to be passed to server */
  
  mach_port_t reply;		/* where to send the answer */
  mach_msg_type_name_t reply_type; /* type of reply port */
};

/* This represents a request from a call to auth_server_authenticate
   which is waiting for the corresponding call.  */
struct saved_server
{
  struct saved_server *next;	/* hash link */
  
  mach_port_t rendezvous;	/* rendezvous port */
  mach_port_t rendezvous2;	/* secondary rendezvous port */

  struct apt *server;		/* who made the call? */
  mach_port_t passthrough;	/* new port to be given to user */

  mach_port_t reply;		/* where to send the answer */
  mach_msg_type_name_t reply_type; /* type of reply port */
};

struct saved_user *userlist;
struct saved_server *serverlist;

struct apt
{
  mach_port_t port;
  uid_t *gen_uids, *aux_uids;
  uid_t *gen_gids, *aux_gids;
  int ngen_uids, naux_uids, ngen_gids, naux_gids;
  struct apt *next, **prevp;
};

struct apt *allapts;

struct apt *getauthstruct (void);
void auth_nosenders (struct apt *);

mach_port_t auth_pset;

/* Our version number */
char *auth_version = "0.0 pre-alpha";


/* Demultiplex an incoming message. */
int
request_server (mach_msg_header_t *inp,
		mach_msg_header_t *outp)
{
  extern int auth_server (), notify_server ();
  
  return (auth_server (inp, outp) 
	  || notify_server (inp, outp));
  
}

void
main (int argc, char **argv)
{
  mach_port_t boot;
  struct apt *firstauth;
  process_t proc;
  extern int mach_msg_server ();
  mach_port_t hostpriv, masterdev;
  
  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_PORT_SET,
		      &auth_pset);

  task_get_bootstrap_port (mach_task_self (), &boot);

  firstauth = getauthstruct ();
  firstauth->gen_uids = malloc (sizeof (uid_t) * 1);
  firstauth->aux_uids = malloc (sizeof (uid_t) * 2);
  firstauth->gen_gids = malloc (sizeof (uid_t) * 1);
  firstauth->aux_gids = malloc (sizeof (uid_t) * 2);
  firstauth->gen_uids[0] = 0;
  firstauth->aux_uids[0] = firstauth->aux_uids[1] = 0;
  firstauth->gen_gids[0] = 0;
  firstauth->aux_gids[0] = firstauth->aux_gids[1] = 0;
  firstauth->ngen_uids = firstauth->ngen_gids = 1;
  firstauth->naux_uids = firstauth->naux_gids = 2;

  startup_authinit (boot, firstauth->port, MACH_MSG_TYPE_MAKE_SEND, &proc);
  proc_getprivports (proc, &hostpriv, &masterdev);
  proc_register_version (proc, hostpriv, "auth", HURD_RELEASE, auth_version);
  mach_port_deallocate (mach_task_self (), masterdev);
  _hurd_port_set (&_hurd_ports[INIT_PORT_PROC], proc);
  _hurd_proc_init (argv);

  /* Init knows intimately that we will be ready for messages
     as soon as this returns. */
  startup_essential_task (boot, mach_task_self (), MACH_PORT_NULL, "auth",
			  hostpriv);
  mach_port_deallocate (mach_task_self (), boot);
  mach_port_deallocate (mach_task_self (), hostpriv);

  while (1)
    mach_msg_server (request_server, vm_page_size * 2, auth_pset);
}


/* Server routines: */

/* Called when an auth port has no more senders. */
error_t
_S_do_mach_notify_no_senders (mach_port_t notify,
			      mach_port_mscount_t mscount)
{
  auth_nosenders (convert_auth_to_apt (notify));
  return 0;
}

/* A given auth handle has no more senders; deallocate all state referring
   to this handle.  */
void
auth_nosenders (struct apt *auth)
{
  /* Remove all references to this from the saved lists */
  {
    struct saved_user *s, **prevp, *tmp;
    for (s = userlist, prevp = &userlist; s;)
      {
	if (s->userid == auth)
	  {
	    *prevp = s->next;
	    tmp = s;
	    prevp = &s->next;
	    s = s->next;
	    
	    mach_port_deallocate (mach_task_self (), tmp->rendezvous);
	    mach_port_deallocate (mach_task_self (), tmp->reply);
	    free (tmp);
	  }
	else
	  {
	    prevp = &s->next;
	    s = s->next;
	  }
      }
  }
  {
    struct saved_server *s, **prevp, *tmp;
    for (s = serverlist, prevp = &serverlist; s;)
      {
	if (s->server == auth)
	  {
	    *prevp = s->next;
	    tmp = s;
	    prevp = &s->next;
	    s = s->next;
	    
	    mach_port_deallocate (mach_task_self (), tmp->rendezvous);
	    mach_port_deallocate (mach_task_self (), tmp->reply);
	    if (tmp->passthrough)
	      mach_port_deallocate (mach_task_self (), tmp->passthrough);
	    free (tmp);
	  }
	else
	  {
	    prevp = &s->next;
	    s = s->next;
	  }
      }
  }

  /* Take off allapts list */
  *auth->prevp = auth->next;
  if (auth->next)
    auth->next->prevp = auth->prevp;

  /* Delete its storage and port. */
  mach_port_mod_refs (mach_task_self (), auth->port, 
		      MACH_PORT_RIGHT_RECEIVE, -1);
  free (auth->gen_uids);
  free (auth->aux_uids);
  free (auth->gen_gids);
  free (auth->aux_gids);
  free (auth);
}


/* Return true iff TEST is a genuine or auxiliary group id in AUTH. */
inline int
groupmember (gid_t test, 
	     struct apt *auth)
{
  int i;
  
  for (i = 0; i < auth->ngen_gids; i++)
    if (test == auth->gen_gids[i])
      return 1;
  for (i = 0; i < auth->naux_gids; i++)
    if (test == auth->aux_gids[i])
      return 1;
  return 0;
}

/* Return true iff TEST is a genuine or auxiliary uid in AUTH. */
inline int
isuid (uid_t test,
       struct apt *auth)
{
  int i;
  
  for (i = 0; i < auth->ngen_uids; i++)
    if (test == auth->gen_uids[i])
      return 1;
  for (i = 0; i < auth->naux_uids; i++)
    if (test == auth->aux_uids[i])
      return 1;
  return 0;
}

/* Return true if the the given auth handle is root (uid 0).  */
inline int
isroot (struct apt *idblock)
{
  return isuid (0, idblock);
}

/* Allocate a new auth handle, complete with port. */
struct apt *
getauthstruct ()
{
  struct apt *newauth;
  mach_port_t unused;

  newauth = malloc (sizeof (struct apt));
  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE,
		      &newauth->port);
  mach_port_request_notification (mach_task_self (), newauth->port,
				  MACH_NOTIFY_NO_SENDERS, 1, newauth->port,
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &unused);
  mach_port_move_member (mach_task_self (), newauth->port, auth_pset);
  newauth->next = allapts;
  if (newauth->next)
    newauth->next->prevp = &newauth->next;
  newauth->prevp = &allapts;
  allapts = newauth;
  return newauth;
}
  


/* Client requests */

/* Implement auth_getids as described in <hurd/auth.defs>. */
kern_return_t
S_auth_getids (struct apt *auth,
	       uid_t **gen_uids,
	       u_int *ngen_uids,
	       uid_t **aux_uids,
	       u_int *naux_uids,
	       uid_t **gen_gids,
	       u_int *ngen_gids,
	       uid_t **aux_gids,
	       u_int *naux_gids)
{
  if (!auth)
    return EOPNOTSUPP;
  
  if (auth->ngen_uids > *ngen_uids)
    *gen_uids = auth->gen_uids;
  else
    bcopy (auth->gen_uids, *gen_uids, sizeof (uid_t) * auth->ngen_uids);
  *ngen_uids = auth->ngen_uids;

  if (auth->naux_uids > *naux_uids)
    *aux_uids = auth->aux_uids;
  else
    bcopy (auth->aux_uids, *aux_uids, sizeof (uid_t) * auth->naux_uids);
  *naux_uids = auth->naux_uids;

  if (auth->ngen_gids > *ngen_gids)
    *gen_gids = auth->gen_gids;
  else
    bcopy (auth->gen_gids, *gen_gids, sizeof (uid_t) * auth->ngen_gids);
  *ngen_gids = auth->ngen_gids;

  if (auth->naux_gids > *naux_gids)
    *aux_gids = auth->aux_gids;
  else
    bcopy (auth->aux_gids, *aux_gids, sizeof (uid_t) * auth->naux_gids);
  *naux_gids = auth->naux_gids;

  return 0;
}

/* Implement auth_makeauth as described in <hurd/auth.defs>. */
kern_return_t
S_auth_makeauth (struct apt *auth,
		 mach_port_t *authpts,
		 u_int nauths,
		 uid_t *gen_uids,
		 u_int ngen_uids,
		 uid_t *aux_uids,
		 u_int naux_uids,
		 uid_t *gen_gids,
		 u_int ngen_gids,
		 uid_t *aux_gids,
		 u_int naux_gids,
		 mach_port_t *newhandle)
{
  int i, j;
  struct apt *newauth;
  struct apt **auths = alloca ((nauths + 1) * sizeof (struct apt *));
  int hasroot = 0;

  if (!auth)
    return EOPNOTSUPP;
  auths[0] = auth;

  for (i = 0; i < nauths; i++)
    if (!(auths[i + 1] = convert_auth_to_apt (authpts[i])))
      return EINVAL;

  nauths++;

  for (i = 0; i < nauths; i++)
    if (isroot (auth))
      {
	hasroot = 1;
	break;
      }
  
  if (!hasroot)
    {
      int has_it;
      
      for (i = 0; i < ngen_uids; i++)
	{
	  has_it = 0;
	  for (j = 0; j < nauths; j++)
	    if (isuid (gen_uids[i], auths[j]))
	      {
		has_it = 1;
		break;
	      }
	  if (!has_it)
	    return EPERM;
	}
      
      for (i = 0; i < naux_uids; i++)
	{
	  has_it = 0;
	  for (j = 0; j < nauths; j++)
	    if (isuid (aux_uids[i], auths[j]))
	      {
		has_it = 1;
		break;
	      }
	  if (!has_it)
	    return EPERM;
	}
      
      for (i = 0; i < ngen_gids; i++)
	{
	  has_it = 0;
	  for (j = 0; j < nauths; j++)
	    if (groupmember (gen_gids[i], auths[j]))
	      {
		has_it = 1;
		break;
	      }
	  if (!has_it)
	    return EPERM;
	}
	    
      for (i = 0; i < naux_gids; i++)
	{
	  has_it = 0;
	  for (j = 0; j < nauths; j++)
	    if (groupmember (aux_gids[i], auths[j]))
	      {
		has_it = 1;
		break;
	      }
	  if (!has_it)
	    return EPERM;
	}
    }  

  for (j = 0; j < nauths - 1; j++)
    mach_port_deallocate (mach_task_self (), authpts[j]);

  newauth = getauthstruct ();
  newauth->gen_uids = malloc (sizeof (uid_t) * ngen_uids);
  newauth->aux_uids = malloc (sizeof (uid_t) * naux_uids);
  newauth->gen_gids = malloc (sizeof (uid_t) * ngen_gids);
  newauth->aux_gids = malloc (sizeof (uid_t) * naux_gids);
  newauth->ngen_uids = ngen_uids;
  newauth->naux_uids = naux_uids;
  newauth->ngen_gids = ngen_gids;
  newauth->naux_gids = naux_gids;
  bcopy (gen_uids, newauth->gen_uids, ngen_uids * sizeof (uid_t));
  bcopy (aux_uids, newauth->aux_uids, naux_uids * sizeof (uid_t));
  bcopy (gen_gids, newauth->gen_gids, ngen_gids * sizeof (uid_t));
  bcopy (aux_gids, newauth->aux_gids, naux_gids * sizeof (uid_t));
  
  *newhandle = newauth->port;
  return 0;
}

/* Implement auth_user_authenticate as described in <hurd/auth.defs>. */
kern_return_t
S_auth_user_authenticate (struct apt *userauth,
			  mach_port_t reply,
			  mach_msg_type_name_t reply_porttype,
			  mach_port_t rendezvous,
			  mach_port_t rendezvous2,
			  mach_port_t *newport,
			  mach_msg_type_name_t *newporttype)
{
  struct saved_server *sv, **spp;
  struct saved_user *u;
  
  if (!userauth)
    return EOPNOTSUPP;
  
  /* Look for this port in the server list.  */
  for (sv = serverlist, spp = &serverlist; sv;)
    {
      if (sv->rendezvous == rendezvous && sv->rendezvous2 == rendezvous2)
	{
	  *spp = sv->next;
	  break;
	}
      else
	{
	  spp = &sv->next;
	  sv = sv->next;
	}
    }
  
  if (sv)
    {
      *newport = sv->passthrough;
      *newporttype = MACH_MSG_TYPE_MOVE_SEND;
      auth_server_authenticate_reply (sv->reply, sv->reply_type, 0,
				      userauth->gen_uids, userauth->ngen_uids,
				      userauth->aux_uids, userauth->naux_uids,
				      userauth->gen_gids, userauth->ngen_uids,
				      userauth->aux_gids, userauth->naux_uids);
      free (sv);
      
      /* Drop both rights from the call.  */
      mach_port_mod_refs (mach_task_self (), rendezvous, MACH_PORT_RIGHT_SEND,
			  -2);
      mach_port_mod_refs (mach_task_self (), rendezvous2, MACH_PORT_RIGHT_SEND,
			  -2);
      return 0;
    }

  /* User got here first.  */
  u = malloc (sizeof (struct saved_user));
  
  u->rendezvous = rendezvous;
  u->rendezvous2 = rendezvous2;
  u->userid = userauth;

  u->reply = reply;
  u->reply_type = reply_porttype;
  
  u->next = userlist;
  userlist = u;
  
  return MIG_NO_REPLY;
}

/* Implement auth_server_authenticate as described in <hurd/auth.defs>. */
kern_return_t
S_auth_server_authenticate (struct apt *serverauth,
			    mach_port_t reply,
			    mach_msg_type_name_t reply_porttype,
			    mach_port_t rendezvous,
			    mach_port_t rendezvous2,
			    mach_port_t newport,
			    uid_t **gen_uids,
			    u_int *ngen_uids,
			    uid_t **aux_uids,
			    u_int *naux_uids,
			    uid_t **gen_gids,
			    u_int *ngen_gids,
			    uid_t **aux_gids,
			    u_int *naux_gids)
{
  struct saved_user *su, **sup;
  struct saved_server *v;
  struct apt *uauth;
  
  if (!serverauth)
    return EOPNOTSUPP;

  /* Look for this port in the user list.  */
  for (su = userlist, sup = &userlist; su;)
    {
      if (su->rendezvous == rendezvous && su->rendezvous2 == rendezvous2)
	{
	  *sup = su->next;
	  break;
	}
      else
	{
	  sup = &su->next;
	  su = su->next;
	}
    }
  if (su)
    {
      auth_user_authenticate_reply (su->reply, su->reply_type, 0, newport,
				    MACH_MSG_TYPE_MOVE_SEND);
      
      uauth = su->userid;
      
      if (uauth->ngen_uids > *ngen_uids)
	*gen_uids = uauth->gen_uids;
      else
	bcopy (uauth->gen_uids, *gen_uids, sizeof (uid_t) * uauth->ngen_uids);
      *ngen_uids = uauth->ngen_uids;

      if (uauth->naux_uids > *naux_uids)
	*aux_uids = uauth->aux_uids;
      else
	bcopy (uauth->aux_uids, *aux_uids, sizeof (uid_t) * uauth->naux_uids);
      *naux_uids = uauth->naux_uids;

      if (uauth->ngen_gids > *ngen_gids)
	*gen_gids = uauth->gen_gids;
      else
	bcopy (uauth->gen_gids, *gen_gids, sizeof (uid_t) * uauth->ngen_gids);
      *ngen_gids = uauth->ngen_gids;

      if (uauth->naux_gids > *naux_gids)
	*aux_gids = uauth->aux_gids;
      else
	bcopy (uauth->aux_gids, *aux_gids, sizeof (uid_t) * uauth->naux_gids);
      *naux_gids = uauth->naux_gids;

      free (su);

      mach_port_mod_refs (mach_task_self (), rendezvous, MACH_PORT_RIGHT_SEND,
			  -2);
      mach_port_mod_refs (mach_task_self (), rendezvous2, MACH_PORT_RIGHT_SEND,
			  -2);
      return 0;
    }
  
  /* Server got here first.  */
  v = malloc (sizeof (struct saved_server));
  
  v->rendezvous = rendezvous;
  v->rendezvous2 = rendezvous2;
  v->passthrough = newport;
  
  v->reply = reply;
  v->reply_type = reply_porttype;
  
  v->next = serverlist;
  serverlist = v;
  
  return MIG_NO_REPLY;
}

/* Convert an auth port into an auth handle structure. */
struct apt *
convert_auth_to_apt (auth_t auth)
{
  struct apt *a;
  for (a = allapts; a; a = a->next)
    if (a->port == auth)
      return a;
  return 0;
}

/* Unneeded notification stubs: */
error_t
_S_do_mach_notify_port_deleted (mach_port_t notify,
				    mach_port_seqno_t seqno,
				    mach_port_t name)
{
  return 0;
}

error_t
_S_do_mach_notify_msg_accepted (mach_port_t notify,
				    mach_port_seqno_t seqno,
				    mach_port_t name)
{
  return 0;
}

error_t
_S_do_mach_notify_port_destroyed (mach_port_t notify,
				      mach_port_seqno_t seqno,
				      mach_port_t name)
{
  return 0;
}

error_t
_S_do_mach_notify_send_once (mach_port_t notify,
				 mach_port_seqno_t seqno)
{
  return 0;
}

error_t
_S_do_mach_notify_dead_name (mach_port_t notify,
				 mach_port_seqno_t seqno,
				 mach_port_t name)
{
  return 0;
}