/* connect.c  15-Apr-00 */
/* (C)opyright (C) 1993-2000, Martin Stover, Marburg, Germany
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* history since 01-Sep-00
 * mst:01-Sep-00: pcz:added real unix rights patch from Przemyslaw Czerpak
 *
 */


#include "net.h"
#include "unxfile.h"

#include <dirent.h>
#include <utime.h>

/* #define TEST_FNAME   "PRINT.000"
*/

int use_mmap=USE_MMAP;
int tells_server_version=1;   /* defaults to 3.11 */
int server_version_flags=0;
int max_burst_send_size=0x2000;
int max_burst_recv_size=0x2000;

int  default_uid=-1;
int  default_gid=-1;


#ifdef TEST_FNAME
  static int test_handle=-1;
#endif

static int  default_umode_dir  = 0751;
static int  default_umode_file = 0640;
static int  act_umode_dir=0;
static int  act_umode_file=0;

#include "nwfname.h"
#include "nwvolume.h"
#include "nwshare.h"
#include "nwattrib.h"
#include "nwarchive.h"
#include "trustee.h"
#include "nwfile.h"
#include "nwconn.h"
#include "namspace.h"
#include "namedos.h"
#include "connect.h"

/* connect.h may already be include-guarded through another header before
 * NW_VOL is visible, so keep this local forward declaration before the
 * first call in build_dir_name().
 */
void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp, int len);
static void build_dos_attr_name(int volume, uint8 *path, char *unixname,
                                struct stat *stb, uint8 *out, int out_len);


typedef struct {
   dev_t  dev;          /* unix dev           */
   ino_t  inode;        /* unix inode         */
   time_t timestamp;    /* Zeitmarke          */
   uint8  *path;        /* path ab Volume     */
   uint8  volume;       /* Welches Volume     */
   uint8  is_temp;      /* 0:perm. 1:temp 2: spez. temp */
   uint8  drive;        /* driveletter        */
   uint8  task;         /* actual task        */
} NW_DIR;


NW_DIR    dirs[MAX_NW_DIRS];
int       used_dirs=0;

#define MAX_EXTRACTED_BASE_HANDLES 64

typedef struct {
  uint32 id;
  int    volume;
  uint8  *path;
  dev_t  dev;
  ino_t  inode;
  uint8  task;
} EXTRACTED_BASE_HANDLE;

static EXTRACTED_BASE_HANDLE extracted_base_handles[MAX_EXTRACTED_BASE_HANDLES];
static uint32 next_extracted_base_handle = 1;

static void clear_extracted_base_handles(int task)
{
  int j;
  for (j = 0; j < MAX_EXTRACTED_BASE_HANDLES; j++) {
    EXTRACTED_BASE_HANDLE *ebh = &(extracted_base_handles[j]);
    if (ebh->id && (task < 0 || ebh->task == (uint8)task)) {
      xfree(ebh->path);
      memset(ebh, 0, sizeof(*ebh));
    }
  }
  if (task < 0) next_extracted_base_handle = 1;
}
int       act_uid=-1;     /* unix uid 0=root */
int       act_gid=-1;     /* unix gid */
int       act_obj_id=0L;  /* mars_nwe UID, 0=not logged in, 1=supervisor  */
int       act_id_flags=0; /* &1 == supervisor equivalence !!! */
int       entry8_flags=0; /* special flags, see examples nw.ini, entry 8 */
int       entry31_flags=0; /* not used yet */

static gid_t *act_grouplist=NULL;  /* first element is counter !! */

static int       connect_is_init = 0;

#define MAX_DIRHANDLES    80
#define EXPIRE_COST	  86400 /* Is one day enough? :) */

typedef struct {
  DIR    *f;
  char   unixname[256]; /* full unixname             */
  dev_t  dev;           /* Unix dev                  */
  ino_t  inode;         /* Unix Inode                */
  time_t timestamp;     /* last allocation           */
  char   *kpath;        /* one char after unixname   */
  int    vol_options;   /* searchoptions             */
  int    volume;        /* Volume Number	     */
  int    sequence;      /* Search sequence           */
  off_t  dirpos;        /* Current pos in unix dir   */
  int    no_search_trustee; /* mst:15-Apr-00 */
} DIR_HANDLE;

static DIR_HANDLE   dir_handles[MAX_DIRHANDLES];

static int anz_dirhandles=0;


/* Last real directory entry found by the old DOS scan code.
 * Some callers rebuild unixname later from the synthetic DOS alias; keep the
 * real parent/name pair so the final attribute formatter can still generate
 * a collision-aware 8.3 alias for display.
 */
static int   last_dos_alias_volume = -1;
static ino_t last_dos_alias_inode  = 0;
static uint8 last_dos_alias_parent[300];
static uint8 last_dos_alias_real[256];
static uint8 last_dos_alias_alias[14];

static void remember_dos_alias_source(int volume, uint8 *parent, uint8 *real,
                                      ino_t inode)
{
  last_dos_alias_volume = volume;
  last_dos_alias_inode  = inode;
  strmaxcpy(last_dos_alias_parent, parent ? (char*)parent : "",
            sizeof(last_dos_alias_parent)-1);
  strmaxcpy(last_dos_alias_real, real ? (char*)real : "",
            sizeof(last_dos_alias_real)-1);
  last_dos_alias_alias[0] = '\0';
  if (parent && real) {
    build_dos_83_alias(get_volume_options(volume), parent, real, inode,
                       last_dos_alias_alias, sizeof(last_dos_alias_alias));
  }
}


static char *build_unix_name(NW_PATH *nwpath, int modus)
/*
 * returns complete UNIX path
 * modus & 1 : ignore fn, (only path)
 * modus & 2 : no  '/' at end
 */
{
  static char unixname[300];    /* must be big enouugh */
  int volume = nwpath->volume;
  char *p;
  if (volume < 0 || volume >= used_nw_volumes
                 || !nw_volumes[volume].unixnamlen
                 || nw_volumes[volume].unixnamlen > 250) {
    errorp(10, "build_unix_name", "volume=%d not ok\n", volume);
    xstrcpy(unixname, "Z/Z/Z/Z"); /*  */
    return(unixname);
  }
  memcpy(unixname, (char*)nw_volumes[volume].unixname,
                   nw_volumes[volume].unixnamlen ); /* first UNIXNAME VOLUME */

  p  = unixname + nw_volumes[volume].unixnamlen;
  p += strmaxcpy(p, (char*)nwpath->path, sizeof(unixname) - nw_volumes[volume].unixnamlen -1);  /* now the path */

  if ( (!(modus & 1)) && nwpath->fn[0])
    strmaxcpy(p, (char*)nwpath->fn, sizeof(unixname) - (int)(p-unixname) -1); /* and now fn  */
  else if ((modus & 2) && (*(p-1) == '/')) {
    if (p > unixname+1) *(--p) = '\0';
    else {
      *p++ = '.';
      *p   = '\0';
    }
  }
  dos2unixcharset(unixname);
  return(unixname);
}


static int new_dir_handle(struct stat *stb, NW_PATH *nwpath)
/*
 * RETURN=errorcode (<0) or dir_handle
 */
{
  int rethandle;
  DIR_HANDLE  *dh   = NULL;
  time_t  akttime   = time(NULL);
  time_t  last_time = akttime;
  int  thandle      = 1;
  int  nhandle      = 0;
  for (rethandle=0; rethandle < anz_dirhandles; rethandle++){
    dh=&(dir_handles[rethandle]);
    if (dh->f) { /* only the actual dir-handle should remain open */
      closedir(dh->f);
      dh->f=NULL;
    }
    if (!dh->inode) {
      if (!nhandle)
        nhandle = rethandle+1;
    } else if (dh->dev == stb->st_dev && dh->inode == stb->st_ino
                && dh->volume == nwpath->volume){
      nhandle   = rethandle+1;
      break;
    } else if (dh->timestamp < last_time){
      thandle   = rethandle+1;
      last_time = dh->timestamp;
    }
  }

  if (!nhandle){
    if (anz_dirhandles < MAX_DIRHANDLES) {
      dh=&(dir_handles[anz_dirhandles]);
      dh->f=NULL;
      rethandle = ++anz_dirhandles;
    } else {
      dh=&(dir_handles[thandle-1]);
      rethandle = thandle;
    }
  } else
    rethandle=nhandle;

  /* init dir_handle */
  dh=&(dir_handles[rethandle-1]);
  dh->kpath = dh->unixname + xstrcpy(dh->unixname, build_unix_name(nwpath, 0));
  if (dh->f) {
    closedir(dh->f);
    dh->f=NULL;
  }

  if ( ( !(dh->no_search_trustee=tru_eff_rights_exists(nwpath->volume, dh->unixname, stb, TRUSTEE_F))) 
       || (dh->no_search_trustee & TRUSTEE_T) ) {
    if ((dh->f=opendir(dh->unixname)) == (DIR*)NULL) {
      if (seteuid(0)) {}
      dh->f=opendir(dh->unixname);
     (void)reseteuid();
    }
  }

  if (NULL != dh->f) {
    dh->volume      = nwpath->volume;
    dh->vol_options = nw_volumes[dh->volume].options;
    dh->dev         = stb->st_dev;
    dh->inode       = stb->st_ino;
    dh->timestamp   = akttime;
    dh->sequence    = 0;
    dh->dirpos      = (off_t)0;
    if (dh->vol_options & VOL_OPTION_REMOUNT) {
      closedir(dh->f);
      dh->f = NULL;
    }
  } else {
    dh->inode       = 0;
    dh->unixname[0] = '\0';
    dh->vol_options = 0;
    dh->kpath       = dh->unixname;
    rethandle       = /* -0x9c */ -0xff;
  }
  return(rethandle);
}

static int free_dir_handle(int dhandle)
{
  if (dhandle > 0 && --dhandle < anz_dirhandles) {
    DIR_HANDLE  *dh=&(dir_handles[dhandle]);
    if (dh->f != (DIR*) NULL) {
      closedir(dh->f);
      dh->f = (DIR*)NULL;
    }
    dh->inode = 0;
    while (anz_dirhandles && !dir_handles[anz_dirhandles-1].inode)
      anz_dirhandles--;
    return(0);
  }
  return(-0x88); /* wrong dir_handle */
}

void set_default_guid(void)
{
  if (seteuid(0)) {}
  setgroups(0, NULL);
  if (setegid(default_gid) < 0 || seteuid(default_uid) < 0) {
    errorp(1, "set_default_guid, !! SecurityAbort !!",
      "Cannot set default gid=%d and uid=%d" , default_gid, default_uid);
    exit(1);
  }
  act_gid        = default_gid;
  act_uid        = default_uid;
  act_umode_dir  = default_umode_dir;
  act_umode_file = default_umode_file;
  xfree(act_grouplist);
}

void set_guid(int gid, int uid)
{
  if ( gid < 0 || uid < 0
     || seteuid(0)
     || setegid(gid)
     || seteuid(uid) ) {
    set_default_guid();
    if (gid < 0  && uid < 0) {
      /* don't print error */
      gid = act_gid;
      uid = act_uid;
    }
  } else if (act_gid != gid || act_uid != uid) {
    struct passwd *pw = getpwuid(uid);
    if (NULL != pw) {
      if (seteuid(0)) {}
      initgroups(pw->pw_name, gid);
    }
    act_gid = gid;
    act_uid = uid;

    act_umode_dir  = default_umode_dir;
    act_umode_file = default_umode_file;

    xfree(act_grouplist);
    if (seteuid(uid))
      set_default_guid();
    else {
      int k=getgroups(0, NULL);
      if (k > 0) {
        act_grouplist=(gid_t*)xmalloc((k+1) * sizeof(gid_t));
        if (getgroups(k, act_grouplist+1) < 0) {}
        *act_grouplist=(gid_t)k;
      }
    }

  }
  XDPRINTF((5,0,"SET GID=%d, UID=%d %s", gid, uid,
        (gid==act_gid && uid == act_uid) ? "OK" : "failed"));
}

void reset_guid(void)
{
  set_guid(act_gid, act_uid);
}

void reseteuid(void)
{
  if (seteuid(act_uid))
    reset_guid();
}

int in_act_groups(gid_t gid)
/* returns 1 if gid is member of act_grouplist else 0 */
{
  int    k;
  gid_t *g;
  if (!act_grouplist) return(0);
  k=(int)*act_grouplist;
  g = act_grouplist;
  while (k--) {
    if (*(++g) == gid) return(1);
  }
  return(0);
}

/* pcz:01-Sep-00 */ 
int get_unix_access_rights(struct stat *stb, uint8 *unixname)
/* returns F_OK, R_OK, W_OK, X_OK  */
/* ----- old ----------------------*/
/* ORED with 0x10 if owner access  */
/* ORED with 0x20 if group access  */
/* ----- corrent-------------------*/
/* ORED with 0x20 if W_OK  access  */
/* --------------------------------*/
{
  int mode=0;
  uid_t ruid, euid, rgid;

  ruid=getuid();
  euid=geteuid();
  rgid=getgid();

  if (setreuid(act_uid,0)) {}
  if (setgid(act_gid)) {}
  
  if (!access(unixname, F_OK)) {

    if (!access(unixname, R_OK))
      mode |= R_OK;
    if (!access(unixname, W_OK))
      /* mode |= W_OK; */
      mode |= W_OK | 0x20;
    if (!access(unixname, X_OK))
      mode |= X_OK;

    /* mode |= get_unix_eff_rights(stb) & ~(R_OK|W_OK|X_OK); */
  }
  if (setgid(rgid)) {}
  if (setreuid(ruid, euid)) {}


  return(mode);
}


int get_unix_eff_rights(struct stat *stb)
/* returns F_OK, R_OK, W_OK, X_OK  */
/* ORED with 0x10 if owner access  */
/* ORED with 0x20 if group access  */
{
  int mode = 0;
  if (!act_uid)
    return(0x10 | R_OK | W_OK | X_OK) ;  /* root */
  else {
    if (act_uid == stb->st_uid) {
      mode    |= 0x10;
      if (stb->st_mode & S_IXUSR)
        mode  |= X_OK;
      if (stb->st_mode & S_IRUSR)
        mode  |= R_OK;
      if (stb->st_mode & S_IWUSR)
        mode  |= W_OK;
    } else if ( (act_gid == stb->st_gid)
             || in_act_groups(stb->st_gid) ) {
      mode    |= 0x20;
      if (stb->st_mode & S_IXGRP)
        mode  |= X_OK;
      if (stb->st_mode & S_IRGRP)
        mode  |= R_OK;
      if (stb->st_mode & S_IWGRP)
        mode  |= W_OK;
    } else {
      if (stb->st_mode & S_IXOTH)
        mode  |= X_OK;
      if (stb->st_mode & S_IROTH)
        mode  |= R_OK;
      if (stb->st_mode & S_IWOTH)
        mode  |= W_OK;
    }
  }
  return(mode);
}



/* next routine is called after new login */
void set_nw_user(int gid, int uid,
                 int id_flags,
                 uint32 obj_id,   uint8 *objname,
                 int unxloginlen, uint8 *unxloginname,
                 int grpcount,    uint32 *grps)
{
  uint8 unxlogin[20];
  strmaxcpy(unxlogin, unxloginname, min(unxloginlen, sizeof(unxlogin)-1));
  nwconn_set_program_title(objname);
  set_guid(gid, uid);
  act_obj_id   = obj_id;
  act_id_flags = id_flags;
  XDPRINTF((5, 0, "actual obj_id is set to 0x%x", obj_id));
  if ((act_id_flags&1) && obj_id != 1) {
    XDPRINTF((1, 0, "logged in user %s (%s) has supervisor privilegs", objname, unxlogin));
  }
  nw_setup_vol_opts(act_gid, act_uid,
                    act_umode_dir, act_umode_file,
                    unxlogin);
  tru_init_trustees(grpcount, grps);
}


uint32 get_file_owner(struct stat *stb)
/* returns nwuser creator of file */
{
  uint32 owner=1L;  /* default Supervisor */
  if (act_obj_id && stb && (stb->st_uid == act_uid))
    owner=act_obj_id;
  XDPRINTF((5, 0, "get_file_owner owner=0x%x", owner));
  return(owner);
}

static char *conn_get_nwpath_name(NW_PATH *p)
/* for debugging */
{
static char nwpathname[300];
  char volname[100];
  if (p->volume < 0 || p->volume >= used_nw_volumes) {
    slprintf(volname, sizeof(volname)-1, "<%d=NOT-OK>", (int)p->volume);
  } else xstrcpy(volname, (char*)nw_volumes[p->volume].sysname);
  slprintf(nwpathname, sizeof(nwpathname)-1, "%s:%s%s", volname, p->path, p->fn);
  return(nwpathname);
}

