/* Authentication server
   Copyright (C) 1992 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.h"
#include "auth_reply.h"
#include "startup.h"
#include <mach/error.h>
#include <gnu/errno.h>
#include <gnu/posix_errors.h>
#include <mach/mig_errors.h>
#include <mach/notify.h>

/* This represents a request from a call to auth_user/server_authenticate
   which is waiting for the corresponding call.  */
struct saved
{
  struct saved *next;		/* hash link */
  
  mach_port_t rendezvous;	/* rendezvous port */

  mach_port_t newport;		/* to be passed to user */
  struct idblock *ids;		/* 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 */
};

struct saved *userlist;
struct saved *serverlist;

void hash_insert (struct saved *, struct saved **);
struct saved *hash_extract (mach_port_t, struct saved **);
struct idblock *getauth (void);
void auth_nosenders (struct idblock *);

mach_port_t host_priv;

mach_port_t auth_pset;


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 ()
{
  mach_port_t boot;
  struct idblock *firstauth;
  extern int mach_msg_server ();
  
  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_PORT_SET,
		      &auth_pset);

  task_get_special_port (mach_task_self (), TASK_BOOTSTRAP_PORT, &boot);

  firstauth = getauth ();
  bzero (firstauth, sizeof (struct idblock));

  startup_authinit (boot, (mach_port_t)firstauth, &host_priv);
  
  while (1)
    mach_msg_server (request_server, vm_page_size * 2, auth_pset);
}


/* Server routines: */

void
auth_nosenders (struct idblock *auth)
{
  struct saved *s, *prev, *tmp;

  /* Remove all references to this from the saved lists */
  for (s = userlist, prev = 0; s;)
    {
      if (s->ids == auth)
	{
	  if (prev)
	    prev->next = s->next;
	  else
	    userlist = s->next;
	  tmp = s;
	  s = s->next;
	  free (tmp);
	}
      else
	{
	  prev = s;
	  s = s->next;
	}
    }
  
  /* Delete its storage and port. */
  mach_port_deallocate (mach_task_self (), (mach_port_t) auth);
  free (auth);
}

error_t  
auth_getids (struct idblock *userauth,
	     struct idblock *ids)
{
  bcopy (userauth, ids, sizeof (struct idblock));
  return POSIX_SUCCESS;
}

inline int
groupmember (gid_t test, 
	     struct idblock *idblock)
{
  int i;
  
  if (test == idblock->rgid
      || test == idblock->svgid)
    return 1;
  
  for (i = 0; i < idblock->ngroups; i++)
    if (test == idblock->gidset[i])
      return 1;
  return 0;
}

inline int
isuid (uid_t test,
       struct idblock *idblock);
{
  int i;

  if (test == idblock->ruid
      || test == idblock->svuid)
    return 1;
  
  for (i = 0; i < idblock->nuids; i++)
    if (test == idblock->uidset[i])
      return 1;
  return 0;
}

inline int
isroot (struct idblock *idblock)
{
  return isuid (0, idblock);
}

struct idblock *
getauth ()
{
  struct idblock *newauth;
  mach_port_t unused;
  error_t err;

  newauth = malloc (sizeof (struct idblock));
  err = mach_port_allocate_name (mach_task_self (), MACH_PORT_RIGHT_RECEIVE,
				 (mach_port_t) newauth);
  if (err == KERN_NAME_EXISTS)
    {
      struct idblock *tryagain = getauth ();
      free (newauth);
      return tryagain;
    }

  mach_port_request_notification (mach_task_self (), (mach_port_t) newauth,
				  MACH_NOTIFY_NO_SENDERS, 1,
				  (mach_port_t) newauth,
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &unused);
  mach_port_move_member (mach_task_self (), (mach_port_t) newauth, auth_pset);
  return newauth;
}
  


/* Client requests */

error_t
auth_getids (struct idblock *userauth,
	     struct idblock *reply)
{
  bcopy (userauth, reply, sizeof (struct idblock));
  return POSIX_SUCCESS;
}

error_t
auth_makeauth (struct idblock *userauth,
	       struct idblock newids,
	       mach_port_t *newhandle)
{
  int i;
  struct idblock *newauth;

  if (badform (&newids))
    return EINVAL;
      
  if (!isroot (userauth))
    {
      if (!groupmember (newids.rgid, userauth)
	  || !groupmember (newids.svgid, userauth))
	return POSIX_EPERM;
      
      for (i = 0; i < newids.ngroups; i++)
	if (!groupmember (newids.gidset[i], userauth))
	  return POSIX_EPERM;

      if (!isuid (newids.ruid, userauth)
	  || !isuid (newids.svuid, userauth))
	return POSIX_EPERM;
      
      for (i = 0; i < newids.nuids; i++)
	if (!isuid (newids.uidset[i], userauth))
	  return POSIX_EPERM;
    }

  newauth = getauth ();
  bcopy (&newids, newauth, sizeof (struct idblock));
  
  *newhandle = (mach_port_t) newauth;
  return POSIX_SUCCESS;
}

