/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *  Portions Copyright (C) 1994 Rafael W. Luebbert
 *  Portions Copyright (C) 1996 Jeff Shepherd
 *
 *  This 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.
 *
 *  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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define _KERNEL
#include "ixemul.h"
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include "multiuser.h"
#include <amitcp/usergroup.h>

static char *
getUserPasswd(char *name)
{
#if 1 /* This gets muFS's password */
      /* Only uncomment this when I figure out how ACrypt works */
    char *retval = NULL;
    char passdir[MAXPATHLEN];
    BPTR passwddir = muGetPasswdDirLock();

    if (passwddir) {
	if (NameFromLock(passwddir, passdir, MAXPATHLEN)) {
	    if (AddPart(passdir, muPasswd_FileName, MAXPATHLEN)) {
		FILE *fi = (FILE *)syscall(SYS_fopen,passdir,"r");
		char passwdline[1024];
		char username[muUSERNAMESIZE] = {0,};
		char passwd[muPASSWORDSIZE] = {0,};
		int found_it = 0;

		if (fi) {
		    while (fgets(passwdline,1024,fi)) {

			char *sep = strchr(passwdline,'|');
			char *old;
			strncpy(username,passwdline,sep-passwdline);

			old = ++sep;
			sep = strchr(old,'|');
			strncpy(passwd,old,sep - old);

			if (!strcmp(username,name)) {
			    found_it = 1;
			    break;
			}
		    }
		    if (found_it) {
			retval = strdup(passwd);
		    }
		    fclose(fi);
		}
	    }
	}
	UnLock(passwddir);
    }
    if (!retval)
	retval = strdup("*");
    return retval;
#else
/* Use the network's password */
    struct passwd *pwd = netcall(NET_getpwnam,name);
    char *retval;
    if (pwd)
	retval = strdup(pwd->pw_passwd);
    else
	retval = strdup("*");
    return retval;
#endif
}

/* multiuser doesn't allow a password entry for nobody (uid = 0)
 * I will create one though
 */
static struct muUserInfo nobody_userinfo = {
    "nobody",
    0,
    0,
    "nobody",
    "t:",
    0,
    (UWORD *)NULL,
    "cli",
};


static struct passwd *
UserInfo2pw (struct muUserInfo *UI)
{
  struct passwd *pw = &u.u_passwd;

  if (UI == NULL)
    return NULL;

  pw->pw_name = UI->UserID;
  pw->pw_dir = UI->HomeDir;
#if 1
  if (pw->pw_passwd)
    free(pw->pw_passwd);
  pw->pw_passwd = getUserPasswd(pw->pw_name);
#else
  pw->pw_passwd = "*";
#endif
  pw->pw_uid = UI->uid;
  pw->pw_gid = UI->gid;
  pw->pw_change = 0;
  pw->pw_class = 0;
  pw->pw_gecos = UI->UserName;
  pw->pw_shell = UI->Shell;
  pw->pw_expire = 0;

  return pw;
}

uid_t
getuid(void)
{
  if (muBase)
    return (muGetTaskOwner(NULL) >> 16);
  if (u.u_ixnetbase)
    return netcall(NET_getuid);
  return 0;
}

uid_t
geteuid(void)
{
  if (muBase)
    return getuid();
  if (u.u_ixnetbase)
    return netcall(NET_geteuid);
  return 0;
}

int
setuid (uid_t uid)
{
  int retval = 0;

  if (muBase)
  {
    if (getuid() == muROOT_UID)
    {
      struct muUserInfo *UI = muAllocUserInfo();

      if (UI) {
	UI->uid = uid;
	UI = muGetUserInfo (UI, muKeyType_uid);

	if (muLogin(muT_Task, (ULONG)FindTask(NULL),
		muT_UserID, (ULONG)UI->UserID,
		muT_NoLog, TRUE,
		TAG_DONE)) {
	    u.u_setuid++;
	}
	muFreeUserInfo(UI);
      }
    }
    else {
      errno = EPERM;
      retval = -1;
    }

    return retval;
    /* I don't think usergroup + muFS need to setuid */
    /* uid = MU2UG(uid); */
  }

  if (u.u_ixnetbase)
    return netcall(NET_setuid, uid);
  else
  /* just always succeed... */
    return retval;
}