/* new from Andrew Sapozhnikov <sapa@hq.icb.chel.su>
 * added in 0.99.pl7, removes old x_str_match routine
 * Do not work for namespace routines, see namespace.c !!
 */
/* NW3.12/MSDOS-like filename matching. *
 * Wildcards '*' and '?' allowed.	*/
static int x_str_match(uint8 *s, uint8 *p, int soptions)
{
  while (*p) {
    switch (*p) {
      case 0xbf:
      case '?': if (*s && *s != '.') s++;
      		p++;
      		break;
#if 0
      case 0xaa:
      case '*': while (*s && *s != '.') s++;
		while (*p && *p != '.' && *p != 0xae) p++;
		break;
#else
      /* changed 27-May-98, 0.99.pl10 to handle '*' correct,
       * and to handle '*xy.xyz' in a `better` (non DOS) way.
       * now I know again why my old routine was so terrible ;-)
       */

      case 0xaa:
      case '*': ++p;
                if (*p) {
                  while (*s && *s != '.') {
                    if (x_str_match(s, p, soptions))
                      return(1);
                    else
                      s++;
                  }
                } else {
                  if (*(p-1) == '*') /* last star '*' match everything */
                    return(1);
                  else  /* but  0xaa not */
                    while (*s && *s != '.') s++;
                }
		break;
#endif

      case 0xae:
      case '.': if (*s) {
      		  if(*s != '.') return 0;
		  else s++;
                }
		p++;
		break;
      default : if (! ((soptions & VOL_OPTION_IGNCASE) ?
      			dfn_imatch(*s, *p) : (*s == *p)) ) return 0;
      		p++;
      		s++;

    }
  }
  return (*s ? 0 : 1);
}

int fn_dos_match(uint8 *s, uint8 *p, int options)
{
  uint8 *ss=s;
  int   len=0;
  int   pf=0;
  uint8 p_buff[200];
  uint8 *pp=p_buff;

  for (; *p; p++){
    if (*p != 0xff)
      *pp++=*p;
  }
  *pp='\0';
  p=p_buff;

  for (; *ss; ss++){
    if (*ss == '.') {
      if (pf++) return(0); /* no 2. point */
      len=0;
    } else {
      ++len;
      if ((pf && len > 3) || len > 8) return(0);

      if (!(options & VOL_OPTION_IGNCASE)){
        if (options & VOL_OPTION_DOWNSHIFT){   /* only downshift chars */
          if (*ss >= 'A' && *ss <= 'Z') return(0);
        } else {            /* only upshift chars   */
          if (*ss >= 'a' && *ss <= 'z') return(0);
        }
      }

    }
  }
  return(x_str_match(s, p, options));
}

typedef struct {
  int        attrib;
  struct stat statb;
  uint8      *ubuf;   /* userbuff */
} FUNC_SEARCH;

static int func_search_entry(NW_PATH *nwpath, int attrib,
        int (*fs_func)(NW_PATH *nwpath, FUNC_SEARCH *fs), FUNC_SEARCH *fs)

/* returns  > 0  if OK  < 1 if not ok or not found */
{
  struct dirent* dirbuff;
  DIR            *f=NULL;
  int            result=0;
  int            okflag=0;
  char           xkpath[256];
  uint8          entry[256];
  int            volume = nwpath->volume;
  int            soptions;
  FUNC_SEARCH    fs_local;
  if (!fs) {
    fs        = &fs_local;
    fs->ubuf  = NULL;
  }
  fs->attrib  = attrib;
  if (volume < 0 || volume >= used_nw_volumes) return(-1); /* something wrong */
  else  soptions = nw_volumes[volume].options;
  xstrcpy(entry,  (char*)nwpath->fn);

  nwpath->fn[0] = '\0';
  xstrcpy(xkpath, build_unix_name(nwpath, 1|2));

  XDPRINTF((5,0,"func_search_entry attrib=0x%x path:%s:, xkpath:%s:, entry:%s:",
        attrib, nwpath->path, xkpath, entry));

  if ( (!stat(xkpath, &(fs->statb)))
    && !tru_eff_rights_exists(volume, xkpath, &(fs->statb), TRUSTEE_F)) {
    if ((f=opendir(xkpath)) == (DIR*)NULL) {
      if (seteuid(0)) {}
      f=opendir(xkpath);
     (void)reseteuid();
    }
  }

  if (f != (DIR*)NULL) {
    char *kpath=xkpath+strlen(xkpath);
    *kpath++ = '/';
    while ((dirbuff = readdir(f)) != (struct dirent*)NULL){
      okflag = 0;
      if (dirbuff->d_ino) {
        uint8 *name=(uint8*)(dirbuff->d_name);
        uint8 dname[256];
        uint8 dosalias[14];
        xstrcpy(dname, name);
        unix2doscharset(dname);
        *kpath = '\0';
        build_dos_83_alias(soptions, (uint8*)xkpath, name,
                           dirbuff->d_ino, dosalias, sizeof(dosalias));
        okflag = (name[0] != '.' &&
                 ( (!strcmp((char*)dosalias, (char*)entry))
                 || fn_dos_match(dosalias, entry, soptions)));
        if (okflag) {
          strmaxcpy(kpath, (char*)name, sizeof(xkpath) - (int)(kpath-xkpath) -1 );
          if (!s_stat(xkpath, &(fs->statb), NULL)) {
            okflag = (  ( ( (fs->statb.st_mode & S_IFMT) == S_IFDIR) &&  (attrib & 0x10))
                  ||    ( ( (fs->statb.st_mode & S_IFMT) != S_IFDIR) && !(attrib & 0x10)));
            if (okflag){
              *kpath = '\0';
              remember_dos_alias_source(volume, (uint8*)xkpath, name,
                                        fs->statb.st_ino);
              xstrcpy(nwpath->fn, (char*)dosalias);
              XDPRINTF((5,0,"FOUND=:%s: attrib=0x%x", nwpath->fn, fs->statb.st_mode));
              result = (*fs_func)(nwpath, fs);
              if (result < 0) break;
              else result=1;
            }
          } else okflag = 0;
        }
        XDPRINTF((6,0, "NAME=:%s: OKFLAG %d", name, okflag));
      }  /* if */
    } /* while */
    closedir(f);
  } /* if */
  return(result);
}

static int get_dir_entry(NW_PATH *nwpath,
                          int    *sequence,
                          int    attrib,
                          struct stat *statb)

/* returns 0 if OK and errcode if not OK */
{
  struct dirent* dirbuff;
  DIR            *f=NULL;
  int            okflag=-0xff; /* not found */
  char           xkpath[256];
  uint8          entry[256];
  int            volume = nwpath->volume;
  int            soptions;
  int            akt_sequence=0;
  int            no_search_trustee = 0;

  if (volume < 0 || volume >= used_nw_volumes) return(-0x98); /* something wrong */
  else  soptions = nw_volumes[volume].options;
  xstrcpy(entry,  (char*)nwpath->fn);
  nwpath->fn[0] = '\0';
  xstrcpy(xkpath, build_unix_name(nwpath, 1|2));
  XDPRINTF((5,0,"get_dir_entry attrib=0x%x path:%s:, xkpath:%s:, entry:%s:",
                          attrib, nwpath->path, xkpath, entry));

  if ( (!stat(xkpath, statb))
    && ( (!(no_search_trustee = tru_eff_rights_exists(volume, xkpath, statb, TRUSTEE_F))) 
       || (no_search_trustee&TRUSTEE_T)) ) {
    if ((f=opendir(xkpath)) == (DIR*)NULL) {
      if (seteuid(0)) {}
      f=opendir(xkpath);
     (void)reseteuid();
    }
  }

  if (f != (DIR*)NULL) {
    char *kpath=xkpath+strlen(xkpath);
    *kpath++ = '/';
    if (*sequence == MAX_U16) *sequence = 0;

    while (akt_sequence++ < *sequence) {
      if (NULL == readdir(f)) {
        closedir(f);
        return(-0xff);
      }
    }

    while ((dirbuff = readdir(f)) != (struct dirent*)NULL){
      okflag = -0xff;
      (*sequence)++;
      if (dirbuff->d_ino) {
        uint8 *name=(uint8*)(dirbuff->d_name);
        uint8 dname[256];
        uint8 dosalias[14];
        xstrcpy(dname, name);
        unix2doscharset(dname);
        *kpath = '\0';
        build_dos_83_alias(soptions, (uint8*)xkpath, name,
                           dirbuff->d_ino, dosalias, sizeof(dosalias));
        okflag = ((name[0] != '.' &&
                 ( (!strcmp((char*)dosalias, (char*)entry))
                 || fn_dos_match(dosalias, entry, soptions)))) ? 0 : -0xff;
        if (!okflag) {
          strmaxcpy(kpath, (char*)name, sizeof(xkpath) - (int)(kpath-xkpath) -1);
          if (!s_stat(xkpath, statb, NULL)) {
            okflag = ((  ( ( (statb->st_mode & S_IFMT) == S_IFDIR) &&  (attrib & 0x10))
                  ||    ( ( (statb->st_mode & S_IFMT) != S_IFDIR) && !(attrib & 0x10))))
                  ? 0 : -0xff;
            if (!okflag){
              if ( (!no_search_trustee) ||
                  !tru_eff_rights_exists(volume, xkpath, statb, TRUSTEE_T)) {

                if (soptions & VOL_OPTION_IS_PIPE)  {
                  statb->st_size  = 0x70000000|(statb->st_mtime&0xfffffff);
                }
                *kpath = '\0';
                remember_dos_alias_source(volume, (uint8*)xkpath, name,
                                          statb->st_ino);
                XDPRINTF((99,0,"DOSGETDIR parent='%s' real='%s' alias='%s' inode=%ld", xkpath, name, dosalias, (long)statb->st_ino));
                xstrcpy(nwpath->fn, (char*)dosalias);
                XDPRINTF((5,0,"FOUND=:%s: attrib=0x%x", nwpath->fn, statb->st_mode));
                break; /* ready */
              } else
                 okflag = -0xff;
            }
          } else okflag = -0xff;
        }
        XDPRINTF((6,0, "NAME=:%s: OKFLAG %d", name, okflag));
      }  /* if */
    } /* while */
    closedir(f);
  } else okflag=-0x89; /* no search rights */
  return(okflag);
}

static DIR *give_dh_f(DIR_HANDLE    *dh)
{
  if (!dh->f) {
    *(dh->kpath) = '\0';
    if (seteuid(0)) {}
    dh->f  = opendir(dh->unixname);
   (void)reseteuid();
  }
  dh->timestamp=time(NULL); /* tnx to Andrew Sapozhnikov */
  return(dh->f);
}

static void release_dh_f(DIR_HANDLE *dh, int expire)
{
  if (dh->f && (dh->vol_options & VOL_OPTION_REMOUNT) ) {
    closedir(dh->f);
    dh->f = NULL;
  }
  if (expire)
    dh->timestamp-=EXPIRE_COST; /* tnx to Andrew Sapozhnikov */
}

static int get_dh_entry(DIR_HANDLE *dh,
                        uint8  *search,
                        int    size_search,
                        int    *sequence,
                        int    attrib,
                        char   *unixname,
                        int    size_unixname,
                        struct stat *statb)

/* returns 1 if OK and 0 if not OK */
{
  DIR            *f     = give_dh_f(dh);
  int            okflag = 0;

  if (f != (DIR*)NULL) {
    struct  dirent *dirbuff;
    uint8   entry[256];
    xstrcpy(entry, search);
    if ( (uint16)*sequence == MAX_U16)  *sequence = 0;

    if (*sequence < dh->sequence || ((long)dh->dirpos) < 0L) {
      dh->dirpos   = (off_t)0;
      dh->sequence = 0;
    }
    SEEKDIR(f, dh->dirpos);

    if (dh->sequence != *sequence) {
      while (dh->sequence < *sequence) {
        if (NULL == readdir(f)) {
          dh->dirpos = TELLDIR(f);
          release_dh_f(dh, 1);
          return(0);
        }
        dh->sequence++;
      }
      dh->dirpos = TELLDIR(f);
    }
    XDPRINTF((5,0,"get_dh_entry seq=0x%x, attrib=0x%x path:%s:, entry:%s:",
                *sequence, attrib, dh->unixname, entry));

    while ((dirbuff = readdir(f)) != (struct dirent*)NULL){
      okflag = 0;
      dh->sequence++;
      if (dirbuff->d_ino) {
        uint8 *name=(uint8*)(dirbuff->d_name);
        uint8 dname[256];
        uint8 dosalias[14];
        xstrcpy(dname, name);
        unix2doscharset(dname);
        build_dos_83_alias(dh->vol_options, (uint8*)dh->unixname, name,
                           dirbuff->d_ino, dosalias, sizeof(dosalias));
        okflag = (name[0] != '.' &&
                 ( (!strcmp((char*)dosalias, (char*)entry))
                 || fn_dos_match(dosalias, entry, dh->vol_options)));

        if (okflag) {
          strmaxcpy(dh->kpath, (char*)name,
                    sizeof(dh->unixname) - (int)(dh->kpath-dh->unixname) -1 );
          XDPRINTF((5,0,"get_dh_entry Name=%s unixname=%s",
                                  name, dh->unixname));

          if (!s_stat(dh->unixname, statb, NULL)) {
            okflag = ( (( (statb->st_mode & S_IFMT) == S_IFDIR) &&  (attrib & 0x10))
                     || (((statb->st_mode & S_IFMT) != S_IFDIR) && !(attrib & 0x10)));
            
            /* mst:15-Apr-00 */ 
            if (okflag && dh->no_search_trustee  
               && tru_eff_rights_exists(dh->volume, dh->unixname, statb, TRUSTEE_T))
              okflag=0;
            
            if (okflag){
              if (unixname)
                strmaxcpy(unixname, dh->unixname, size_unixname-1);
              *(dh->kpath) = '\0';
              remember_dos_alias_source(dh->volume, (uint8*)dh->unixname,
                                        name, statb->st_ino);
              XDPRINTF((99,0,"DOSDH parent='%s' real='%s' alias='%s' inode=%ld", dh->unixname, name, dosalias, (long)statb->st_ino));
              strmaxcpy((char*)search, (char*)dosalias, size_search-1);
              break; /* ready */
            }
          } else okflag = 0;
        }
      }  /* if */
    } /* while */
    dh->kpath[0] = '\0';
    *sequence  = dh->sequence;
    dh->dirpos = TELLDIR(f);
    release_dh_f(dh, (dirbuff==NULL));
  } /* if */
  return(okflag);
}

static void conn_build_path_fn(uint8 *vol,
                               uint8 *path,
                               uint8 *fn,
                               int   *has_wild,
                               uint8 *data,
                               int   len,
			       int   lenn)

/* is called from build_path  */
{
   uint8  *p  = NULL;
   uint8  *p1 = path;
   *vol       = '\0';
   *has_wild  = 0;     /* no wild char */
   while (len-- && *data){
     if (*data == 0xae) *p1++ = '.';
     else if (*data == 0xaa|| *data == '*' ) {
       /* *p1++ = '*'; */
       *p1++ = *data;
       (*has_wild)++;
     } else if (*data == 0xbf|| *data == '?' ) {
       *p1++ = '?';
       (*has_wild)++;
     } else if (*data == '/' || *data == '\\') {
       *p1++ = '/';
       p = p1;
     } else if (*data == ':') { /* extract volume */
       int len = (int)(p1 - path);
       memcpy(vol, path, len);
       vol[len]   = '\0';
       up_fn(vol);
       p1 = path;
     } else *p1++ = *data;
     data++;
   }
   *p1 = '\0';
   if (fn != NULL) {  /* if with filename     */
     if (p != NULL){  /* exist directory-path  */
       strmaxcpy((char*)fn, (char*)p, 255);
       *p = '\0';
     } else {         /* only filename */
       strmaxcpy((char*)fn, (char*)path, 255);
       *path= '\0';
     }
   }
}

static int build_path( NW_PATH *path,
                       uint8   *data,
                       int     len,
                       int     only_dir)
/*
 * fills path structure with the right values
 * if only_dir > 0, then the full path will be interpreted
 * as directory, in the other way, the last segment of path
 * will be interpreted as fn.
 * returns -0x98, if volume is wrong
 */
{
  uint8 vol[256];
  if (len > 255) len = 255;
  conn_build_path_fn(vol,
                     path->path,
                     (only_dir) ? NULL
                                : path->fn,
                     &(path->has_wild),
                     data, len, sizeof(path->fn));

  path->volume = -1;
  if (only_dir) path->fn[0] = '\0';

  if (vol[0]) {  /* there is a volume in path */
    int j = used_nw_volumes;
    while (j--) {
      if (!strcmp((char*)nw_volumes[j].sysname, (char*)vol)) {
        path->volume = j;
        if (nw_volumes[j].options & VOL_OPTION_DOWNSHIFT) {
          down_fn(path->path);
          down_fn(path->fn);
        } else {
          up_fn(path->path);
          up_fn(path->fn);
        }
        break;
      }
    }
    if (path->volume < 0) return(-0x98);
  }
  return(0);
}