error_t
auth_combine (struct idblock *auth1,
	      struct idblock *auth2,
	      mach_port_t *newauthhandle)
{
  struct idblock tmpauth;
  struct idblock *newauth;
  int i;
  
#define adduid(n) ({							    \
		      if (!isuid ((n), &tmpauth))			    \
			{						    \
			  if (tmpauth.nuids == NUIDS_MAX - 1)		    \
			    goto toobig;				    \
			  tmpauth.uidset[tmpauth.nuids++] = (n);	    \
			}						    \
		    })
#define addgid(n) ({							    \
		      if (!groupmember ((n), &tmpauth))			    \
			{						    \
			  if (tmpauth.ngroups == NGROUPS_MAX - 1)	    \
			    goto toobig;				    \
			  tmpauth.gidset[tmpauth.ngroups++] = (n);	    \
			}						    \
		    })

  bcopy (auth1, &tmpauth, sizeof (struct idblock));
  adduid (auth2->ruid);
  adduid (auth2->svuid);
  addgid (auth2->rgid);
  addgid (auth2->svgid);
  for (i = 0; i < auth2->nuids; i++)
    adduid (auth2->uidset[i]);
  for (i = 0; i < auth2->ngroups; i++)
    addgid (auth2->gidset[i]);
  
  newauth = getauth ();
  bcopy (&tmpauth, newauth, sizeof (struct idblock));
  *newauthhandle = (mach_port_t) newauth;
  return POSIX_SUCCESS;
  
 toobig:
  return POSIX_EINVAL;
#undef adduid
#undef addgid
}

error_t
auth_user_authenticate (struct idblock *userauth,
			mach_port_t reply,
			mach_msg_type_name_t reply_porttype,
			mach_port_t rendezvous,
			mach_port_t *newport)
{
  struct saved *sv;
  
  /* Look for this port in the server list.  */
  sv = hash_extract (rendezvous, &serverlist);
  if (sv)
    {
      *newport = sv->newport;
      auth_server_authenticate_reply (sv->reply, sv->reply_type,
				      POSIX_SUCCESS, *userauth);
      free (sv);
      
      /* Drop both rights from the call.  */
      mach_port_mod_refs (mach_task_self (), rendezvous, MACH_PORT_RIGHT_SEND,
			  -2);
      return POSIX_SUCCESS;
    }

  /* User got here first.  */
  sv = malloc (sizeof (struct saved));
  
  sv->rendezvous = rendezvous;
  sv->ids = userauth;

  sv->reply = reply;
  sv->reply_type = reply_porttype;
  
  hash_insert (sv, &userlist);
  
  return MIG_NO_REPLY;
}

error_t
auth_server_authenticate (struct idblock *serverauth,
			  mach_port_t reply,
			  mach_msg_type_name_t reply_porttype,
			  mach_port_t rendezvous,
			  mach_port_t newport,
			  struct idblock *outblock)
{
  struct saved *sv;
  
  /* Look for this port in the user list.  */
  sv = hash_extract (rendezvous, &userlist);
  if (sv)
    {
      auth_user_authenticate_reply (sv->reply, sv->reply_type,
				    POSIX_SUCCESS, newport);
      
      bcopy (sv->ids, outblock, sizeof (struct idblock));
      free (sv);

      mach_port_mod_refs (mach_task_self (), rendezvous, MACH_PORT_RIGHT_SEND,
			  -2);
      return POSIX_SUCCESS;
    }
  
  /* Server got here first.  */
  sv = malloc (sizeof (struct saved));
  
  sv->rendezvous = rendezvous;
  sv->newport = newport;
  
  sv->reply = reply;
  sv->reply_type = reply_porttype;
  
  hash_insert (sv, &serverlist);
  
  return MIG_NO_REPLY;
}

struct idblock *
convert_auth_to_idblock (auth_t auth)
{
  return (struct idblock *) auth;
}




/* Well, should these be a real hash table??  dunno.  */
void
hash_insert (struct saved *sv,
	     struct saved **listhead)
{
  sv->next = *listhead;
  *listhead = sv;
}

struct saved *
hash_extract (mach_port_t rendezvous,
	      struct saved **listhead)
{
  struct saved *sv, *prev;
  
  for (prev = 0, sv = *listhead; sv; prev = sv, sv = sv->next)
    if (sv->rendezvous == rendezvous)
      {
	if (prev)
	  prev->next = sv->next;
	else
	  *listhead = sv->next;
	return sv;
      }
  return 0;
}


