/*
**	$VER: accounts.c 1.6 (02.11.94)
**
**	accounts.library functions
**
**	© Copyright 1994 by Norbert Püschel
**	All Rights Reserved
*/

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/multiuser.h>
#include <clib/accounts_protos.h>
#include <pragmas/accounts_pragmas.h>
#include <clib/alib_protos.h>

#include <exec/memory.h>
#include <exec/execbase.h>
#include <dos/notify.h>
#include <envoy/errors.h>

#include <string.h>
#include "daemon.h"
#include <debug.h>

#define NOBODY

#ifdef NOBODY
const UBYTE nobody[] = "nobody";

#define NOBODYNAME (STRPTR)nobody
#endif

#define BUFFLEN 256
#define UNAMELEN 32
#define GNAMELEN 32
#define PWDLEN   16

extern struct Library *AccountsBase;

struct MinList         UserList;
struct SignalSemaphore UserSemaphore;
struct MinList         GroupList;
struct SignalSemaphore GroupSemaphore;

struct InternalUserInfo {
  struct MinNode  iui_Node;
  struct UserInfo iui_UserInfo;
  struct MinList  iui_SecGroups;
  UBYTE           iui_CryptPasswd[PWDLEN];
};

struct InternalGroupInfo {
  struct MinNode   igi_Node;
  struct GroupInfo igi_GroupInfo;
};

struct SecGroup {
  struct MinNode            sg_Node;
  struct InternalGroupInfo *sg_IGI;
};

void init_users(void)

{
  NewList((struct List *)&UserList);
  InitSemaphore(&UserSemaphore);
}

void init_groups(void)

{
  NewList((struct List *)&GroupList);
  InitSemaphore(&GroupSemaphore);
}

void free_users(void)

{
  struct InternalUserInfo *iui;
  struct SecGroup *sg;

  ObtainSemaphore(&UserSemaphore);
  iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
  while(iui->iui_Node.mln_Succ) {
    Remove((struct Node *)iui);
    sg = (struct SecGroup *)(iui->iui_SecGroups.mlh_Head);
    while(sg->sg_Node.mln_Succ) {
      Remove((struct Node *)sg);
      FreeMem(sg,sizeof(struct SecGroup));
      sg = (struct SecGroup *)(iui->iui_SecGroups.mlh_Head);
    }
    FreeMem(iui,sizeof(struct InternalUserInfo));
    iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
  }
  ReleaseSemaphore(&UserSemaphore);  
}

void free_groups(void)

{
  struct InternalGroupInfo *igi;

  ObtainSemaphore(&GroupSemaphore);
  igi = (struct InternalGroupInfo *)(GroupList.mlh_Head);  
  while(igi->igi_Node.mln_Succ) {
    Remove((struct Node *)igi);
    FreeMem(igi,sizeof(struct InternalGroupInfo));
    igi = (struct InternalGroupInfo *)(GroupList.mlh_Head);  
  }
  ReleaseSemaphore(&GroupSemaphore);  
}

BOOL adduser(STRPTR uname,UWORD uid,UWORD gid,ULONG flags,STRPTR pwd)

{
  struct InternalUserInfo *iui;

  iui = (struct InternalUserInfo *)
        AllocMem(sizeof(struct InternalUserInfo),MEMF_PUBLIC);
  if(iui) {
    strncpy(iui->iui_UserInfo.ui_UserName,uname,UNAMELEN);
    iui->iui_UserInfo.ui_UserID = uid;
    iui->iui_UserInfo.ui_PrimaryGroupID = gid;
    iui->iui_UserInfo.ui_Flags = flags;
    strncpy(iui->iui_CryptPasswd,pwd,PWDLEN);
    NewList((struct List *)&(iui->iui_SecGroups));
    AddTail((struct List *)&UserList,(struct Node *)iui);
  }
  return((BOOL)(iui != 0));
}

BOOL addgroup(STRPTR gname,UWORD gid)

{
  struct InternalGroupInfo *igi;

  igi = (struct InternalGroupInfo *)
        AllocMem(sizeof(struct InternalGroupInfo),MEMF_PUBLIC);
  if(igi) {
    strncpy(igi->igi_GroupInfo.gi_GroupName,gname,32);
    igi->igi_GroupInfo.gi_GroupID = gid;
    AddTail((struct List *)&GroupList,(struct Node *)igi);
  }
  return((BOOL)(igi != 0));
}