static int nw_path_directory_is_ok(NW_PATH *nwpath, struct stat *stbuff)
/* returns UNIX inode of path  */
{
  int    result=0;
  if ((!act_obj_id) && !(entry8_flags & 1)) {
    if (nwpath->volume)
      result = -0x9c;   /* wrong path, only volume 0 is OK */
    else {
      char *p=nwpath->path;
      char pp[10];
      while (*p=='/') ++p;
      strmaxcpy(pp, p, 6);
      up_fn(pp);
      p=pp+5;
      if (memcmp(pp, "LOGIN", 5) || (*p!='\0' && *p!='/') )
        result=-0x9c;
    }
  }
  if (!result && !s_stat(build_unix_name(nwpath, 1 | 2 ), stbuff, NULL)
      && S_ISDIR(stbuff->st_mode))
    return(stbuff->st_ino);
  else if (!result)
    result = -0x9c;   /* wrong path */
  XDPRINTF((4,0x10, "NW_PATH_OK failed:`%s`", conn_get_nwpath_name(nwpath)));
  return(result);
}

static int build_dir_name(NW_PATH *nwpath,     /* gets complete path     */
                           struct  stat *stbuff,
                           int     dir_handle)  /* search with dirhandle  */

/* return -completition code or inode */
{
   uint8      searchpath[256];
   uint8      *p=searchpath;
   uint8      *ppp=nwpath->path;
   int        completition=0;

   xstrcpy(searchpath, (char*)ppp);  /* save path */

   if (nwpath->volume > -1) { /* absolute path */
     *ppp= '\0';
   } else  {  /* volume not kwown yet, I must get it about dir_handle */
     if (dir_handle > 0 &&
       --dir_handle < (int)used_dirs && dirs[dir_handle].inode){
       nwpath->volume = dirs[dir_handle].volume;
       if (searchpath[0] == '/') { /* absolute path */
         p++;
         *ppp = '\0';
       } else { /* get path from dir_handle */
         NW_VOL *v = &nw_volumes[nwpath->volume];
         strmaxcpy((char*)ppp, (char*)dirs[dir_handle].path,
                  sizeof(nwpath->path) - (int)(ppp-nwpath->path) -1 );
         if (v->options & VOL_OPTION_IGNCASE)
            ppp += strlen(ppp);
       }
     } else return(-0x9b); /* wrong dir handle */
   }
   if (*p) {
     uint8  *panf = nwpath->path;
     uint8  *p1   = panf+strlen((char*)panf);
     uint8  *a    = p;
     uint8  w;
     int    state = 0;
     while ((!completition) && (w = *p++) > 0){
       if (!state){
         XDPRINTF((5,0,"in build_dir_name path=:%s:", nwpath->path));
         if (w == '.')      state = 20;
         else if (w == '/') state = 30;
         else state++;
       } else if (state < 9){
         if (w == '.')      state = 10;
         else if (w == '/') state = 30;
         else state++;
         if (state == 9) completition= -0x9c; /* something wrong */
       } else if (state < 14){
         if (w == '.') return(-0x9c);
         else if (w == '/') state = 30;
         else state++;
         if (state == 14) completition= -0x9c; /* something wrong  */
       } else if (state == 20){
         if (ppp > nwpath->path)
            ppp=nwpath->path;
         if (w == '/') state = 30;
         else if (w != '.') completition= -0x9c; /* something wrong  */
       }
       if (state == 30 || !*p) { /* now action */
         uint8 *xpath=a;
         int   len = (int)(p-a);
         if (len && state == 30) --len; /* '/' don not need it here */
         a = p;
         if (len) {
           if (*xpath == '.') {
             uint8 *xp=xpath+1;
             if (*xp == '.') {
               while (*xp++ == '.' && completition > -1) {
                 p1--; /* steht nun auf letztem Zeichen '/' od. ':' */
                 if (p1 < panf) completition = -0x9c ;
                  /* wrong path, don't can go back any more */
                 else {
                   while (p1 > panf && *(--p1) != '/');;
                   if (p1 == panf) *p1='\0';
                   else *(++p1) = '\0';
                 }
               }
             }
           } else {
             memcpy(p1, xpath, len);
             p1   += len;
             *p1++ = '/';
             *p1   = '\0';
           }
         } /* if len */
         state = 0;
       }
     }
   }
   if (!completition) {
     if (nwpath->volume > -1 && nwpath->volume < used_nw_volumes){
       NW_VOL *v = &nw_volumes[nwpath->volume];
       if (v->options & VOL_OPTION_DOWNSHIFT) {
         down_fn(ppp);
         down_fn(nwpath->fn);
       } else {
         up_fn(ppp);
         up_fn(nwpath->fn);
       }
       if (v->options & VOL_OPTION_IGNCASE) {
         uint8 unixname[1024]; /* should be enough */
         uint8 *pp=unixname+v->unixnamlen;
         int   offset  = ppp - nwpath->path;
         int   pathlen = strlen(nwpath->path);
         int   fnlen   = strlen(nwpath->fn);
         memcpy(unixname, v->unixname, v->unixnamlen);
         strmaxcpy(pp, nwpath->path, sizeof(unixname) - v->unixnamlen-1);
         if (fnlen)
           strmaxcpy(pp+pathlen, nwpath->fn,
                   sizeof(unixname) - v->unixnamlen - pathlen-1 );
         dos2unixcharset(pp);
         pp      += offset;
         pathlen -= offset;
         mangle_dos_name(v, unixname, pp, sizeof(unixname) - (int)(pp - unixname));
         unix2doscharset(pp);
         XDPRINTF((5, 0, "Mangled DOS/unixname=%s", unixname));
           memcpy(ppp, pp, pathlen);
         if (fnlen)
           memcpy(nwpath->fn, pp+pathlen, fnlen);
       }
     } else return(-0x98); /* wrong volume */
     completition = nw_path_directory_is_ok(nwpath, stbuff);
   }
   return(completition);
}

static int conn_get_kpl_path(NW_PATH *nwpath, struct stat *stbuff,
                          int dirhandle, uint8 *data, int len, int only_dir)
/*
 * if ok then the inode of dir will be returned
 * else a negativ errcode will be returned
 */
{
   int completition = build_path(nwpath, data, len, only_dir);
   XDPRINTF((5, 0, "compl=0x%x, conn_get_kpl_path %s",
      completition, conn_get_nwpath_name(nwpath)));
   if (!completition) completition = build_dir_name(nwpath, stbuff, dirhandle);
   return(completition);
}

int conn_get_full_path(int dirhandle, uint8 *data, int len,
                       uint8 *fullpath, int size_fullpath)
/* returns path in form VOLUME:PATH */
{
  NW_PATH nwpath;
  struct stat stbuff;
  int result = build_path(&nwpath, data, len, 0);
  fullpath[0]='\0';
  if (!result)
     result = build_dir_name(&nwpath, &stbuff, dirhandle);
  if (result > -1) {
    uint8 *p=(*nwpath.path=='/') ? nwpath.path+1 : nwpath.path;
    int len = slprintf(fullpath, size_fullpath-1, "%s:%s",
       nw_volumes[nwpath.volume].sysname, p);
    if (len > 0) {
      if (nwpath.fn[0]) {
        if (*p) fullpath[len++]='/';
        strmaxcpy(fullpath+len, nwpath.fn, size_fullpath - len -1);
      }
      result = len + strlen(nwpath.fn);
    } else result = -0x9c; /* wrong path */
  }
  XDPRINTF((1, 0, "conn_get_full_path: result=%d,(0x%x),`%s`", result, result, fullpath));
  return(result);
}

int conn_get_kpl_unxname(char *unixname,
                         int size_unixname,
                         int dirhandle,
                         uint8 *data, int len)
/*
 * gives the unixname of dirhandle + path
 * returns volumenumber, or < 0 if error
 */
{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = build_path(&nwpath, data, len, 0);
  if (!completition)
     completition = build_dir_name(&nwpath, &stbuff, dirhandle);
  if (completition > -1) {
    if (unixname)
      strmaxcpy(unixname, build_unix_name(&nwpath, 0), size_unixname-1);
    completition=nwpath.volume;
  }
  XDPRINTF((5, 0, "conn_get_kpl_unxname: completition=0x%x", completition));
  return(completition);
}

void un_date_2_nw(time_t time, uint8 *d, int high_low)
{
  struct tm  *s_tm=localtime(&time);
  uint16  xdate=s_tm->tm_year - 80;
  xdate <<= 4;
  xdate |= s_tm->tm_mon+1;
  xdate <<= 5;
  xdate |= s_tm->tm_mday;
  if (high_low) {
    U16_TO_BE16(xdate, d);
  } else {
    U16_TO_16(xdate, d);
  }
}

void un_time_2_nw(time_t time, uint8 *d, int high_low)
{
  struct tm  *s_tm=localtime(&time);
  uint16  xdate=s_tm->tm_hour;
  xdate <<= 6;
  xdate |= s_tm->tm_min;
  xdate <<= 5;
  xdate |= (s_tm->tm_sec / 2);
  if (high_low) {
    U16_TO_BE16(xdate, d);
  } else {
    U16_TO_16(xdate, d);
  }
}

time_t nw_2_un_time(uint8 *d, uint8 *t)
{
  uint16 xdate = GET_BE16(d);
  uint16 xtime = (t != (uint8*) NULL) ? GET_BE16(t) : 0;

  int year     = (xdate >> 9) + 80;
  int month    = (xdate >> 5) & 0x0F;
  int day      = xdate & 0x1f;
  int hour     = xtime >> 11;
  int minu     = (xtime >> 5) & 0x3f;
  int sec      = (xtime & 0x1f) << 1;  /* Tnx to Csoma Csaba */
  struct tm    s_tm;
  s_tm.tm_year   = year;
  s_tm.tm_mon    = month-1;
  s_tm.tm_mday   = day;
  s_tm.tm_hour   = hour;
  s_tm.tm_min    = minu;
  s_tm.tm_sec    = sec;
  s_tm.tm_isdst  = -1;
  return(mktime(&s_tm));
}

static void un_time_2_nw_parts(time_t t, uint16 *date, uint16 *timev)
{
  uint8 b[2];

  if (date) {
    un_date_2_nw(t, b, 0);
    *date = (uint16)GET_16(b);
  }
  if (timev) {
    un_time_2_nw(t, b, 0);
    *timev = (uint16)GET_16(b);
  }
}

static time_t nw_2_un_time_parts(uint16 xdate, uint16 xtime)
{
  int year  = (xdate >> 9) + 80;
  int month = (xdate >> 5) & 0x0f;
  int day   = xdate & 0x1f;
  int hour  = xtime >> 11;
  int minu  = (xtime >> 5) & 0x3f;
  int sec   = (xtime & 0x1f) << 1;
  struct tm s_tm;

  if (month < 1 || month > 12 || day < 1 || day > 31 ||
      hour > 23 || minu > 59 || sec > 59)
    return((time_t)-1);

  memset(&s_tm, 0, sizeof(s_tm));
  s_tm.tm_year  = year;
  s_tm.tm_mon   = month - 1;
  s_tm.tm_mday  = day;
  s_tm.tm_hour  = hour;
  s_tm.tm_min   = minu;
  s_tm.tm_sec   = sec;
  s_tm.tm_isdst = -1;
  return(mktime(&s_tm));
}

#define DM_ATTRIBUTES             0x0002UL
#define DM_CREATE_DATE            0x0004UL
#define DM_CREATE_TIME            0x0008UL
#define DM_CREATOR_ID             0x0010UL
#define DM_ARCHIVE_DATE           0x0020UL
#define DM_ARCHIVE_TIME           0x0040UL
#define DM_ARCHIVER_ID            0x0080UL
#define DM_MODIFY_DATE            0x0100UL
#define DM_MODIFY_TIME            0x0200UL
#define DM_MODIFIER_ID            0x0400UL
#define DM_LAST_ACCESS_DATE       0x0800UL
#define DM_INHERITED_RIGHTS_MASK  0x1000UL
#define DM_MAXIMUM_SPACE          0x2000UL
#define DM_NCP22_25_TIME_BITS \
        (DM_MODIFY_DATE | DM_MODIFY_TIME | DM_LAST_ACCESS_DATE)
#define DM_NCP22_25_ARCHIVE_BITS \
        (DM_ARCHIVE_DATE | DM_ARCHIVE_TIME | DM_ARCHIVER_ID)
#define DM_NCP22_25_SUPPORTED_BITS \
        (DM_ATTRIBUTES | DM_CREATE_DATE | DM_CREATE_TIME | DM_CREATOR_ID | \
         DM_NCP22_25_ARCHIVE_BITS | DM_NCP22_25_TIME_BITS | \
         DM_MODIFIER_ID | DM_INHERITED_RIGHTS_MASK | DM_MAXIMUM_SPACE)

static int set_ncp22_25_times(int volume, char *unixname, struct stat *stb,
                              NW_SET_DIR_INFO *f, uint32 change_mask,
                              int is_dir)
{
  uint16 mdate;
  uint16 mtimev;
  uint16 adate;
  uint16 atimev;
  struct utimbuf ut;

  if (!(change_mask & DM_NCP22_25_TIME_BITS))
    return(0);

  if (tru_eff_rights_exists(volume, unixname, stb, TRUSTEE_M))
    return(-0x8c);

  ut.actime = stb->st_atime;
  ut.modtime = stb->st_mtime;

  if (change_mask & (DM_MODIFY_DATE | DM_MODIFY_TIME)) {
    un_time_2_nw_parts(stb->st_mtime, &mdate, &mtimev);

    if (is_dir) {
      if (change_mask & DM_MODIFY_DATE)
        mdate = (uint16)GET_16(f->u.d.modify_date);
      if (change_mask & DM_MODIFY_TIME)
        mtimev = (uint16)GET_16(f->u.d.modify_time);
    } else {
      if (change_mask & DM_MODIFY_DATE)
        mdate = (uint16)GET_16(f->u.f.updated.date);
      if (change_mask & DM_MODIFY_TIME)
        mtimev = (uint16)GET_16(f->u.f.updated.time);
    }

    ut.modtime = nw_2_un_time_parts(mdate, mtimev);
    if (ut.modtime == (time_t)-1)
      return(-0x8c);
  }

  if (!is_dir && (change_mask & DM_LAST_ACCESS_DATE)) {
    un_time_2_nw_parts(stb->st_atime, &adate, &atimev);
    adate = (uint16)GET_16(f->u.f.last_access_date);
    ut.actime = nw_2_un_time_parts(adate, atimev);
    if (ut.actime == (time_t)-1)
      return(-0x8c);
  }

  if (utime(unixname, &ut)) {
    if (seteuid(0)) {}
    if (utime(unixname, &ut)) {
      (void)reseteuid();
      return(-0x8c);
    }
    (void)reseteuid();
  }

  return(0);
}

static int set_ncp22_25_archive_info(char *unixname,
                                     NW_SET_DIR_INFO *f,
                                     uint32 change_mask,
                                     int is_dir)
{
  uint16 adate = 0;
  uint16 atime = 0;
  uint32 aid = 0;

  if (!(change_mask & DM_NCP22_25_ARCHIVE_BITS))
    return(0);

  if (is_dir) {
    if (change_mask & DM_ARCHIVE_DATE)
      adate = (uint16)GET_16(f->u.d.archived.date);
    if (change_mask & DM_ARCHIVE_TIME)
      atime = (uint16)GET_16(f->u.d.archived.time);
    if (change_mask & DM_ARCHIVER_ID)
      aid = GET_BE32(f->u.d.archived.id);
  } else {
    if (change_mask & DM_ARCHIVE_DATE)
      adate = (uint16)GET_16(f->u.f.archived.date);
    if (change_mask & DM_ARCHIVE_TIME)
      atime = (uint16)GET_16(f->u.f.archived.time);
    if (change_mask & DM_ARCHIVER_ID)
      aid = GET_BE32(f->u.f.archived.id);
  }

  return(mars_nwe_set_archive_info(unixname,
           !!(change_mask & DM_ARCHIVE_DATE), adate,
           !!(change_mask & DM_ARCHIVE_TIME), atime,
           !!(change_mask & DM_ARCHIVER_ID), aid));
}