int
seteuid (uid_t uid)
{
  if (u.u_ixnetbase)
    return netcall(NET_seteuid, uid);
  else
    return setuid(uid);
}

int
setreuid (int ruid, int euid)
{
  if (u.u_ixnetbase)
    return netcall(NET_setreuid, ruid, euid);
  else
    return setuid(euid);
}

struct passwd *getpwuid (uid_t uid)
{
  char *name;
  struct passwd *retval;

  if (muBase)
    {
      /* active multiuser */
      struct muUserInfo *UI = u.u_UserInfo;

      UI->uid = uid;

      /*
       * "nobody" can't have a password entry
       * so I will create one
       */
      UI = (uid == muOWNER_NOBODY ? &nobody_userinfo : muGetUserInfo (UI, muKeyType_uid));

      return UserInfo2pw (UI);      /* handles errors */
    }

  if (u.u_ixnetbase)
    return (struct passwd *)netcall(NET_getpwuid, uid);

  if ((name = (char *)syscall(SYS_getenv, "USER")))
    retval = (struct passwd *)syscall(SYS_getpwnam, name);
  else
    retval = (struct passwd *)syscall(SYS_getpwnam, "root");
  retval->pw_uid = uid;
  return retval;
}

struct passwd *getpwnam (const char *name)
{
  /* this is not quite safe, since this library function could be called
   * in parallel with different names... well, I don't consider this worth
   * doing `right', it's here to be able to link some programs ;-)
   */
  static struct passwd pw = {
	0,		/* pw_name */
        "*",		/* pw_passwd */
        0,		/* pw_uid */
        0,		/* pw_gid */
        0, 		/* pw_change */
        0,		/* pw_class */
	0,		/* pw_dir */
	0,		/* pw_gecos */
	"/bin/sh",      /* pw_shell */
	0		/* pw_expire */
  };

  if (muBase)
    {
      struct muUserInfo *UI = u.u_UserInfo;

      /*
       * some validation checks
       */
    
      if (name == NULL)
        return NULL;
    
      if ((muUSERIDSIZE - 1) < strlen (name))
        return NULL;

      strcpy (UI->UserID, name);
      UI = muGetUserInfo (UI, muKeyType_UserID);
      /* special case for nobody (uid 0) */
      /* Not sure if this should come before muGetUserInfo()
       * since nobody could have another uid
       */
       if (!UI && !strcmp(name,"nobody"))
	  UI = &nobody_userinfo;

      return UserInfo2pw (UI);      /* handles errors */
    }

  if (u.u_ixnetbase)
    return (struct passwd *)netcall(NET_getpwnam, name);

  pw.pw_name = (char *)name;

  if (!(pw.pw_dir = (char *)syscall(SYS_getenv, "HOME"))) {
    pw.pw_dir = "SYS:";
  }

  if (!(pw.pw_gecos = (char *)syscall(SYS_getenv, "REALNAME"))) {
    pw.pw_gecos = "amiga user";
  }
  return &pw;
}

struct passwd *
getpwent(void)
{
  char *name;

  if (!muBase && u.u_ixnetbase)
    return (struct passwd *)netcall(NET_getpwent);
  name = getenv("USER");
  if (name)
    return (struct passwd *)syscall(SYS_getpwnam, name);
  return (struct passwd *)syscall(SYS_getpwnam, "root");
}

int
setpassent(int stayopen)
{
  if (u.u_ixnetbase)
    return netcall(NET_setpassent, stayopen);
  return 1;
}

int
setpwent(void)
{
  if (u.u_ixnetbase)
    return netcall(NET_setpwent);
  return 1 ;
}

void
endpwent(void)
{
  if (u.u_ixnetbase)
    netcall(NET_endpwent);
}

pid_t
setsid(void)
{
    if (u.u_ixnetbase)
	return (pid_t)netcall(NET_setsid);
    else {
	errno = EPERM;
	return -1;
    }
}