BOOL addsecgroup(struct InternalUserInfo *iui,struct InternalGroupInfo *igi)

{
  struct SecGroup *sg;

  sg = (struct SecGroup *)AllocMem(sizeof(struct SecGroup),MEMF_PUBLIC);
  if(sg) {
    sg->sg_IGI = igi;
    AddTail((struct List *)&(iui->iui_SecGroups),(struct Node *)sg);
  }
  return((BOOL)(sg != 0));
}

STRPTR nextsep(STRPTR s,UBYTE sep)

{
  while(*s && *s != '\n' && *s != sep) s++;
  return(s);
}

struct InternalUserInfo *finduid(UWORD uid)

{
  struct InternalUserInfo *iui;

  iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
  while(iui->iui_Node.mln_Succ) {
    if(iui->iui_UserInfo.ui_UserID == uid) return(iui);
    iui = (struct InternalUserInfo *)(iui->iui_Node.mln_Succ);  
  }
  return(0);
}

struct InternalGroupInfo *findgid(UWORD gid)

{
  struct InternalGroupInfo *igi;

  igi = (struct InternalGroupInfo *)(GroupList.mlh_Head);  
  while(igi->igi_Node.mln_Succ) {
    if(igi->igi_GroupInfo.gi_GroupID == gid) return(igi);
    igi = (struct InternalGroupInfo *)(igi->igi_Node.mln_Succ);  
  }
  return(0);
}

struct InternalUserInfo *finduname(STRPTR uname)

{
  struct InternalUserInfo *iui;

  iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
  while(iui->iui_Node.mln_Succ) {
    if(strcmp(iui->iui_UserInfo.ui_UserName,uname) == 0) return(iui);
    iui = (struct InternalUserInfo *)(iui->iui_Node.mln_Succ);  
  }
  return(0);
}

struct InternalGroupInfo *findgname(STRPTR gname)

{
  struct InternalGroupInfo *igi;

  igi = (struct InternalGroupInfo *)(GroupList.mlh_Head);  
  while(igi->igi_Node.mln_Succ) {
    if(strcmp(igi->igi_GroupInfo.gi_GroupName,gname) == 0) return(igi);
    igi = (struct InternalGroupInfo *)(igi->igi_Node.mln_Succ);  
  }
  return(0);
}

struct InternalUserInfo *finduser(STRPTR uname,UWORD uid)

{
  struct InternalUserInfo *iui;

  iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
  while(iui->iui_Node.mln_Succ) {
    if(iui->iui_UserInfo.ui_UserID == uid &&
       strcmp(iui->iui_UserInfo.ui_UserName,uname) == 0) return(iui);
    iui = (struct InternalUserInfo *)(iui->iui_Node.mln_Succ);  
  }
  return(0);
}

struct InternalGroupInfo *findgroup(STRPTR gname,UWORD gid)

{
  struct InternalGroupInfo *igi;

  igi = (struct InternalGroupInfo *)(GroupList.mlh_Head);  
  while(igi->igi_Node.mln_Succ) {
    if(igi->igi_GroupInfo.gi_GroupID == gid &&
      strcmp(igi->igi_GroupInfo.gi_GroupName,gname) == 0) return(igi);
    igi = (struct InternalGroupInfo *)(igi->igi_Node.mln_Succ);  
  }
  return(0);
}

BOOL issecmem(struct InternalUserInfo *iui,struct InternalGroupInfo *igi)

{
  struct SecGroup *sg;

  sg = (struct SecGroup *)(iui->iui_SecGroups.mlh_Head);
  while(sg->sg_Node.mln_Succ) {
    if(sg->sg_IGI == igi) return(TRUE);
    sg = (struct SecGroup *)(sg->sg_Node.mln_Succ);
  }
  return(FALSE);
}

void read_users(void)