static int set_ncp22_25_file_info(char *unixname,
                                  NW_SET_DIR_INFO *f,
                                  uint32 change_mask,
                                  int is_dir)
{
  uint16 cdate = 0;
  uint16 ctime = 0;
  uint32 cid = 0;
  uint32 mid = 0;
  int result;

  if (!(change_mask & (DM_CREATE_DATE | DM_CREATE_TIME | DM_CREATOR_ID |
                       DM_MODIFIER_ID)))
    return(0);

  if (is_dir) {
    if (change_mask & DM_CREATE_DATE)
      cdate = (uint16)GET_16(f->u.d.created.date);
    if (change_mask & DM_CREATE_TIME)
      ctime = (uint16)GET_16(f->u.d.created.time);
    if (change_mask & DM_CREATOR_ID)
      cid = GET_BE32(f->u.d.created.id);
  } else {
    if (change_mask & DM_CREATE_DATE)
      cdate = (uint16)GET_16(f->u.f.created.date);
    if (change_mask & DM_CREATE_TIME)
      ctime = (uint16)GET_16(f->u.f.created.time);
    if (change_mask & DM_CREATOR_ID)
      cid = GET_BE32(f->u.f.created.id);
    if (change_mask & DM_MODIFIER_ID)
      mid = GET_BE32(f->u.f.updated.id);
  }

  result = mars_nwe_set_file_info(unixname,
             !!(change_mask & DM_CREATE_DATE), cdate,
             !!(change_mask & DM_CREATE_TIME), ctime,
             !!(change_mask & DM_CREATOR_ID), cid);
  if (!result && !is_dir)
    result = mars_nwe_set_file_modifier_info(unixname,
               !!(change_mask & DM_MODIFIER_ID), mid);
  return(result);
}


static int set_ncp22_25_maximum_space(int volume, char *unixname,
                                      struct stat *stb,
                                      NW_SET_DIR_INFO *f,
                                      uint32 change_mask,
                                      int is_dir)
{
  uint32 max_space;
  uint32 quota;
  int result;

  if (!(change_mask & DM_MAXIMUM_SPACE))
    return(0);

  /* NetWare exposes maximum space on directory entries.  mars_nwe already
   * has a Linux user-quota backend for volume restrictions; use that as the
   * closest available mapping.  Files have no max_space field in the DOS
   * layout, so ignore the bit if a client sends it for a file.
   */
  if (!is_dir) {
    XDPRINTF((5,0,
      "NCP22/25 maximum space ignored for file %s",
      unixname ? unixname : ""));
    return(0);
  }

  if (tru_eff_rights_exists(volume, unixname, stb, TRUSTEE_M))
    return(-0x8c);

  max_space = GET_BE32(f->u.d.max_space);

  /* Treat the common unlimited sentinels as "remove restriction". */
  if (max_space == MAX_U32 || max_space == 0x40000000UL)
    quota = 0;
  else
    quota = max_space;

  result = nw_set_vol_restrictions((uint8)volume, act_uid, quota);
  if (result) {
    /* If quota support is not compiled/enabled, do not break old clients. */
    XDPRINTF((5,0,
      "NCP22/25 maximum space quota ignored: vol=%d uid=%d max=0x%08lx rc=%d",
      volume, act_uid, (unsigned long)max_space, result));
    if (result == -0xfb)
      return(0);
  } else {
    XDPRINTF((5,0,
      "NCP22/25 maximum space quota set: vol=%d uid=%d max=0x%08lx quota=0x%08lx",
      volume, act_uid, (unsigned long)max_space, (unsigned long)quota));
  }
  return(result);
}

static void log_ncp22_25_change_bits(uint32 change_mask)
{
  uint32 unsupported = change_mask & ~DM_NCP22_25_SUPPORTED_BITS;

  XDPRINTF((5,0,
    "NCP22/25 change bits: change=0x%08lx supported=0x%08lx unsupported=0x%08lx",
    (unsigned long)change_mask,
    (unsigned long)(change_mask & DM_NCP22_25_SUPPORTED_BITS),
    (unsigned long)unsupported));

  if (unsupported) {
    XDPRINTF((5,0,
      "NCP22/25 unsupported change bits ignored: other=0x%08lx",
      (unsigned long)unsupported));
  }
}

static int get_file_attrib(NW_FILE_INFO *f, char *unixname, struct stat *stb,
                           NW_PATH *nwpath)
{
  uint8 spath[14];
  uint32  dwattrib;
  build_dos_attr_name(nwpath->volume, nwpath->fn, unixname, stb,
                      spath, sizeof(spath));
  memset(f->name, 0, sizeof(f->name));
  strncpy((char*)f->name, (char*)spath, sizeof(f->name)-1);

  dwattrib  = get_nw_attrib_dword(nwpath->volume, unixname, stb);
  U16_TO_16(dwattrib, f->attrib);

  {
    uint16 create_date = 0;
    uint8 meta_flags = 0;
    mars_nwe_get_file_info(unixname, &create_date, NULL, NULL, &meta_flags);
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_DATE) {
      U16_TO_BE16(create_date, f->create_date);
    } else {
      un_date_2_nw(stb->st_mtime, f->create_date, 1);
    }
  }
  un_date_2_nw(stb->st_atime, f->acces_date,  1);
  un_date_2_nw(stb->st_mtime, f->modify_date, 1);
  un_time_2_nw(stb->st_mtime, f->modify_time, 1);
  U32_TO_BE32(stb->st_size,   f->size);
  return(1);
}


static int get_dir_attrib(NW_DIR_INFO *d, char *unixname, struct stat *stb,
                         NW_PATH *nwpath)
{
  uint8 spath[14];
  uint32  dwattrib;
  XDPRINTF((5,0, "get_dir_attrib of %s", conn_get_nwpath_name(nwpath)));
  build_dos_attr_name(nwpath->volume, nwpath->fn, unixname, stb,
                      spath, sizeof(spath));
  memset(d->name, 0, sizeof(d->name));
  strncpy((char*)d->name, (char*)spath, sizeof(d->name)-1);

  dwattrib = get_nw_attrib_dword(nwpath->volume, unixname, stb);
  U16_TO_16(dwattrib, d->attrib);

  {
    uint16 create_date = 0;
    uint16 create_time = 0;
    uint32 creator_id = 0;
    uint8 meta_flags = 0;
    mars_nwe_get_file_info(unixname, &create_date, &create_time,
                           &creator_id, &meta_flags);
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_DATE) {
      U16_TO_BE16(create_date, d->create_date);
    } else {
      un_date_2_nw(stb->st_mtime, d->create_date, 1);
    }
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_TIME) {
      U16_TO_BE16(create_time, d->create_time);
    } else {
      un_time_2_nw(stb->st_mtime, d->create_time, 1);
    }
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATOR) {
      U32_TO_BE32(creator_id, d->owner_id);
    } else {
      U32_TO_BE32(get_file_owner(stb), d->owner_id);
    }
  }
  d->access_right_mask = 0;
  d->reserved          = 0;
  U16_TO_BE16(0, d->next_search);
  return(1);
}

static int do_delete_file(NW_PATH *nwpath, FUNC_SEARCH *fs)
{
  char           unname[256];
  xstrcpy(unname, build_unix_name(nwpath, 0));
  XDPRINTF((5,0,"DELETE FILE unname:%s:", unname));
  return(nw_unlink_node(nwpath->volume, unname, &(fs->statb)));
}

int nw_delete_files(int dir_handle, int searchattrib, uint8 *data, int len)
{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);
  if (completition >  -1) {
    completition = func_search_entry(&nwpath, searchattrib, do_delete_file, NULL);
    if (completition < 0) return(completition);
    else if (!completition) return(-0xff);
  }
  return(completition);
}

#if 1
/* new since 22-May-99, 0.99.pl16 */
/* corrected 03-Jun-99, 0.99.pl17 */
typedef struct {
  NW_PATH     destpath;
  struct stat statbuf;
} MV_FILES_STRUCT;

static int do_mv_file(NW_PATH *nwpath, FUNC_SEARCH *fs)
/*  rename one File */
{
  char unsource[256];
  int  result=0;
  MV_FILES_STRUCT *nws=(MV_FILES_STRUCT*)fs->ubuf;
  struct stat statb;
  xstrcpy(unsource, build_unix_name(nwpath, 0));

  if (stat(unsource, &statb) ||
     tru_eff_rights_exists(nwpath->volume, unsource, &statb,
           TRUSTEE_W|TRUSTEE_M|TRUSTEE_R))
          result=-0x8b;
  else if ( !S_ISDIR(fs->statb.st_mode) /* file */ && !(entry8_flags&0x80) &&
            -1 == share_file(fs->statb.st_dev, fs->statb.st_ino, 0x10f, 2) )
      result = -0x8a; /* NO Rename Privileges, file is shared open */
                      /* patch from Przemyslaw Czerpak */
  if (!result) {
    /* sourcefile is ok, now try to move to destfile */
    char undest[256];
    char saved_fn[256];

    uint8 *frompath = nwpath->fn;
    uint8 *topath   = nws->destpath.fn;
    uint8 *otopath  = saved_fn;
    uint8 c;

    /* we must save destpath, because perhaps we must modify it */
    strmaxcpy(saved_fn, nws->destpath.fn, sizeof(saved_fn)-1);

    while ((0 != (c = *otopath++))) {
      switch (c) {
        case '?' :
        case 0xbf:
              if ( *frompath
                && *frompath != '.'
                && *frompath != 0xae )
                *topath++ =  *frompath++;
              break;

        case '.' :
        case 0xae:
              while (*frompath
                && *frompath != '.'
                && *frompath != 0xae )
                frompath++;
              if (*frompath)
                frompath++;
              /* only append '.' if not at end */
              if (*otopath)
                *topath++='.';
              break;

          default:
              if (*frompath && *frompath != '.' && *frompath != 0xae)
                frompath++;
              *topath++=c;
              break;

      } /* switch */
    } /* while */
    *topath='\0';
    xstrcpy(undest, build_unix_name(&nws->destpath, 0));
    /* now restore destpath.fn */
    xstrcpy(nws->destpath.fn, saved_fn);

    if (seteuid(0)) {}
    if (entry8_flags & 0x4)  /* new: 20-Nov-96 */
      result = unx_mvfile_or_dir(unsource, undest);
    else
      result = unx_mvfile(unsource, undest);
   (void)reseteuid();

    switch (result) {
      case   0      : break;                 /* ok             */
      case   EEXIST : result = -0x92; break; /* allready exist */
      case   EXDEV  : result = -0x9a; break; /* cross device   */
      case   EROFS  : result = -0x8b; break; /* no rights      */
      default       : result = -0xff; break; /* unknown error  */
    }
  }

  return(result);
}

int nw_mv_files(int searchattrib,
                int sourcedirhandle, uint8 *sourcedata, int sourcedatalen,
                int destdirhandle,   uint8 *destdata,   int destdatalen)
{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = conn_get_kpl_path(&nwpath, &stbuff, sourcedirhandle, sourcedata, sourcedatalen, 0);
  if (completition >  -1) {
    FUNC_SEARCH  fs;
    MV_FILES_STRUCT mvs;
    completition=conn_get_kpl_path(&mvs.destpath, &mvs.statbuf,
                 destdirhandle, destdata, destdatalen, 0);
    if (completition > -1) {
      char destpath[256];
      completition=0;
      xstrcpy(destpath, build_unix_name(&mvs.destpath, 1));
      if (tru_eff_rights_exists(mvs.destpath.volume, destpath,
              &mvs.statbuf, TRUSTEE_W))
        completition=-0x8b;
      /* now destpath is tested to be writable */
    }
    if (completition > -1) {
      fs.ubuf = (uint8*)&mvs;
      completition = func_search_entry(&nwpath, searchattrib, do_mv_file, &fs);
      if (completition < 0) return(completition);
      else if (!completition) return(-0xff);
      else return(0);
    }
  }
  return(completition);
}


#else /* old version (before 22-May-99, 0.99.pl15 ) */

/* "Resolve" string 'topath' (possible with wildcards '?') using
 *  'frompath' as source for substitution. Make changes directly in
 *  'topath'. Return new length of topath (equal or less than original value)
 *  Routine from: Andrew Sapozhnikov
 */

static int apply_wildcards (uint8 *frompath, int flen, uint8 *topath, int tlen)
{
  int i,tlen2;
  uint8 c,*topath2;

  for (i=flen; i > 0; i--) {
    c=frompath[i-1];
    if (c == ':' || c == '\\' || c == '/') break;
  }
  frompath+=i;
  flen-=i;

  for (i=tlen; i > 0; i--) {
    c=topath[i-1];
    if(c == ':' || c == '\\' || c == '/')    break;
  }
  topath2=(topath+=i);
  tlen2=tlen-i;

  while (tlen2--) {
    switch (c=*topath2++) {
      case '?':
      case 0xbf:
            if (flen && *frompath != '.' && *frompath != 0xae) {
              *topath++ = *frompath++;
              flen--;
            } else tlen--;
            break;
      case '.':
      case 0xae:
            while (flen && *frompath != '.' && *frompath != 0xae) {
              frompath++;
              flen--;
            }
            if (flen) {
              frompath++;
              flen--;
            }
            *topath++=c;
            break;
        default:
            if (flen && *frompath != '.' && *frompath != 0xae) {
              frompath++;
              flen--;
            }
            *topath++=c;
    }
  }
  return tlen;
}

int mv_file(int qdirhandle, uint8 *q, int qlen,
            int zdirhandle, uint8 *z, int zlen)
{
  NW_PATH quellpath;
  struct stat qstbuff;
  NW_PATH zielpath;
  struct stat zstbuff;
  int completition;
  zlen=apply_wildcards(q, qlen, z, zlen);
  completition=conn_get_kpl_path(&quellpath, &qstbuff, qdirhandle, q, qlen, 0);

  if (completition > -1) {
    char qfn[256];
    xstrcpy(qfn, build_unix_name(&quellpath,0));
    completition=conn_get_kpl_path(&zielpath, &zstbuff, zdirhandle, z, zlen, 0);
    if (completition > -1) {
      char zpath[256];
      completition=0;
      xstrcpy(zpath, build_unix_name(&zielpath, 1));
      if (stat(qfn, &qstbuff) ||
        tru_eff_rights_exists(quellpath.volume, qfn, &qstbuff,
           TRUSTEE_W|TRUSTEE_M|TRUSTEE_R))
          completition=-0x8b;
      else if (tru_eff_rights_exists(zielpath.volume, zpath, &zstbuff,
           TRUSTEE_W))
        completition=-0x8b;
    }
    if (!completition){
      char unziel[256];
      xstrcpy(unziel, build_unix_name(&zielpath,0));

      if (seteuid(0)) {}
      if (entry8_flags & 0x4)  /* new: 20-Nov-96 */
        completition = unx_mvfile_or_dir(qfn, unziel);
      else
        completition = unx_mvfile(qfn, unziel);
     (void)reseteuid();

      switch (completition) {
        case   0      : break;
        case   EEXIST : completition = -0x92; break; /* allready exist */
        case   EXDEV  : completition = -0x9a; break; /* cross device   */
        case   EROFS  : completition = -0x8b; break; /* no rights      */
        default       : completition = -0xff;
      }
    }
  }
  return(completition);
}
#endif

static int do_set_file_info(NW_PATH *nwpath, FUNC_SEARCH *fs)
{
  char unname[256];
  int  result=0;
  NW_FILE_INFO *f=(NW_FILE_INFO*)fs->ubuf;
  int voloptions = get_volume_options(nwpath->volume);
  struct stat statb;
  xstrcpy(unname, build_unix_name(nwpath, 0));
  if (!stat(unname, &statb)) {
    if (S_ISFIFO(statb.st_mode) || (voloptions&VOL_OPTION_IS_PIPE))
      return(0); /* do nothing but report OK */
    if (tru_eff_rights_exists(nwpath->volume, unname, &statb, TRUSTEE_M))
      result=-0x8c;  /* no modify rights */
  } else result=-0xff;
  if (!result) {
    struct utimbuf ut;
    ut.actime = ut.modtime = nw_2_un_time(f->modify_date, f->modify_time);
    result=set_nw_attrib_word(nwpath->volume, unname, &statb,
                    (int)GET_16(f->attrib));
    if (!result) {
      if (seteuid(0)) {}
      if (utime(unname, &ut))
        result= (-0x8c); /* no modify rights */
     (void)reseteuid();
    }
  }
  XDPRINTF((5,0,"set_file_info result=0x%x, unname:%s:", unname, -result));
  return(result);
}