{
  BPTR passwd;
  UBYTE buff[BUFFLEN];
  STRPTR s,pwd,uidstr,gidstr;
  ULONG flags = 0;
  LONG uid,gid;

  ObtainSemaphore(&UserSemaphore);

#ifdef NOBODY
  if(!adduser(NOBODYNAME,0,0,0,"")) return;
#endif

  passwd = Open(muPasswd_FileName,MODE_OLDFILE);
  if(passwd) {
    while(FGets(passwd,buff,BUFFLEN-1)) {
      s = nextsep(buff,'|');
      if(*s != '|') break;
      *(s++) = '\0';
      pwd = s;
      s = nextsep(pwd,'|');
      if(*s != '|') break;
      if(s != pwd) flags = UFLAGF_NeedsPassword;
      *(s++) = '\0';
      uidstr = s;
      s = nextsep(uidstr,'|');
      if(*s != '|') break;
      *(s++) = '\0';
      gidstr = s;
      s = nextsep(gidstr,'|');
      if(*s != '|') break;
      *s = '\0';

      if(StrToLong(uidstr,&uid) <= 0) break;
      if(StrToLong(gidstr,&gid) <= 0) break;

      flags |= UFLAGF_AdminPassword; /* should evaluate config !!! */
      if(uid == muROOT_UID) flags |= (UFLAGF_AdminGroups|UFLAGF_AdminAll);

      if(!adduser(buff,uid,gid,flags,pwd)) break;
    }
    Close(passwd);
  }
  ReleaseSemaphore(&UserSemaphore);
}

void read_groups(void)

{
  BPTR group;
  UBYTE buff[BUFFLEN];
  STRPTR s,gidstr;
  LONG uid,gid;
  struct InternalGroupInfo *igi;
  struct InternalUserInfo *iui;
  BOOL done;

  ObtainSemaphore(&UserSemaphore);
  ObtainSemaphore(&GroupSemaphore);

  group = Open(muGroup_FileName,MODE_OLDFILE);
  if(group) {
    while(FGets(group,buff,BUFFLEN-1) && *buff != ' ' && *buff != '\n') {
      s = nextsep(buff,'|');
      if(*s != '|') break;
      *(s++) = '\0';
      gidstr = s;
      s = nextsep(gidstr,'|');
      if(*s != '|') break;
      *s = '\0';

      if(StrToLong(gidstr,&gid) <= 0) break;

      if(!addgroup(buff,gid)) break;
    }
    while(FGets(group,buff,BUFFLEN-1)) { /* secondary groups */
      s = nextsep(buff,':');
      if(*s != ':') break;
      *(s++) = '\0';
      if(StrToLong(buff,&uid) <= 0) break;
      iui = finduid(uid);
      if(uid) {
        do {
          gidstr = s;
          s = nextsep(gidstr,',');
          done = (*s != ',');
          *(s++) = '\0';
          if(StrToLong(gidstr,&gid) <= 0) break;
          igi = findgid(gid);
          if(igi) {
            if(!addsecgroup(iui,igi)) break;
          }
        } while(!done);
      }
    }
    Close(group);
  }
  ReleaseSemaphore(&GroupSemaphore);
  ReleaseSemaphore(&UserSemaphore);
}

void __saveds guardian_func(void)