static void free_dir_stuff(struct stat *stb, int complete)
{
  NW_DIR *d=&(dirs[0]);
  int j = -1;
  if (complete) {
    while (++j < (int)used_dirs){
      if (  d->dev   == stb->st_dev &&
            d->inode == stb->st_ino)
        d->inode = 0;
      d++;
    }
    j = -1;
  }
  while (++j < (int)anz_dirhandles){
    DIR_HANDLE  *dh=&(dir_handles[j]);
    if (dh && dh->dev == stb->st_dev && dh->inode == stb->st_ino) {
      if (complete)
        free_dir_handle(j+1);
      else if (dh->f) { /* only close directories */
        /* needed to ask for null pointer, hint from Boris Popov, 21-Feb-99 */
        closedir(dh->f);
        dh->f = (DIR*)NULL;
      }
    }
  }
}

int nw_set_file_information(int dir_handle, uint8 *data, int len,
                             int searchattrib, NW_FILE_INFO *f)
{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);
  if (completition >  -1) {
    FUNC_SEARCH  fs;
    fs.ubuf = (uint8*)f;
    completition = func_search_entry(&nwpath, searchattrib,
                 do_set_file_info, &fs);
    if (completition < 0) return(completition);
    else if (!completition) return(-0xff);
  }
  return(completition);
}

int nw_set_file_attributes(int dir_handle, uint8 *data, int len,
                          int attrib, int newattrib)

{
  char          unname[256];
  struct stat   stbuff;
  int           completition=-0x9c;
  NW_PATH       nwpath;
#if PERSISTENT_SYMLINKS
  S_STATB       stb;
#endif
  build_path(&nwpath, data, len, 0);
  if (nwpath.fn[0] != '.') { /* Files with . at the beginning are not ok */
    completition = build_dir_name(&nwpath, &stbuff, dir_handle);
  }
  if (completition < 0) return(completition);
  xstrcpy(unname, build_unix_name(&nwpath, 2));
  XDPRINTF((5,0,"set file attrib 0x%x, unname:%s:", newattrib,  unname));

  if (!s_stat(unname, &stbuff, &stb)){
    int result = set_nw_attrib_byte(nwpath.volume, unname, &stbuff, newattrib);
    return( (result != 0) ? -0x8c : 0);  /* no modify rights */
  }
  return(-0x9c); /* wrong path */
}

static int nw_rmdir(uint8 *unname)
{
  if (rmdir(unname)) {
    if (errno == /*EEXIST*/ ENOTEMPTY) return(-0xa0); /* directory not empty */
    return(-0x8a); /* no privilegs */
  }
  return(0);
}

int nw_unlink_node(int volume, uint8 *unname, struct stat *stb)
{
  int result=-1;
  uint32 attrib=get_nw_attrib_dword(volume, unname, stb);
  /* first we look for attributes */
  if (attrib & (FILE_ATTR_R|FILE_ATTR_DELETE_INH))
     return(-0x8a); /* don't delete 'readonly' */
  if (tru_eff_rights_exists(volume, unname, stb, TRUSTEE_E))
     return(-0x8a); /* no delete rights */

  if (S_ISDIR(stb->st_mode)) { /* directory */
    free_dir_stuff(stb, 0);
    result=nw_rmdir(unname);
    if (result==-0x8a){  /* no privilegs */
      if (seteuid(0)) {}
      result=nw_rmdir(unname);
     (void)reseteuid();
    }
    if (!result)
      free_dir_stuff(stb, 1);
  } else {
    /* changed by: Ingmar Thiemann <ingmar@gefas.com>
     * because share_file() changed
     */
    if (!(entry8_flags&0x10) &&
         -1 == share_file(stb->st_dev, stb->st_ino, 0x10f, 2))
      return(-0x8a); /* NO Delete Privileges, file is open */
    if (0 != (result=unlink(unname))){
      if (seteuid(0)) {}
      result=unlink(unname) ? -0x8a : 0;
     (void)reseteuid();
    }
  }
  if (!result) {
    free_nw_ext_inode(volume, unname, stb->st_dev, stb->st_ino);
  }
  return(result);
}

int nw_creat_node(int volume, uint8 *unname, int mode)
/* creat file or directory, depending on mode */
/* mode & 0x1 == directory     */
/* mode & 0x2 == creat/trunc   */
/* next is only for file creation, needed by some queue functions */
/* mode & 0x8 == ignore rights, try to open as root */
{
  struct stat stb;
  uint8 path[260];
  uint8 *p=path+strlen(unname);
  xstrcpy(path, unname);
  while (p > path && *p != '/') --p;
  if (p > path) {
    *p='\0';
    if (stat(path, &stb)) return(-0x9c);
  } else if (*p=='/') {
    *(p+1)='.';
    *(p+2)='\0';
    if (stat(path, &stb)) return(-0x9c);
  } else
    return(-0x9c);

  if (mode & 1) {  /* directory */
    int result=-0xff;
    if (!tru_eff_rights_exists(volume, path, &stb, TRUSTEE_C)){
      result=mkdir(unname, 0777);
      if (result) {
        if (seteuid(0)) {}
        if (0==(result=mkdir(unname, 0755)))
          if (chown(unname, act_uid, act_gid)) {}
       (void)reseteuid();
      }
      if (result)
        result=-0xff;
    } else result=-0x84; /* no creat rights */
    if (!result) {
      int umode_dir=get_volume_umode_dir(volume);
      if (umode_dir) {
        if (umode_dir == -1)   /* we get parent dir */
          umode_dir=stb.st_mode;
        if (seteuid(0)) {}
        chmod(unname, umode_dir);
       (void)reseteuid();
      }
    }
    return(result);
  } else {  /* file */
    int fd=-1;
    struct stat stbuff;
    int exist;

    if (seteuid(0)) {}
    exist=stat(unname, &stbuff) ? 0 : 1;
   (void)reseteuid();

    if (!(mode&0x8)) { /* we must test for access */
      if (exist) { /* test for write rights */
        if (get_nw_attrib_dword(volume, unname, &stbuff) & FILE_ATTR_R)
          return(-0x94); /* No write rights */
        if (tru_eff_rights_exists(volume, unname, &stbuff, TRUSTEE_W))
          return(-0x94); /* No write rights */
      } else { /* test for creat rights */
        if (tru_eff_rights_exists(volume, path, &stb, TRUSTEE_C))
          return(-0x84); /* No creat rights */
      }
    }

    if (mode & 2 || (exist && (mode&0x8)) ) { /* trunc */
      if (0 > (fd=open(unname, O_CREAT|O_TRUNC|O_RDWR, 0666))) {
        if (seteuid(0)) {}
        if (-1 < (fd=open(unname, O_CREAT|O_TRUNC|O_RDWR, 0600)))
          if (chown(unname, act_uid, act_gid)) {}
       (void)reseteuid();
      }
    } else if (!exist) {
      if (0 > (fd = creat(unname, 0777))) {
        if (seteuid(0)) {}
        if (-1 < (fd = creat(unname, 0751)))
          if (chown(unname, act_uid, act_gid)) {}
       (void)reseteuid();
      }
    }
    if ( fd > -1 ) {
      int umode_file=get_volume_umode_file(volume);
      close(fd);
      if (umode_file > 0)
        chmod(unname, umode_file);
      return(0);
    }
  }
  return(-0xff);
}

int nw_utime_node(int volume, uint8 *unname, struct stat *stb,
                   time_t t)
{
  if (!tru_eff_rights_exists(volume, unname, stb, TRUSTEE_M)){
    struct utimbuf ut;
    ut.actime = ut.modtime = t;
    if (!utime(unname, &ut))
      return(0);
    if (seteuid(0)) {}
    if (!utime(unname, &ut)) {
     (void)reseteuid();
      return(0);
    }
   (void)reseteuid();
  }
  return(-0x8c); /* no modify privileges */
}

int nw_mk_rd_dir(int dir_handle, uint8 *data, int len, int mode)
{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = conn_get_kpl_path(&nwpath, &stbuff,
                    dir_handle, data, len, (mode) ? 0 : 1 );
  if (completition > -1) {
    char unname[256];
    xstrcpy(unname, build_unix_name(&nwpath, 2));
    if (mode) {
      completition=nw_creat_node(nwpath.volume, unname, 1);
    } else { /* rmdir */
      if (!stat(unname, &stbuff))
        completition=nw_unlink_node(nwpath.volume, unname, &stbuff);
      else
       completition=-0x9c;
    }
  }
  return(completition);
}


int mv_dir(int dir_handle, uint8 *sourcedata, int sourcedatalen,
                           uint8 *destdata,   int destdatalen)
{
  NW_PATH quellpath;
  struct stat qstbuff;
  NW_PATH zielpath;
  int completition=conn_get_kpl_path(&quellpath, &qstbuff, dir_handle,
                                     sourcedata, sourcedatalen, 0);
  if (completition > -1){
    char qfn[256];
    char zpath[256];
    struct stat zstbuff;
    completition = 0;
    xstrcpy(qfn, build_unix_name(&quellpath,0));
    memcpy(&zielpath, &quellpath, sizeof(NW_PATH));
    strmaxcpy(zielpath.fn, destdata, destdatalen);

    /* patch from Sven Norinder <snorinder@sgens.ericsson.se> :09-Nov-96 */
    if (get_volume_options(zielpath.volume) & VOL_OPTION_DOWNSHIFT)
      down_fn(zielpath.fn);
    else
      up_fn(zielpath.fn);

#if 0
    /* this is not possible ---- */
    completition=conn_get_kpl_path(&zielpath, &zstbuff, dir_handle, destdata, destdatalen, 0);
    /* ----------- because ----- */
    /* for example the novell rendir.exe does something like this
     * 0x0,0xd,0xf,0x0,0x7,'T','M','P',':','\','I','I',0x2,'K','K'
     * no dirhandle, qpath = fullpath, zpath = only name
     */
#endif

    xstrcpy(zpath, build_unix_name(&zielpath, 1));
    if (stat(qfn, &qstbuff) ||
        tru_eff_rights_exists(quellpath.volume, qfn, &qstbuff,
           TRUSTEE_W|TRUSTEE_M|TRUSTEE_R))
      completition=-0x8b;
    else if (stat(zpath, &zstbuff) ||
       tru_eff_rights_exists(zielpath.volume, zpath, &zstbuff,
           TRUSTEE_W))
      completition=-0x8b;

    if (completition > -1){
      int result;
      char unziel[256];
      xstrcpy(unziel,   build_unix_name(&zielpath,  0));

      if (seteuid(0)) {}
      result = unx_mvdir((uint8 *)qfn, (uint8 *)unziel);
     (void)reseteuid();

      XDPRINTF((4,0, "rendir result=%d, '%s'->'%s'",
                 result, qfn, unziel));
      if (!result)
        completition = 0;
      else {
        if (result == EEXIST)
          completition=-0x92;    /* allready exist */
        else if (result == EXDEV)
          completition=-0x9a;    /* cross devices */
        else completition=-0x9c; /* wrong path */
      }
    }
  }
  return(completition);
}

int nw_mv_dir_between_handles(int sourcedirhandle, uint8 *sourcedata, int sourcedatalen,
                              int zdirhandle, uint8 *destdata, int destdatalen)
/*
 * Rename/move a directory where source and destination are expressed using
 * separate directory handles.  This is needed by NCP 22/46 Rename Or Move
 * (old).  The older mv_dir() helper only supports the classic NCP 22/15
 * style where the destination is just a new name below the source parent.
 */
{
  NW_PATH quellpath;
  NW_PATH zielpath;
  struct stat qstbuff;
  struct stat zstbuff;
  int completition = conn_get_kpl_path(&quellpath, &qstbuff,
                                       sourcedirhandle,
                                       sourcedata, sourcedatalen, 0);

  XDPRINTF((5,0,
    "nw_mv_dir_between_handles: enter src_h=%d dst_h=%d src_len=%d dst_len=%d src=`%.*s` dst=`%.*s` src_res=%d",
    sourcedirhandle, zdirhandle, sourcedatalen, destdatalen,
    sourcedatalen, sourcedata, destdatalen, destdata, completition));

  if (completition > -1) {
    char qfn[256];
    char zparent[256];
    char unziel[256];

    xstrcpy(qfn, build_unix_name(&quellpath, 0));
    XDPRINTF((5,0,
      "nw_mv_dir_between_handles: source nwpath=`%s` unix=`%s` mode=0%o",
      conn_get_nwpath_name(&quellpath), qfn, (unsigned)qstbuff.st_mode));

    if (!S_ISDIR(qstbuff.st_mode)) {
      XDPRINTF((5,0, "nw_mv_dir_between_handles: source is not directory"));
      return(-0x9c);
    }

    completition = conn_get_kpl_path(&zielpath, &zstbuff,
                                     zdirhandle,
                                     destdata, destdatalen, 0);
    XDPRINTF((5,0,
      "nw_mv_dir_between_handles: dest resolve result=%d",
      completition));
    if (completition < 0)
      return(completition);

    if (quellpath.volume != zielpath.volume) {
      XDPRINTF((5,0,
        "nw_mv_dir_between_handles: cross-volume src_vol=%d dst_vol=%d",
        quellpath.volume, zielpath.volume));
      return(-0x9a);       /* cross devices/volumes */
    }

    xstrcpy(zparent, build_unix_name(&zielpath, 1));

    if (stat(qfn, &qstbuff) ||
        tru_eff_rights_exists(quellpath.volume, qfn, &qstbuff,
           TRUSTEE_W|TRUSTEE_M|TRUSTEE_R)) {
      XDPRINTF((5,0,
        "nw_mv_dir_between_handles: source rights/stat failed path=`%s`",
        qfn));
      completition=-0x8b;
    } else if (stat(zparent, &zstbuff) ||
       tru_eff_rights_exists(zielpath.volume, zparent, &zstbuff,
           TRUSTEE_W)) {
      XDPRINTF((5,0,
        "nw_mv_dir_between_handles: dest parent rights/stat failed path=`%s`",
        zparent));
      completition=-0x8b;
    }

    if (completition > -1) {
      int result;
      xstrcpy(unziel, build_unix_name(&zielpath, 0));

      XDPRINTF((5,0,
        "nw_mv_dir_between_handles: moving `%s` -> `%s` parent=`%s`",
        qfn, unziel, zparent));

      if (seteuid(0)) {}
      result = unx_mvdir((uint8 *)qfn, (uint8 *)unziel);
      (void)reseteuid();

      XDPRINTF((4,0, "rendir/movedir result=%d, '%s'->'%s'",
                 result, qfn, unziel));
      if (!result)
        completition = 0;
      else {
        if (result == EEXIST)
          completition=-0x92;    /* already exists */
        else if (result == EXDEV)
          completition=-0x9a;    /* cross devices */
        else completition=-0x9c; /* wrong path */
      }
    }
  }
  XDPRINTF((5,0, "nw_mv_dir_between_handles: return %d", completition));
  return(completition);
}


static int change_dir_entry( NW_DIR *dir,     int volume,
                             uint8 *path,
                             int dev,         ino_t inode,
                             int driveletter, int is_temp,
                             int new_entry,   int task)
{
  if (new_entry || (dir->inode && dir->is_temp != 2)) {
    int len;
    if (*path=='/' && *(path+1) != '\0') path++;
    len=strlen(path);
    if (dir->path) xfree(dir->path);
    dir->path=xmalloc(len+2);
    if (len) {
      memcpy(dir->path, path, len);
      if (dir->path[len-1] != '/') {
        *(dir->path + len) = '/';
        ++len;
      }
    } else {
      *(dir->path) = '/';
      ++len;
    }
    *(dir->path+len)  = '\0';
    dir->dev          = dev;
    dir->inode        = inode;
    dir->volume       = (uint8) volume;
    dir->timestamp    = time(NULL);
    if (driveletter > -1)  {
      dir->drive   = (uint8) driveletter;
      dir->task    = (uint8)task;
    } else {
      if (task     < (int)dir->task) dir->task = (uint8) task;
    }
    if (is_temp     > -1) dir->is_temp = (uint8) is_temp;
    return(0);
  } else {
    if (!dir->inode) return(-0x9b); /* wrong handle */
    else return(-0xfa); /* temp remap Error */
  }
}

void nw_exit_connect(void)
{
  if (connect_is_init) {
    init_file_module(-1);
  }
#if WITH_NAME_SPACE_CALLS
  exit_name_space_module();
#endif
}