{
  struct muBase *muBase;
  struct NotifyRequest *passwd_req;
  struct NotifyRequest *group_req;
  BYTE passwd_sig,group_sig;
  BPTR passwd_dir,olddir;  

  BOOL pok,gok;

  ULONG mask,rmask,pmask,gmask,emask;

  volatile struct Daemon *guardian;

  guardian = DaemonInit();

  if(DaemonOK(guardian)) {
    init_users();
    init_groups();

    muBase = (struct muBase *)OpenLibrary(MULTIUSERNAME,MULTIUSERVERSION);
  
    if(muBase) {
      passwd_dir = muGetPasswdDirLock();
      CloseLibrary((struct Library *)muBase);
    }
    else {
      UBYTE buff[256];
  
      if(GetVar("PASSWDDIR",buff,256,0) != -1) {
        passwd_dir = Lock(buff,SHARED_LOCK);
      }
      else {
        passwd_dir = Lock("S:",SHARED_LOCK);
      }
    }
  
    if(passwd_dir) {
      D(bug("Passwd dir found !\n"));
      olddir = CurrentDir(passwd_dir);
      passwd_sig = AllocSignal(-1);
      if(passwd_sig != -1) {
        group_sig = AllocSignal(-1);
        if(group_sig != -1) {
          D(bug("Signals allocated !\n"));
          passwd_req = (struct NotifyRequest *)
            AllocMem(sizeof(struct NotifyRequest),MEMF_PUBLIC|MEMF_CLEAR);        
          if(passwd_req) {
            group_req = (struct NotifyRequest *)
              AllocMem(sizeof(struct NotifyRequest),MEMF_PUBLIC|MEMF_CLEAR);        
            if(group_req) {
              D(bug("Notifiers allocated !\n"));
              passwd_req->nr_Name = muPasswd_FileName;
              passwd_req->nr_Flags = NRF_SEND_SIGNAL;
              passwd_req->nr_stuff.nr_Signal.nr_Task = FindTask(0);
              passwd_req->nr_stuff.nr_Signal.nr_SignalNum = passwd_sig;

              group_req->nr_Name = muGroup_FileName;
              group_req->nr_Flags = NRF_SEND_SIGNAL;
              group_req->nr_stuff.nr_Signal.nr_Task = FindTask(0);
              group_req->nr_stuff.nr_Signal.nr_SignalNum = group_sig;

              pok = StartNotify(passwd_req);
              gok = StartNotify(group_req);

              read_users();
              read_groups();

              DaemonInitOK(guardian);

              pmask = 1L << passwd_sig;
              gmask = 1L << group_sig;
              emask = 1L << DaemonSignal(guardian);
              mask = pmask | gmask | emask;

              D(bug("Starting Loop !\n"));

              for(;;) {
                rmask = Wait(mask);

                if(rmask & emask) break;
                if(rmask & pmask) {
                  D(bug("New Users !\n"));
                  ObtainSemaphore(&UserSemaphore);
                  free_users();
                  read_users();
                  ReleaseSemaphore(&UserSemaphore);
                }
                if(rmask & gmask) {
                  D(bug("New Groups !\n"));
                  ObtainSemaphore(&GroupSemaphore);
                  free_groups();
                  read_groups();
                  ReleaseSemaphore(&GroupSemaphore);
                }
              }

              D(bug("Ending Loop !\n"));

              if(gok) EndNotify(group_req);
              if(pok) EndNotify(passwd_req);

              FreeMem(group_req,sizeof(struct NotifyRequest));
            }
            FreeMem(passwd_req,sizeof(struct NotifyRequest));
          }
          FreeSignal(group_sig);
        }
        FreeSignal(passwd_sig);
      }
      UnLock(CurrentDir(olddir));
    }
   
    D(bug("Freeing Userinfo ...\n"));
    free_users();
    D(bug("Freeing Groupinfo ...\n"));
    free_groups();
  }

  D(bug("Ending Guardian !\n"));
  DaemonExit(guardian);
}

/* allocation functions */

struct UserInfo * __saveds LIBAllocUserInfo(void)

{
  D(bug("AllocUserInfo\n"));

  return((struct UserInfo *)AllocMem(sizeof(struct UserInfo),MEMF_CLEAR));
}

struct GroupInfo * __saveds LIBAllocGroupInfo(void)

{
  D(bug("AllocGroupInfo\n"));

  return((struct GroupInfo *)AllocMem(sizeof(struct GroupInfo),MEMF_CLEAR));
}

void __saveds __asm LIBFreeUserInfo(register __a0 struct UserInfo *user)

{
  D(bug("FreeUserInfo: Name: %s, UID: %ld\n",
         user->ui_UserName,user->ui_UserID));

  FreeMem(user,sizeof(struct UserInfo));
}


void __saveds __asm LIBFreeGroupInfo(register __a0 struct GroupInfo *group)

{
  D(bug("FreeGroupInfo: Name: %s, GID: %ld\n",
         group->gi_GroupName,group->gi_GroupID));

  FreeMem(group,sizeof(struct GroupInfo));
}

/* xxxToyyy functions */

ULONG __saveds __asm LIBNameToUser(register __a0 STRPTR uname,
                                   register __a1 struct UserInfo *user)

{
  ULONG retval;
  struct InternalUserInfo *iui;

  D(bug("NameToUser\n"));

  ObtainSemaphoreShared(&UserSemaphore);

#ifdef NOBODY
  if(stricmp(NOBODYNAME,uname) == 0) uname = NOBODYNAME; /* normalize */
#endif

  iui = finduname(uname);

  if(iui) {
    *user = iui->iui_UserInfo;
    retval = 0;
  }
  else retval = ENVOYERR_UNKNOWNUSER;

  ReleaseSemaphore(&UserSemaphore);

  return(retval);
}

ULONG __saveds __asm LIBNameToGroup(register __a0 STRPTR gname,
                                    register __a1 struct GroupInfo *group)