int nw_init_connect(void)
/* May be called when ever you want */
{
  uint8    *login   = (uint8*) "LOGIN/";
  NW_PATH  nwlogin;
  FILE *f= open_nw_ini();

  if (f != (FILE*) NULL){
    uint8  buff[256];
    struct stat stbuff;
    int            what;
    int k        = MAX_NW_DIRS;
    NW_DIR *d    = &(dirs[0]);
    int namspace_max_baseh=0;
    int namspace_max_searchh=0;
    xstrcpy(nwlogin.path, (char*)login);
    nwlogin.fn[0]   = '\0';
    nwlogin.volume  = 0;

    while (k--) {
      if (connect_is_init)
        xfree(d->path);
      else
        d->path = NULL;
      d->volume    = 0;
      d->inode     = 0;
      d->is_temp   = 0;
      d->drive     = 0;
      d++;
    }

    init_file_module(-1);
    clear_extracted_base_handles(-1);

    if (connect_is_init) {
      k = 0;
      while (k++ < anz_dirhandles)
        free_dir_handle(k);
    } else
      connect_is_init++;

    while (0 != (what = get_ini_entry(f, 0, buff, sizeof(buff)))) {
      if (what == 6) { /* version */
        if (2 != sscanf((char*)buff, "%d %x",
               &tells_server_version,
               &server_version_flags))
          server_version_flags=0;
      } else if (what == 8) { /* entry8_flags */
        entry8_flags = hextoi((char*)buff);
      } else if (what == 9) { /* umode */
        uint8 buf1[300], buf2[300];
        if (2 == sscanf((char*)buff, "%300s %300s", buf1, buf2)) {
          default_umode_dir  = octtoi(buf1);
          default_umode_file = octtoi(buf2);
        }
      } else if (what == 10) { /* GID */
        default_gid = atoi((char*)buff);
      } else if (what == 11) { /* UID */
        default_uid = atoi((char*)buff);
      } else if (what == 30) { /* Burstmodus */
        int  recvsize, sendsize;
        int i= sscanf((char*)buff, "%i %i", &recvsize, &sendsize);
        if (i>0){
          max_burst_recv_size=recvsize;
          if (i>1)
            max_burst_send_size=sendsize;
        }
      } else if (what == 31) { /* entry31_flags */
        entry31_flags = hextoi((char*)buff);
      } else if (50 == what) {
        init_nwfname(buff);
      } else if (63 == what) {  /* MAX_DIR_BASE */
        namspace_max_baseh=atoi(buff);
      } else if (68 == what) {  /* USE_MMAP */
        use_mmap=atoi(buff);
      } else if (80 == what) {
        namspace_max_searchh=atoi(buff);
      } else if (what == 103) { /* Debug */
        get_debug_level(buff);
      }
    } /* while */
    nw_init_volumes(f);
    fclose(f);
#if WITH_NAME_SPACE_CALLS
    init_name_space_module(namspace_max_baseh, namspace_max_searchh);
#endif
    if (used_nw_volumes < 1) {
      errorp(1, "No Volumes defined. Look at ini file entry 1, Abort !!", NULL);
      return(-1);
    }
    if (get_volume_options(0) & (VOL_OPTION_DOWNSHIFT|VOL_OPTION_IGNCASE))
      down_fn(nwlogin.path);
    if (stat(build_unix_name(&nwlogin, 0), &stbuff)) {
      errorp(1, "Stat error LOGIN Directory, Abort !!",
            "UnixPath=`%s`, nwlogin.path=%s, nwlogin.fn=%s",
               build_unix_name(&nwlogin, 0), nwlogin.path, nwlogin.fn  );
      return(-1);
    }
    (void)change_dir_entry(&(dirs[0]), 0, nwlogin.path,
                       stbuff.st_dev, stbuff.st_ino,
                       0, 0, 1, 0);
    /* first Handle must be known und must not be temp */
    /* and has no Drive-Character                      */
    used_dirs      = 1;
    anz_dirhandles = 0;
    return(0);
  } else return(-1);
}

int nw_free_handles(int task)
/*
 * if task== -1 then all is initialized
 * else the temp handles of the actual task
 * are deleted. I hope this is right. !??
 */
{
  if (task == -1)
    return(nw_init_connect());
  else {
    NW_DIR *d = &(dirs[0]);
    int     k = used_dirs;
    clear_extracted_base_handles(task);
    while (k--) {
      if (d->is_temp && d->task == task) {
      /* only actual task */
        xfree(d->path);
        d->volume    = 0;
        d->inode     = 0;
        d->is_temp   = 0;
        d->drive     = 0;
      }
      d++;
    }
    for (k=0; k < anz_dirhandles; k++){
      DIR_HANDLE *dh=&(dir_handles[k]);
      if (dh->f) {
        closedir(dh->f);
        dh->f=NULL;
      }
    }
    init_file_module(task);
  }
  return(0);
}

int xinsert_new_dir(int volume, uint8 *path, int dev, int inode, int drive, int is_temp, int task)
{
  int j              = 0;
  int    freehandle  = 0;
  int    timedhandle_same_task = 0;
  int    timedhandle_any_task  = 0;
  time_t lowtime_same_task = time(NULL);
  time_t lowtime_any_task  = time(NULL);

  /*
   * Temporary directory handles are supposed to be freed automatically at
   * End Of Job.  Some DOS requesters/tools, notably Novell NCOPY through
   * Client32, can allocate temp handles without sending EOJ often enough for
   * MARS-NWE's small dir-handle table.  The old code only reused empty slots
   * and returned 0x9d (no directory handles) once the table had filled with
   * stale temp handles.  That makes NCOPY fail before the actual copy with
   * "NWGetEffectiveDirectoryRights returned an error."
   *
   * Prefer a genuinely free slot.  If the table is full and this is another
   * temp-handle allocation, recycle the oldest temp handle from the same task;
   * only if none exists, recycle the oldest temp handle from any task.
   * Permanent handles are never recycled here.
   */
  for (j = (used_dirs) ? 1 : 0; j < (int)used_dirs; j++) {
    NW_DIR *d = &(dirs[j]);
    if (!d->inode) {
       freehandle = j+1;
    } else if (is_temp && d->is_temp) {
      if (d->task == (uint8)task && d->timestamp <= lowtime_same_task) {
        timedhandle_same_task = j+1;
        lowtime_same_task     = d->timestamp;
      }
      if (d->timestamp <= lowtime_any_task) {
        timedhandle_any_task = j+1;
        lowtime_any_task     = d->timestamp;
      }
    }
  }
  if (!freehandle && used_dirs < MAX_NW_DIRS) freehandle = ++used_dirs;
  if (!freehandle && is_temp) {
    freehandle = timedhandle_same_task ? timedhandle_same_task : timedhandle_any_task;
    if (freehandle) {
      NW_DIR *d = &(dirs[freehandle-1]);
      XDPRINTF((3,0,"Recycle stale temp dhandle:%d old='%d:%s' oldtask=%d newtask=%d",
              freehandle, d->volume, d->path ? (char*)d->path : "",
              d->task, task));
      if (d->path) {
        xfree(d->path);
        d->path = NULL;
      }
      d->inode = 0;
    }
  }
  if (freehandle){
    (void)change_dir_entry(&(dirs[freehandle-1]),
          volume, path, dev, inode,
          drive, is_temp, 1, task);
    while (used_dirs > freehandle && !dirs[used_dirs-1].inode) used_dirs--;
    return(freehandle);
  } else return(-0x9d);  /* no dir Handles */
}

static int insert_new_dir(NW_PATH *nwpath, int dev, int inode,
                    int drive, int is_temp, int task)
{
 return(xinsert_new_dir(nwpath->volume, nwpath->path,
                       dev, inode, drive, is_temp, task));
}


int nw_search(uint8 *info, uint32 *fileowner,
              int dirhandle, int searchsequence,
              int search_attrib, uint8 *data, int len)

{
   NW_PATH nwpath;
   struct stat stbuff;
   int     completition = conn_get_kpl_path(&nwpath, &stbuff, dirhandle, data, len, 0);
   XDPRINTF((5,0,"nw_search path:%s:, fn:%s:, completition:0x%x",
     nwpath.path, nwpath.fn, completition));
   if (completition > -1) {
      struct stat stbuff;
      completition=get_dir_entry(&nwpath,
                        &searchsequence,
                        search_attrib,
                        &stbuff);
      if (!completition) {
         char unixname[300];
         xstrcpy(unixname, build_unix_name(&nwpath, 0));
         if ( S_ISDIR(stbuff.st_mode) ) {
           get_dir_attrib((NW_DIR_INFO*)info, unixname, &stbuff,
                   &nwpath);
         } else {
           get_file_attrib((NW_FILE_INFO*)info, unixname, &stbuff,
                &nwpath);
         }
         if (fileowner) *fileowner = get_file_owner(&stbuff);
         return(searchsequence);
      }
   }
   return(completition);
}

int nw_dir_get_vol_path(int dirhandle, uint8 *path)
/* returns volume or error ( < 0) */
{
  int result;
  NW_DIR *dir    = (dirhandle > 0 && dirhandle <= used_dirs)
                     ? &(dirs[dirhandle-1])
                     : NULL;
  if (dir && dir->inode) {
    XDPRINTF((3,0,"MAPDEBUG nw_dir_get_vol_path handle=%d vol=%d path=`%s` drive=%d task=%d temp=%d inode=0x%x",
      dirhandle, dir->volume, dir->path ? (char*)dir->path : "",
      dir->drive, dir->task, dir->is_temp, dir->inode));
    int llen       = strlen(dir->path);
    uint8  *p      = path+llen;
    result         = dir->volume;
    memcpy(path, dir->path, llen+1);
    if (llen && *(p-1) == '/')
        *(p-1) = '\0';
    if (result < 0) result = -0x98; /* wrong volume */
  } else result = -0x9b;
  return(result);
}

int nw_dir_search(uint8 *info,
              int dirhandle, int searchsequence,
              int search_attrib, uint8 *data, int len)

{
   NW_PATH nwpath;
   int   completition=-0x9c;
   build_path(&nwpath, data, len, 0);
   if (dirhandle > 0 && --dirhandle < anz_dirhandles){
     DIR_HANDLE *dh = &(dir_handles[dirhandle]);
     char unixname[300];
     struct stat stbuff;
     if (dh->vol_options & VOL_OPTION_DOWNSHIFT) {
       down_fn(nwpath.fn);
     } else {
       up_fn(nwpath.fn);
     }
     nwpath.volume=dh->volume;
     if (get_dh_entry(dh,
                      nwpath.fn,
                      sizeof(nwpath.fn),
                      &searchsequence,
                      search_attrib,
                      unixname,
                      sizeof(unixname),
                      &stbuff)){

       if ( S_ISDIR(stbuff.st_mode) ) {
         get_dir_attrib((NW_DIR_INFO*)info, unixname,  &stbuff,
              &nwpath);
       } else {
         if (dh->vol_options & VOL_OPTION_IS_PIPE)
           stbuff.st_size  = 0x70000000|(stbuff.st_mtime&0xfffffff);
         get_file_attrib((NW_FILE_INFO*)info, unixname, &stbuff,
              &nwpath);
       }
       return(searchsequence);
     } else return(-0xff);     /* not found */
   } else return(completition); /* wrong path */
}

int nw_alloc_dir_handle( int    dir_handle,  /* source directory handle   */
                         uint8  *data,       /* source path               */
                         int    len,         /* pathlen                   */
                         int    driveletter, /* A .. Z normal             */
                         int    is_temphandle, /* temp handle         = 1 */
                                               /* special temp handle = 2 */
                         int    task,          /* process task            */
                         int    *eff_rights)
{
   NW_PATH nwpath;
   struct stat stbuff;
   int inode=conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 1);
   if (inode > -1) {
     uint8   unixname[257];
     xstrcpy(unixname, build_unix_name(&nwpath, 0));
     inode = insert_new_dir(&nwpath, stbuff.st_dev, stbuff.st_ino,
                           driveletter, is_temphandle, task);
     *eff_rights=tru_get_eff_rights(nwpath.volume, unixname, &stbuff);
   }
   XDPRINTF((4,0,"Allocate %shandle:%s, Qhandle=%d, drive=%d, Task=%d, result=0x%x",
       (is_temphandle) ? "Temp" : "Perm", conn_get_nwpath_name(&nwpath),
               dir_handle, driveletter, task, inode));

   return(inode);
}

int nw_open_dir_handle( int        dir_handle,
                        uint8      *data,     /* extra path  */
                        int        len,       /* len of DATA */

                        int        *volume,   /* Volume      */
                        int        *dir_id,
                        int        *searchsequence)

/*
 * Routine returns handle to use in searchroutines.
 * RETURN=errcode ( <0 ) or eff. ACCES Rights
*/

{
   NW_PATH nwpath;
   struct stat stbuff;
   int completition = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 1);
   if (completition > -1) {
     XDPRINTF((5,0,"NW_OPEN_DIR: completition = 0x%x; nwpath= %s",
           (int)completition, conn_get_nwpath_name(&nwpath) ));

     completition = new_dir_handle(&stbuff, &nwpath);
     if (completition > -1) {
       struct stat stb;
       DIR_HANDLE *dh  = &(dir_handles[completition-1]);
       s_stat(dh->unixname, &stb, NULL);
       *volume         = dh->volume;
       *dir_id         = completition;
       *searchsequence = MAX_U16;
       completition    = tru_get_eff_rights(dh->volume, dh->unixname, &stb);
     }
     XDPRINTF((5,0,"NW_OPEN_DIR_2: completition = 0x%x",
                    completition));
   } else {
     XDPRINTF((4,0,"NW_OPEN_DIR failed: completition = -0x%x", -completition));
   }
   return(completition);
}

int nw_free_dir_handle(int dir_handle, int task)
{
  if (dir_handle && --dir_handle < (int)used_dirs) {
    NW_DIR *d=&(dirs[dir_handle]);
    XDPRINTF((4,0,"free dhandle:%d, task=%d, d->inode=0x%x, used_dirs=%d",
            dir_handle+1, task, d->inode, used_dirs));
    if (!d->inode
#if 0
    || d->task != task
#endif
    ) return(-0x9b); /* wrong handle */
    else {
      d->inode = 0;
      xfree(d->path);
    }
    return(0);
  } else return(-0x9b); /* wrong handle */
}

int alter_dir_handle(int targetdir, int volume, uint8 *path,
                     int dev, int inode, int task)
/* targetdir will be changed */
{
  if (targetdir > 0 && --targetdir < used_dirs
    && dirs[targetdir].is_temp != 2) {
    /* do not change special temphandles */
    XDPRINTF((3,0,"MAPDEBUG Change dhandle:%d(%d) -> '%d:%s' dev=0x%x inode=0x%x old='%d:%s' olddrive=%d oldtask=%d",
              targetdir+1, task, volume, path, dev, inode,
              dirs[targetdir].volume, dirs[targetdir].path ? (char*)dirs[targetdir].path : "",
              dirs[targetdir].drive, dirs[targetdir].task));
    XDPRINTF((5,0,"Change dhandle:%d(%d) -> '%d:%s'", targetdir+1, task,
              volume, path));
    return(change_dir_entry(&dirs[targetdir],
                    volume, path, dev, inode,
                    -1, -1, 0, task));
    /* here the existing handle is only modified */
  } else return(-0x9b); /* BAD DIR Handle */
}


int nw_set_dir_handle(int targetdir, int dir_handle,
                          uint8 *data, int len, int task)
/* targetdirs gets path of dirhandle + data */
{
  NW_PATH nwpath;
  struct stat stbuff;
  int inode = conn_get_kpl_path(&nwpath, &stbuff,  dir_handle, data, len, 1);
  XDPRINTF((3,0,"MAPDEBUG nw_set_dir_handle target=%d source=%d len=%d task=%d resolved=%s inode=0x%x",
    targetdir, dir_handle, len, task, conn_get_nwpath_name(&nwpath), inode));
  if (inode > -1)
    inode = alter_dir_handle(targetdir, nwpath.volume, nwpath.path,
                              stbuff.st_dev, stbuff.st_ino, task);
  XDPRINTF((3,0,"MAPDEBUG nw_set_dir_handle result=0x%x target=%d", inode, targetdir));
  return(inode);  /* invalid PATH */
}

int conn_map_path_to_dir_entry(int dirhandle, uint8 *data, int len,
                               uint8 *volume_out, uint8 *dirnum_out)
/*
 * NCP23/F4 Convert Path to Dir Entry helper.
 * Returns 0 or a negative mars_nwe/NCP error.
 * Reply fields are volume byte and DOS namespace directory number (LO-HI).
 */
{
  char unixname[512];
  struct stat stbuff;
  int volume = conn_get_kpl_unxname(unixname, sizeof(unixname),
                                    dirhandle, data, len);

  if (volume < 0) return(volume);
  if (s_stat(unixname, &stbuff, NULL)) return(-0x9c);

  /*
   * NCP23/F4 maps a path to a namespace entry number.  Despite the
   * historical "Dir Entry" wording, clients may use the returned number
   * for file entries as well as directory entries, and NCP23/F3 maps that
   * number back to a component path.  Therefore do not reject regular
   * files here; any stat'able volume entry that can be represented by the
   * namespace handle table is valid.
   */

  {
    DEV_NAMESPACE_MAP dnm;
    uint32 dirnum;

    dnm.dev       = stbuff.st_dev;
    dnm.namespace = NAME_DOS;
    dirnum = nw_vol_inode_to_handle(volume, stbuff.st_ino, &dnm);
    if (!dirnum) return(-0x9b);

    if (volume_out) *volume_out = (uint8)volume;
    if (dirnum_out) U32_TO_32(dirnum, dirnum_out);

    XDPRINTF((5,0,
      "NCP23/F4 helper: dh=%d pathlen=%d unix=`%s` vol=%d dirnum=0x%lx",
      dirhandle, len, unixname, volume, (unsigned long)dirnum));
  }

  return(0);
}


int nw_get_directory_path(int dir_handle, uint8 *name, int size_name)
{
  int     result   = -0x9b;
  name[0] = '\0';
  if (dir_handle > 0 && --dir_handle < (int)used_dirs
                     && dirs[dir_handle].inode) {
    int volume = dirs[dir_handle].volume;
    if (volume > -1 && volume < used_nw_volumes){
      result = slprintf((char*)name, size_name-1, "%s:%s", nw_volumes[volume].sysname, dirs[dir_handle].path);
      if (result > 0) {
        if (name[result-1] == '/') name[--result] = '\0';
        up_fn(name);
      } else result= -0x9c; /* wrong path */
    } else result = -0x98;
  }
  XDPRINTF((3,0,"MAPDEBUG nw_get_directory_path:%s: Handle=%d, result=0x%x", name, dir_handle+1, result));
  XDPRINTF((5,0,"nw_get_directory_path:%s: Handle=%d, result=0x%x", name, dir_handle+1, result));
  return(result);
}


int nw_extract_base_handle(int dir_handle, uint8 *handle_id)
/* Extract a Base Handle: stores an opaque handle id for Restore Directory Handle */
{
  int result = -0x9b;
  if (dir_handle > 0 && --dir_handle < (int)used_dirs) {
    NW_DIR *d = &(dirs[dir_handle]);
    if (d->inode && !d->is_temp && d->volume < used_nw_volumes) {
      int j;
      EXTRACTED_BASE_HANDLE *free_ebh = NULL;
      for (j = 0; j < MAX_EXTRACTED_BASE_HANDLES; j++) {
        EXTRACTED_BASE_HANDLE *ebh = &(extracted_base_handles[j]);
        if (!ebh->id && !free_ebh) free_ebh = ebh;
        if (ebh->id && ebh->volume == d->volume && ebh->path
            && !strcmp((char*)ebh->path, (char*)d->path)) {
          free_ebh = ebh;
          break;
        }
      }
      if (free_ebh) {
        if (!free_ebh->id) {
          free_ebh->id = next_extracted_base_handle++;
          if (!next_extracted_base_handle) next_extracted_base_handle = 1;
        }
        xfree(free_ebh->path);
        free_ebh->path   = xmalloc(strlen((char*)d->path)+1);
        strcpy((char*)free_ebh->path, (char*)d->path);
        free_ebh->volume = d->volume;
        free_ebh->dev    = d->dev;
        free_ebh->inode  = d->inode;
        free_ebh->task   = d->task;
        U32_TO_BE32(free_ebh->id, handle_id);
        result = 0;
      } else result = -0x96; /* server out of memory/table space */
    }
  }
  XDPRINTF((4,0,"Extract base handle from dhandle=%d result=0x%x", dir_handle+1, result));
  return(result);
}

int nw_restore_base_handle(uint8 *handle_id, int task, int *eff_rights)
/* Restore an Extracted Base Handle: allocate a new permanent handle from a saved id */
{
  uint32 id = GET_BE32(handle_id);
  int j;
  int result = -0x9b;
  for (j = 0; j < MAX_EXTRACTED_BASE_HANDLES; j++) {
    EXTRACTED_BASE_HANDLE *ebh = &(extracted_base_handles[j]);
    if (ebh->id == id && ebh->path && ebh->volume >= 0 && ebh->volume < used_nw_volumes) {
      NW_PATH nwpath;
      struct stat stbuff;
      uint8 unixname[257];
      nwpath.volume = ebh->volume;
      nwpath.fn[0] = '\0';
      strmaxcpy(nwpath.path, (char*)ebh->path, sizeof(nwpath.path)-1);
      xstrcpy(unixname, build_unix_name(&nwpath, 0));
      if (stat((char*)unixname, &stbuff)) {
        result = -0x9c; /* invalid path */
      } else if (!S_ISDIR(stbuff.st_mode)) {
        result = -0x9c;
      } else {
        result = xinsert_new_dir(nwpath.volume, nwpath.path,
                   stbuff.st_dev, stbuff.st_ino, 0, 0, task);
        if (result > 0 && eff_rights) {
          *eff_rights = tru_get_eff_rights(nwpath.volume, unixname, &stbuff);
        }
      }
      break;
    }
  }
  XDPRINTF((4,0,"Restore base handle id=0x%x task=%d result=0x%x", id, task, result));
  return(result);
}

int nw_get_vol_number(int dir_handle)
/* Get Volume Nummmer with Handle */
{
  int     result   = -0x9b; /* wrong handle */
  if (dir_handle > 0 && --dir_handle < (int)used_dirs
          && dirs[dir_handle].inode  )  {
    result = dirs[dir_handle].volume;
    if (result < 0 || result >= used_nw_volumes) result = -0x98; /* wrong volume */
  }
  XDPRINTF((5,0,"nw_get_vol_number:0x%x: von Handle=%d", result, dir_handle+1));
  return(result);
}

int nw_get_eff_dir_rights(int dir_handle, uint8 *data, int len, int modus)
/* modus 0=only_dir, 1=dirs and files */
{
  char          unname[256];
  struct stat   stbuff;
  NW_PATH       nwpath;
  int completition = conn_get_kpl_path(&nwpath, &stbuff,
                               dir_handle, data, len, 0);
  if (completition < 0) return(completition);
  xstrcpy(unname, build_unix_name(&nwpath, 0));
  if (s_stat(unname, &stbuff, NULL) ||
    (!modus && !S_ISDIR(stbuff.st_mode)) ) {
    completition = -0x9c;
  } else
    completition=tru_get_eff_rights(nwpath.volume, unname, &stbuff);
  return(completition);
}

int nw_creat_open_file(int dir_handle, uint8 *data, int len,
            NW_FILE_INFO *info, int attrib, int access,
            int creatmode, int task)
/*
 * creatmode: 0 = open | 1 = creat | 2 = creatnew  & 4 == save handle
 * attrib ??
 * access: 0x1=read, 0x2=write
 */
{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);
  if (completition > -1) {
     char unixname[300];
     xstrcpy(unixname, build_unix_name(&nwpath, 0));
     completition=file_creat_open(nwpath.volume, (uint8*)unixname,
                      &stbuff, attrib, access, creatmode, task);

     if (completition > -1)
       get_file_attrib(info, unixname, &stbuff, &nwpath);
  }
  return(completition);
}

static int s_nw_scan_dir_info(int dir_handle,
                       uint8 *data, int len, uint8 *subnr,
                       uint8 *subname, uint8 *subdatetime,
                       uint8 *owner, uint8 *wild)
{
  int volume;
  int searchsequence;
  int dir_id;
  int rights = nw_open_dir_handle(dir_handle, data, len,
                           &volume, &dir_id, &searchsequence);

  if (rights > -1) {
    DIR_HANDLE *dh = &(dir_handles[dir_id-1]);
    struct stat stbuff;
    uint16 dirsequence     = GET_BE16(subnr);
    uint16 aktsequence     = 0;
    uint8  dirname[256];
    if (!dirsequence) dirsequence++;

    if (dh->vol_options & VOL_OPTION_DOWNSHIFT) {
      down_fn(wild);
    } else {
      up_fn(wild);
    }

    xstrcpy(dirname, (char*)wild);
    XDPRINTF((5,0,"SCAN_DIR: rights = 0x%x, subnr = %d",
                (int)rights, (int)GET_BE16(subnr)));

    if (*dirname) {
      char unixname[300];
      while ( get_dh_entry( dh,
                            dirname,
                            sizeof(dirname),
                            &searchsequence,
                            0x10,
                            unixname,
                            sizeof(unixname),
                            &stbuff) ) {

        XDPRINTF((5,0,"SCAN_DIR: von %s, found %s:", dh->unixname, dirname));
        if (++aktsequence == dirsequence) { /* actual found */
          uint8 dosalias[14];
          char parent[300];
          char *slash;
          uint8 *leaf = dirname;

          U16_TO_BE16(aktsequence, subnr);

          /*
           * Do not trust dirname here.  Some old DOS scan paths carry the
           * raw 8 byte directory name only, which collapses colliding long
           * directories to the same display name (LONG_DIR/LONG_DIR).
           * Rebuild the final directory info name from the real full unixname
           * returned by get_dh_entry(), where the long on-disk leaf is still
           * available.
           */
          if (unixname[0]) {
            strmaxcpy(parent, unixname, sizeof(parent)-1);
            slash = strrchr(parent, '/');
            if (slash) {
              leaf = (uint8*)(slash+1);
              *slash = '\0';
              if (!*parent) strcpy(parent, "/");
              build_dos_83_alias(dh->vol_options, (uint8*)parent,
                                 leaf, stbuff.st_ino,
                                 dosalias, sizeof(dosalias));
            } else {
              build_dos_83_alias(dh->vol_options, NULL,
                                 leaf, stbuff.st_ino,
                                 dosalias, sizeof(dosalias));
            }
          } else {
            build_dos_83_alias(dh->vol_options, (uint8*)dh->unixname,
                               dirname, stbuff.st_ino,
                               dosalias, sizeof(dosalias));
          }

          up_fn(dosalias);
          strncpy((char*)subname, (char*)dosalias, 16);
          U32_TO_BE32(get_file_owner(&stbuff),  owner);
          un_date_2_nw(stbuff.st_mtime, subdatetime,   1);
          un_time_2_nw(stbuff.st_mtime, subdatetime+2, 1);
          return(tru_get_inherited_mask(volume, unixname, &stbuff));
        }
        xstrcpy(dirname, (char*)wild);
      } /* while */
    } else {
      *(dh->kpath)   = '.';
      *(dh->kpath+1) = '\0';
      if (!s_stat(dh->unixname, &stbuff, NULL)) {
        U16_TO_BE16(1, subnr);
        memset(subname, 0, 16);
        U32_TO_BE32(get_file_owner(&stbuff),  owner);
        un_date_2_nw(stbuff.st_mtime, subdatetime,   1);
        un_time_2_nw(stbuff.st_mtime, subdatetime+2, 1);
        return(tru_get_inherited_mask(volume, dh->unixname, &stbuff));
      }
    }
    /* return(-0x9c);  NO MORE INFO */
    return(-0xff);
  }
  return(rights);
}

int nw_scan_dir_info(int dir_handle, uint8 *data, int len, uint8 *subnr,
                       uint8 *subname, uint8 *subdatetime, uint8 *owner)
{
  int   k     = len;
  uint8 *p    = data+len;
  uint8 dirname[256];
  while (k) {
    uint8 c = *--p;
    if (c == '/' || c == '\\' || c == ':') {
      p++;
      break;
    }
    --k;
  }
  if (len && k < len) {
    strmaxcpy(dirname, p, len-k);
    len = k;
  } else dirname[0] = '\0';
  XDPRINTF((7, 0, "nw_scan_dir_info, dirname=`%s`, len=%d, k=%d",
    dirname, len , k));
  return(s_nw_scan_dir_info(dir_handle, data, len, subnr,
                     subname, subdatetime, owner, dirname));
}




static void build_dos_attr_name(int volume, uint8 *path, char *unixname,
                                struct stat *stb, uint8 *out, int out_len)
{
  char parent[300];
  char *slash;
  uint8 *leaf = path;
  int options = get_volume_options(volume);

  if (!out || out_len < 2) return;
  *out = '\0';

  if (stb && volume == last_dos_alias_volume
      && stb->st_ino == last_dos_alias_inode
      && last_dos_alias_parent[0] && last_dos_alias_real[0]) {
    if (last_dos_alias_alias[0]) {
      strmaxcpy(out, last_dos_alias_alias, out_len-1);
      up_fn(out);
    } else {
      build_dos_83_alias(options, last_dos_alias_parent,
                         last_dos_alias_real, stb->st_ino, out, out_len);
    }
    return;
  }

  /*
   * Prefer the real unixname when it is available.  Some namespace/DOS scan
   * paths pass an already synthesized 8.3 name in `path`; if we return that
   * blindly, colliding long directories can both be displayed as LONG_DIR.
   */
  if (unixname && *unixname) {
    strmaxcpy(parent, unixname, sizeof(parent)-1);
    slash = strrchr(parent, '/');
    if (slash) {
      *slash = '\0';
      if (*(slash+1)) leaf = (uint8*)(slash+1);
      if (!*parent) strcpy(parent, "/");
      build_dos_83_alias(options, (uint8*)parent,
                         leaf, stb ? stb->st_ino : 0, out, out_len);
      return;
    }
  }

  /* Fallback: keep a previously synthesized DOS alias only when there is no
   * reliable real unixname to rebuild from. */
  if (path && *path && dos_is_83_name(path, options)) {
    strmaxcpy(out, path, out_len-1);
    up_fn(out);
    return;
  }

  build_dos_83_alias(options, NULL,
                     leaf, stb ? stb->st_ino : 0, out, out_len);
}

void get_dos_file_attrib(NW_DOS_FILE_INFO *f,
                               struct stat *stb,
                               int         volume,
                               uint8        *path,
                               char         *unixname)
{
  uint8  spath[14];
  uint32 nw_owner=get_file_owner(stb);
  build_dos_attr_name(volume, path, unixname, stb, spath, sizeof(spath));
  XDPRINTF((99,0,"DOSFILEATTR path='%s' unix='%s' alias='%s' inode=%ld", path ? (char*)path : "", unixname ? unixname : "", spath, stb ? (long)stb->st_ino : 0L));
  f->namlen=min(strlen((char*)spath), 12);
  strncpy((char*)f->name, (char*)spath, f->namlen);
  U32_TO_32(get_nw_attrib_dword(volume, unixname, stb), f->attributes);
  U16_TO_16(tru_get_inherited_mask(volume, unixname, stb),
        f->inherited_rights_mask);
  {
    uint16 create_date = 0;
    uint16 create_time = 0;
    uint32 creator_id = 0;
    uint8 meta_flags = 0;
    mars_nwe_get_file_info(unixname, &create_date, &create_time,
                           &creator_id, &meta_flags);
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_DATE) {
      U16_TO_16(create_date, f->created.date);
    } else {
      un_date_2_nw(stb->st_mtime, f->created.date, 0);
    }
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_TIME) {
      U16_TO_16(create_time, f->created.time);
    } else {
      un_time_2_nw(stb->st_mtime, f->created.time, 0);
    }
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATOR) {
      U32_TO_BE32(creator_id, f->created.id);
    } else {
      U32_TO_BE32(nw_owner, f->created.id);
    }
  }

  {
    uint16 archive_date = 0;
    uint16 archive_time = 0;
    uint32 archiver_id = 0;
    mars_nwe_get_archive_info(unixname, &archive_date, &archive_time,
                              &archiver_id, NULL);
    U16_TO_16(archive_date, f->archived.date);
    U16_TO_16(archive_time, f->archived.time);
    U32_TO_BE32(archiver_id, f->archived.id);
  }

  un_date_2_nw(stb->st_mtime, f->updated.date, 0);
  un_time_2_nw(stb->st_mtime, f->updated.time, 0);
  {
    uint32 modifier_id = 0;
    uint8 mod_flags = 0;
    mars_nwe_get_file_modifier_info(unixname, &modifier_id, &mod_flags);
    if (mod_flags & MARS_NWE_FILEINFO_HAS_MODIFIER) {
      U32_TO_BE32(modifier_id, f->updated.id);
    } else {
      U32_TO_BE32(nw_owner, f->updated.id);
    }
  }

  un_date_2_nw(stb->st_atime, f->last_access_date, 0);
  U32_TO_32(stb->st_size, f->size);
}