{
  ULONG retval;
  struct InternalGroupInfo *igi;

  D(bug("NameToGroup\n"));

  ObtainSemaphoreShared(&GroupSemaphore);

  igi = findgname(gname);

  if(igi) {
    *group = igi->igi_GroupInfo;
    retval = 0;
  }
  else retval = ENVOYERR_UNKNOWNGROUP;

  ReleaseSemaphore(&GroupSemaphore);

  return(retval);
}

ULONG __saveds __asm LIBIDToUser(register __d0 UWORD uid, 
                                 register __a0 struct UserInfo *user)

{
  ULONG retval;
  struct InternalUserInfo *iui;

  D(bug("IDToUser\n"));

  ObtainSemaphoreShared(&UserSemaphore);

  iui = finduid(uid);

  if(iui) {
    *user = iui->iui_UserInfo;
    retval = 0;
  }
  else retval = ENVOYERR_UNKNOWNUSER;

  ReleaseSemaphore(&UserSemaphore);

  return(retval);
}

ULONG __saveds __asm LIBIDToGroup(register __d0 UWORD gid,
                                  register __a0 struct GroupInfo *group)

{
  ULONG retval;
  struct InternalGroupInfo *igi;

  D(bug("IDToGroup\n"));

  ObtainSemaphoreShared(&GroupSemaphore);

  igi = findgid(gid);

  if(igi) {
    *group = igi->igi_GroupInfo;
    retval = 0;
  }
  else retval = ENVOYERR_UNKNOWNGROUP;

  ReleaseSemaphore(&GroupSemaphore);

  return(retval);
}

/* Nextxxx functions */

ULONG __saveds __asm LIBNextUser(register __a0 struct UserInfo *user)

{
  ULONG retval = ENVOYERR_LASTUSER;
  struct InternalUserInfo *iui;

  D(bug("NextUser\n"));

  ObtainSemaphoreShared(&UserSemaphore);

  if(user->ui_UserID != 0
#ifdef NOBODY
     || stricmp(user->ui_UserName,NOBODYNAME) == 0
#endif
    ) { /* take next */
    iui = finduser(user->ui_UserName,user->ui_UserID);

    if(iui) iui = (struct InternalUserInfo *)(iui->iui_Node.mln_Succ);  
    else retval = ENVOYERR_UNKNOWNUSER;
  }
  else { /* take first */   
    iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
  }

  if(iui->iui_Node.mln_Succ) {
    *user = iui->iui_UserInfo;
    retval = 0;
  }

  ReleaseSemaphore(&UserSemaphore);

  return(retval);
}

ULONG __saveds __asm LIBNextGroup(register __a0 struct GroupInfo *group)

{
  ULONG retval = ENVOYERR_LASTGROUP;
  struct InternalGroupInfo *igi = 0;

  D(bug("NextGroup\n"));

  ObtainSemaphoreShared(&GroupSemaphore);

  if(group->gi_GroupID != 0 || ((igi = findgid(0)) != 0 && 
     strcmp(igi->igi_GroupInfo.gi_GroupName,group->gi_GroupName) == 0)) {

    if(igi == 0) igi = findgroup(group->gi_GroupName,group->gi_GroupID);

    if(igi) igi = (struct InternalGroupInfo *)(igi->igi_Node.mln_Succ);  
    else retval = ENVOYERR_UNKNOWNGROUP;
  }
  else { /* take first */   
    igi = (struct InternalGroupInfo *)(GroupList.mlh_Head);  
  }

  if(igi->igi_Node.mln_Succ) {
    *group = igi->igi_GroupInfo;
    retval = 0;
  }

  ReleaseSemaphore(&GroupSemaphore);

  return(retval);
}

ULONG __saveds __asm LIBNextMember(register __a0 struct GroupInfo *group,
                                   register __a1 struct UserInfo *user)

{
  ULONG retval = ENVOYERR_LASTMEMBER;
  struct InternalUserInfo *iui;
  struct InternalGroupInfo *igi;

  D(bug("NextMember\n"));

  ObtainSemaphoreShared(&UserSemaphore);
  ObtainSemaphoreShared(&GroupSemaphore);

  if(group->gi_GroupID) {
    igi = findgid(group->gi_GroupID);
  }
  else {
    igi = findgname(group->gi_GroupName);
    if(igi == 0) igi = findgid(0);
  }
  if(igi) {
    *group = igi->igi_GroupInfo;

    if(user->ui_UserID != 0
#ifdef NOBODY
       || stricmp(user->ui_UserName,NOBODYNAME) == 0
#endif
      ) { /* takenext */

      iui = finduser(user->ui_UserName,user->ui_UserID);

      if(iui) iui = (struct InternalUserInfo *)(iui->iui_Node.mln_Succ);  
      else retval = ENVOYERR_UNKNOWNUSER;
    }
    else { /* take first */   
      iui = (struct InternalUserInfo *)(UserList.mlh_Head);  
    }
    
    while(iui->iui_Node.mln_Succ) {
      if(iui->iui_UserInfo.ui_PrimaryGroupID == group->gi_GroupID ||
         issecmem(iui,igi)) break;
      iui = (struct InternalUserInfo *)(iui->iui_Node.mln_Succ);  
    }

    if(iui->iui_Node.mln_Succ) {
      *user = iui->iui_UserInfo;
      retval = 0;
    }
  }
  else retval = ENVOYERR_UNKNOWNGROUP;
    
  ReleaseSemaphore(&GroupSemaphore);
  ReleaseSemaphore(&UserSemaphore);

  return(retval);
}

/* memberof */

ULONG __saveds __asm LIBMemberOf(register __a0 struct GroupInfo *group,
                                 register __a1 struct UserInfo *user)

{
  ULONG retval = ENVOYERR_UNKNOWNMEMBER;
  struct InternalUserInfo *iui;
  struct InternalGroupInfo *igi;

  D(bug("MemberOf\n"));

  ObtainSemaphoreShared(&UserSemaphore);
  ObtainSemaphoreShared(&GroupSemaphore);

  if(user->ui_UserID) {
    iui = finduid(user->ui_UserID);
  }
  else {
    iui = finduname(user->ui_UserName);
#ifdef NOBODY
    if(iui == 0) iui = finduid(0);
#endif
  }
  if(iui) {
    *user = iui->iui_UserInfo;

    if(group->gi_GroupID) {
      igi = findgid(group->gi_GroupID);
    }
    else {
      igi = findgname(group->gi_GroupName);
      if(igi == 0) igi = findgid(0);
    }
    if(igi) {
      *group = igi->igi_GroupInfo;
      if(user->ui_PrimaryGroupID == group->gi_GroupID || issecmem(iui,igi)) {
        retval = 0;
      }
    }
    else retval = ENVOYERR_UNKNOWNGROUP;
  }
  else retval = ENVOYERR_UNKNOWNUSER;

  ReleaseSemaphore(&GroupSemaphore);
  ReleaseSemaphore(&UserSemaphore);

  D(bug("MemberOf-result: %ld\n",retval));

  return(retval);
}

/* encryption & verification */

STRPTR __saveds __asm LIBECrypt(register __a0 STRPTR buff,
                                register __a1 STRPTR passwd,
                                register __a2 STRPTR uname)

{
  D(bug("ECrypt: Name: %s, Pwd: %s\n",uname,passwd)); 

  return(ACrypt(buff,passwd,uname));
}

ULONG __saveds __asm LIBVerifyUser(register __a0 STRPTR uname,
                                   register __a1 STRPTR passwd,
                                   register __a2 struct UserInfo *user)

{
  UBYTE buff[16];

  D(bug("VerifyUser: Name: %s, Pwd: %s\n",uname,passwd));

  if(ACrypt(buff,passwd,uname)) {
    return(VerifyUserCrypt(uname,buff,user));
  }
  else {
    return(ENVOYERR_NORESOURCES);
  }
}

ULONG __saveds __asm LIBVerifyUserCrypt(register __a0 STRPTR uname,
                                        register __a1 STRPTR passwd,
                                        register __a2 struct UserInfo *user)

{
  ULONG retval = ENVOYERR_UNKNOWNUSER;
  struct InternalUserInfo *iui;

  D(bug("VerifyUserCrypt: Name: %s, Pwd: %s\n",uname,passwd));

  ObtainSemaphoreShared(&UserSemaphore);

#ifdef NOBODY
  if(stricmp(NOBODYNAME,uname) == 0) uname = NOBODYNAME; /* normalize */
#endif

  iui = finduname(uname);
  if(iui && (iui->iui_CryptPasswd[0] == '\0' ||
     strcmp(iui->iui_CryptPasswd,passwd) == 0)) {
    *user = iui->iui_UserInfo;
    retval = 0;
  }

  ReleaseSemaphore(&UserSemaphore);

  D(bug("VerifyUserCrypt: %ld\n",retval));

  return(retval);
}