void get_dos_dir_attrib(NW_DOS_DIR_INFO *f,
                                struct stat *stb,
                                int   volume,
                                uint8 *path,
                                char  *unixname)
{
  uint8 spath[14];
  build_dos_attr_name(volume, path, unixname, stb, spath, sizeof(spath));
  XDPRINTF((99,0,"DOSDIRATTR path='%s' unix='%s' alias='%s' inode=%ld", path ? (char*)path : "", unixname ? unixname : "", spath, stb ? (long)stb->st_ino : 0L));
  f->namlen=min(strlen((char*)spath), 12);
  strncpy((char*)f->name, (char*)spath, f->namlen);
  U32_TO_32(get_nw_attrib_dword(volume, unixname, stb),
             f->attributes);
  U16_TO_16(tru_get_inherited_mask(volume, unixname, stb),
        f->inherited_rights_mask);
  {
    uint16 create_date = 0;
    uint16 create_time = 0;
    uint32 creator_id = 0;
    uint8 meta_flags = 0;
    mars_nwe_get_file_info(unixname, &create_date, &create_time,
                           &creator_id, &meta_flags);
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_DATE) {
      U16_TO_16(create_date, f->created.date);
    } else {
      un_date_2_nw(stb->st_mtime, f->created.date, 0);
    }
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATE_TIME) {
      U16_TO_16(create_time, f->created.time);
    } else {
      un_time_2_nw(stb->st_mtime, f->created.time, 0);
    }
    if (meta_flags & MARS_NWE_FILEINFO_HAS_CREATOR) {
      U32_TO_BE32(creator_id, f->created.id);
    } else {
      U32_TO_BE32(get_file_owner(stb), f->created.id);
    }
  }
  {
    uint16 archive_date = 0;
    uint16 archive_time = 0;
    uint32 archiver_id = 0;
    mars_nwe_get_archive_info(unixname, &archive_date, &archive_time,
                              &archiver_id, NULL);
    U16_TO_16(archive_date, f->archived.date);
    U16_TO_16(archive_time, f->archived.time);
    U32_TO_BE32(archiver_id, f->archived.id);
  }
  un_date_2_nw(stb->st_mtime, f->modify_date, 0);
  un_time_2_nw(stb->st_mtime, f->modify_time, 0);
  {
    uint32 quota = MAX_U32;
    uint32 inuse = 0;
    if (!nw_get_vol_restrictions((uint8)volume, act_uid, &quota, &inuse)) {
      U32_TO_BE32(quota, f->max_space);
    } else {
      U32_TO_BE32(MAX_U32, f->max_space);
    }
  }
}


int nw_scan_a_directory(uint8   *rdata,
                        int     dirhandle,
                        uint8   *data,
                        int     len,
                        int     searchattrib,
                        uint32  searchbeg)   /* 32 bit */
{
  NW_PATH nwpath;
  struct stat stbuff;
  int     completition = conn_get_kpl_path(&nwpath, &stbuff, dirhandle, data, len, 0);
  XDPRINTF((5,0,"nw_scan_a_directory path:%s:, fn:%s:, completition:0x%x",
    nwpath.path, nwpath.fn, completition));
  if (completition > -1) {
     int searchsequence = (searchbeg == MAX_U32) ? MAX_U16 : searchbeg;
     completition=get_dir_entry(&nwpath,
                       &searchsequence,
                       searchattrib,
                       &stbuff);

     if (!completition) {
       char unixname[300];
       NW_SCAN_DIR_INFO *scif = (NW_SCAN_DIR_INFO*)rdata;
       xstrcpy(unixname, build_unix_name(&nwpath, 0));
       memset(rdata, 0, sizeof(NW_SCAN_DIR_INFO));
       U32_TO_BE32((uint32)searchsequence, scif->searchsequence);

       XDPRINTF((5,0, "nw_scan_a_directory = %s, uid=%d, gid=%d",
         conn_get_nwpath_name(&nwpath),
         stbuff.st_uid, stbuff.st_gid));

       if ( S_ISDIR(stbuff.st_mode)) {
          get_dos_dir_attrib(&(scif->u.d), &stbuff,
                             nwpath.volume, nwpath.fn, unixname);
       } else {
          get_dos_file_attrib(&(scif->u.f), &stbuff,
                             nwpath.volume,  nwpath.fn, unixname);
       }
       return(sizeof(NW_SCAN_DIR_INFO));
     }
  }
  return(completition); /* wrong path */
}

int nw_scan_a_root_dir(uint8   *rdata,
                       int     dirhandle)
{
  NW_PATH nwpath;
  struct stat stbuff;
  uint8   data[2];
  int     completition = conn_get_kpl_path(&nwpath, &stbuff, dirhandle, data, 0, 1);
  XDPRINTF((5,0,"nw_scan_a_root_directory_2 path:%s:, fn:%s:, completition:0x%x",
    nwpath.path, nwpath.fn, completition));
  if (completition > -1) {
    char unixname[300];
    xstrcpy(unixname, build_unix_name(&nwpath, 2));
    if (!s_stat(unixname, &stbuff, NULL)) {
      NW_DOS_DIR_INFO  *d=(NW_DOS_DIR_INFO*)rdata;
      memset(rdata, 0, sizeof(NW_DOS_DIR_INFO));
      get_dos_dir_attrib(d, &stbuff, nwpath.volume, nwpath.fn, unixname);
      return(sizeof(NW_DOS_DIR_INFO));
    } else return(-0xff); /* not found */
  } else return(completition); /* wrong path */
}

int nw_set_a_directory_entry(int     dirhandle,
                             uint8   *data,
                             int     len,
                             int     searchattrib,
                             uint32  searchbeg,
                             NW_SET_DIR_INFO *f)
{
  NW_PATH nwpath;
  struct stat stbuff;
  int     completition = conn_get_kpl_path(&nwpath, &stbuff, dirhandle, data, len, 0);
  XDPRINTF((5,0,"nw_set_a_directory_entry path:%s:, fn:%s:, completition:0x%x",
    nwpath.path, nwpath.fn, completition));
  if (completition > -1) {
     int searchsequence = MAX_U16;
     /* (searchbeg == MAX_U32) ? MAX_U16 : searchbeg; */
     completition=get_dir_entry(&nwpath,
                       &searchsequence,
                       searchattrib,
                       &stbuff);
     if (!completition) {
       char unixname[300];
       uint32 change_mask=GET_32(f->change_bits);
       xstrcpy(unixname,build_unix_name(&nwpath, 0));
       log_ncp22_25_change_bits(change_mask);
       if (change_mask & DM_ATTRIBUTES) {
         completition=set_nw_attrib_dword(nwpath.volume, unixname, &stbuff,
            GET_32(f->u.f.attributes));
         if (!completition)
           s_stat(unixname, &stbuff, NULL);
       }
       if (!completition) {
         int result=set_ncp22_25_times(nwpath.volume, unixname, &stbuff,
                         f, change_mask, S_ISDIR(stbuff.st_mode));
         if (result)
           completition=result;
         else if (change_mask & DM_NCP22_25_TIME_BITS)
           s_stat(unixname, &stbuff, NULL);
       }
       if (!completition) {
         completition=set_ncp22_25_archive_info(unixname, f, change_mask,
                         S_ISDIR(stbuff.st_mode));
       }
       if (!completition) {
         completition=set_ncp22_25_file_info(unixname, f, change_mask,
                         S_ISDIR(stbuff.st_mode));
       }
       if (!completition) {
         completition=set_ncp22_25_maximum_space(nwpath.volume, unixname,
                         &stbuff, f, change_mask, S_ISDIR(stbuff.st_mode));
       }
       if (S_ISDIR(stbuff.st_mode)) {
         if (change_mask & DM_INHERITED_RIGHTS_MASK) {
           int result=tru_set_inherited_mask(nwpath.volume, unixname,
                         &stbuff, GET_16(f->u.d.inherited_rights_mask));
           if (result)
             completition=result;
         }
       } else {
         if (change_mask & DM_INHERITED_RIGHTS_MASK) {
           int result=tru_set_inherited_mask(nwpath.volume, unixname,
                         &stbuff, GET_16(f->u.f.inherited_rights_mask));
           if (result)
             completition=result;
         }
       }
     }
  }
  return(completition);
}

static int my_match(uint8 *s, uint8 *p)
{
  int len=0;
  int dot=0;
  for (; *s && *p; s++,p++) {
    if (len == 12) return(0);
    if (*s == '.') {
      if (dot) return(0);
      dot++;
    } else if (dot) {
      if (dot++ > 3) return(0);
    } else if (len == 8) return(0);
    if (!ufn_imatch(*s, *p))
      return(0);
    ++len;
  }
  return( ((!*s) && (*p=='/' || *p == '\0')) ? len : 0);
}

static int get_match(uint8 *unixname, uint8 *p)
{
  DIR       *d;
  uint8     *pp;
#if 0
  uint8     *p1;
  int       inode=0;
#endif
  if (!p || !*p)   return(1);
  *p        = '\0';
  pp=p+1;
  while (*pp == '/') ++pp;
#if 0
  p1=strchr(pp, '/');
  if (!p1) p1=pp+strlen(pp);
  if (p1 > pp+4 && *(p1-4) == '.'
                && *(p1-3) == '_'
                && *(p1-2) == '_'
                && *(p1-1) == '_' ) {
    *(p1-4) = '\0';
    inode=atoi(pp);
    *(p1-4) = '.';
  }
#endif

  if (seteuid(0)) {}
  d=opendir(unixname);
 (void)reseteuid();

  if (NULL != d) {
    struct dirent *dirbuff;
    XDPRINTF((10, 0, "opendir OK unixname='%s' pp='%s'", unixname, pp));
    *p      = '/';
    while ((dirbuff = readdir(d)) != (struct dirent*)NULL){
      int len;
      if (dirbuff->d_ino) {
        XDPRINTF((10, 0, "get match found d_name='%s'", dirbuff->d_name));
        if (
#if 0
        (dirbuff->d_ino ==inode) ||
#endif
        0 != (len=my_match(dirbuff->d_name, pp))) {
          memcpy(pp, dirbuff->d_name, len);
          XDPRINTF((10, 0, "get match, match OK"));
          closedir(d);
          return(get_match(unixname, pp+len));
        }
      }
    }
    closedir(d);
  } else {
    XDPRINTF((3, 0, "DOS get_match opendir failed unixname='%s'", unixname));
    *p='/';
  }
  return(0);
}

/* DOS name mangling is provided by namedos.c. */


int nw_add_trustee(int dir_handle, uint8 *data, int len,
                   uint32 id,  int trustee, int extended)
/* extended 0=add trustee to dir, 1= add ext trustee to dirs and files */
{
  char          unname[256];
  struct stat   stbuff;
  NW_PATH       nwpath;
  int result = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);
  if (result < 0) return(result);
  xstrcpy(unname, build_unix_name(&nwpath, 0));
  if (s_stat(unname, &stbuff, NULL) ||
    (!extended && !S_ISDIR(stbuff.st_mode)) ) {
    result = -0x9c;
  } else {
    NW_OIC nwoic;
    nwoic.id      = id;
    nwoic.trustee = trustee;
    result=tru_add_trustee_set(nwpath.volume, unname,
                              &stbuff,
                              1, &nwoic);
  }
  return(result);
}

int nw_del_trustee(int dir_handle, uint8 *data, int len,
                   uint32 id, int extended)
/* extended 0=del trustee from dir, 1= del ext trustee from dirs and files */
{
  char          unname[256];
  struct stat   stbuff;
  NW_PATH       nwpath;
  int result = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);
  if (result < 0) return(result);
  xstrcpy(unname, build_unix_name(&nwpath, 0));
  if (s_stat(unname, &stbuff, NULL) ||
    (!extended && !S_ISDIR(stbuff.st_mode)) ) {
    result = -0x9c;
  } else {
    result=tru_del_trustee(nwpath.volume, unname, &stbuff, id);
  }
  return(result);
}

int nw_set_dir_info(int dir_handle, uint8 *data, int len,
                   uint32 owner_id, int max_rights,
                   uint8 *creationdate, uint8 *creationtime)
{
  char          unname[256];
  struct stat   stbuff;
  NW_PATH       nwpath;
  int result = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len,0);
  if (result < 0) return(result);
  xstrcpy(unname, build_unix_name(&nwpath, 0));
  if (s_stat(unname, &stbuff, NULL) || !S_ISDIR(stbuff.st_mode)) {
    result = -0x9c;
  } else {
    result=nw_utime_node(nwpath.volume, unname, &stbuff,
                     nw_2_un_time(creationdate, creationtime));
    if (!result)
      result=tru_set_inherited_mask(nwpath.volume, unname,
                       &stbuff, max_rights);
  }
  return(result);
}


int nw_modify_max_right_mask(int dir_handle, uint8 *data, int len,
                   int grant_mask, int revoke_mask)
{
  char          unname[256];
  struct stat   stbuff;
  NW_PATH       nwpath;
  int old_mask;
  int new_mask;
  int result = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);

  if (result < 0) return(result);

  xstrcpy(unname, build_unix_name(&nwpath, 0));
  if (s_stat(unname, &stbuff, NULL) || !S_ISDIR(stbuff.st_mode))
    return(-0x9c);

  old_mask = tru_get_inherited_mask(nwpath.volume, unname, &stbuff);
  new_mask = (old_mask | (grant_mask & 0xff)) & (~revoke_mask & 0xff);

  result = tru_set_inherited_mask(nwpath.volume, unname, &stbuff, new_mask);
  if (!result) {
    int verify_mask = tru_get_inherited_mask(nwpath.volume, unname, &stbuff);
    XDPRINTF((1, 0, "NCP22/4 Modify Max Right Mask: path=`%s`, old=0x%02x, grant=0x%02x, revoke=0x%02x, new=0x%02x, verify=0x%02x",
              unname,
              old_mask & 0xff,
              grant_mask & 0xff,
              revoke_mask & 0xff,
          new_mask & 0xff,
              verify_mask & 0xff));
  }

  return result;
}

int nw_scan_user_trustee(int volume, int *sequence, uint32 id,
                        int *access_mask, uint8 *path)
{
  uint8 volname[100];
  int result = nw_get_volume_name(volume, volname, sizeof(volname));
  if (result > 0) {
    if (*sequence==-1 || *sequence==0xffff)
      *sequence=0;
    result=get_volume_user_trustee(volume, id, 0, sequence,
                                access_mask, path);
  }
  return(result);
}

int nw_scan_for_trustee( int    dir_handle,
                         int    sequence,
                         uint8 *path,
                         int    len,
                         int     max_entries,
                         uint32 *ids,
                         int    *trustees,
                         int     extended)
{
  char          unname[256];
  struct stat   stbuff;
  NW_PATH       nwpath;
  int result = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, path, len,
                     (extended) ? 0 : 1);
  if (result < 0) return(result);
  xstrcpy(unname, build_unix_name(&nwpath, 0));
  if (s_stat(unname, &stbuff, NULL) ||
    (!extended && !S_ISDIR(stbuff.st_mode)) ) {
    result = -0x9c;
  } else {
    result=tru_get_trustee_set(nwpath.volume, unname,
                              &stbuff,
                              sequence,
                              max_entries, ids, trustees);
  }
  return(result);
}


/* quick and dirty hack for 0.99.pl18, 25-Sep-99 */
int nw_log_file(int lock_flag,
                  int timeout,
                  int dir_handle,
                  int len,
                  char *data)

/*
 *   lock_flag
 *   -1 = remove lock
 *   -2 = remove lock + log 
 *    0 = log               
 *    1 = lock exclusive
 *    3 = shared lock
*/

{
  NW_PATH nwpath;
  struct stat stbuff;
  int completition = conn_get_kpl_path(&nwpath, &stbuff, dir_handle, data, len, 0);
  if (completition > -1) {
    char unixname[300];
    xstrcpy(unixname, build_unix_name(&nwpath, 0));
    if (seteuid(0)) {}
    completition = stat(unixname, &stbuff);
   (void)reseteuid();
    if (!completition) {
      if (lock_flag < 0) {  /* remove lock */
        if (lock_flag != -1) 
          completition =
            share_set_file_add_rm(lock_flag, stbuff.st_dev, stbuff.st_ino);
        if (!completition)
           completition = share_file(stbuff.st_dev, stbuff.st_ino,
                         0x300, 0) ? -0xff : 0;
      } else if (lock_flag == 0 || lock_flag == 1 || lock_flag == 3) {  
        completition =
          share_set_file_add_rm(lock_flag, stbuff.st_dev, stbuff.st_ino);
        if ((!completition) && lock_flag)
          /* add lock */
          completition = share_file(stbuff.st_dev, stbuff.st_ino,
                         lock_flag << 8, 1) ? -0xfe : 0;
      } else
        completition = -0xfb;
    } else
      completition = -0xff;   /* not exist, errorcode TODO  ! */
  }
  return(completition);
}

