#include "basic.h"
#include "smbpriv.h"
#include "pstr.h"
#include "errlog.h"
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

int smb_errno;

/*****************************************************************************/
/*                                                                           */
/*  smb_connect                                                              */
/*                                                                           */
/*****************************************************************************/
struct smb_conn *
smb_connect(const char *server,
            int port,
            const char *myname,
            const char *service,
            const char *user,
            const char *passwd)
{
  struct sockaddr_in serv_addr;
  struct hostent *h;
  char my_name[MAXHOSTNAMELEN+1];
  pstr full_service;
  struct smb_conn *result;
  int serv_socket;

  AssertPrintStr(server);
  AssertPrintStr(service);
  
  memset(&serv_addr, 0, sizeof(serv_addr));
  MemGarbage(my_name, MAXHOSTNAMELEN);

  if (myname == NULL) {
    /* gethostbyname doesn't 0-terminate the string if at max.               */
    my_name[MAXHOSTNAMELEN] = 0;
    gethostname(my_name, MAXHOSTNAMELEN);
    myname = my_name;
  }
  
  if ((h = gethostbyname(server)) == NULL) {
    ErrLog(ERR_FAILURE, "%s: unknown host\n");
    return NULL;
  } 

  serv_addr.sin_family      = AF_INET;
  serv_addr.sin_addr.s_addr = ((struct in_addr *)(h->h_addr))->s_addr;
  serv_addr.sin_port        = htons(port);

  full_service = pstr_4("\\\\", server, "\\", service);
  pstr_upper(full_service);

  /* Connect to the server given by serv_addr                                */
  if ((serv_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    ErrLog(ERR_FAILURE, "could not create socket\n");
    result = NULL;
    goto finished;
  }
  
  if ((connect(serv_socket,
               (struct sockaddr *)&serv_addr,
               sizeof(serv_addr))) == -1) {
    ErrLog(ERR_FAILURE, "could not connect to server\n");
    close(serv_socket);
    result = NULL;
    goto finished;
  }

  result = smb_do_connect(serv_socket, myname,
                          server, full_service,
                          user, passwd);

 finished:
  pstr_delete(full_service);
  return result;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_do_connect                                                           */
/*                                                                           */
/*****************************************************************************/
struct smb_conn *
smb_do_connect(const int serv_socket,
               const char *my_name,
               const char *serv_name,
               const char *service,
               const char *user,
               const char *passwd)
{
  struct smb_conn *conn, *n_conn;
  int max_xmit = 65536+4;       /* Space for first requests                  */
  char *b, *p;
  int len;
  pstr namebuf;
  pstr username;
  DWORD sesskey;
  struct {
    enum protocols prot;
    char *name;
  } prots[] = {
    {PROT_CORE,"PC NETWORK PROGRAM 1.0"},
    {PROT_COREPLUS,"MICROSOFT NETWORKS 1.03"},
#if LANMAN1
    {PROT_LANMAN1,"MICROSOFT NETWORKS 3.0"},
    {PROT_LANMAN1,"LANMAN1.0"},
#endif
#if LANMAN2
    {PROT_LANMAN2,"LM1.2X002"},
#endif
    {-1,NULL}
  };
  char dev[] = "A:";
  int numprots, plength, m;

  if (passwd == NULL) {
    passwd = "";
  }
  if (user == NULL) {
    user = getenv("USER");
    if (user == NULL)
      user = getenv("LOGNAME");
    if (user == NULL)
      user = "";
  }
  username = pstr_1(user);

  conn = (struct smb_conn *)new_conn(max_xmit);

  AssertPtr(conn);
  if (!conn) {
    pstr_delete(username);
    return NULL;
  } 

  /* setup the connection block                                              */
  conn->server = serv_socket;
  conn->tid = 0;
  conn->pid = getpid();
  conn->server_uid = conn->uid = getuid();
  conn->gid = getgid();
  conn->mid = conn->pid + 20;

  m = umask(0);
  umask(m);

  m = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~m;
  
  conn->file_mode = m | S_IFREG;
  conn->dir_mode  = m | S_IXUSR | S_IFDIR;


  /* send a rfc1002 session request packet                                   */

  b = conn->buffer;

  len = 4;

  p = b + len;                  /* put in server's name                      */
  namebuf = pstr_1(serv_name);
  AssertStr(namebuf);
  pstr_upper(namebuf);
  len += name_mangle(namebuf, p);
  pstr_delete(namebuf);

  p = b + len;                  /* and my own                                */
  namebuf = pstr_1(my_name);
  AssertStr(namebuf);
  pstr_upper(namebuf);
  len += name_mangle(namebuf, p);
  pstr_delete(namebuf);
  
  /* set length of packet                                                    */
  smb_setlen(b, len);
  b[0] = 0x81;                  /* SESSION REQUEST                           */
  
  if (   send_smb(conn)
      || receive_1002(conn->server, conn->buffer, conn->max_xmit, 0)
      || BVAL(b, 0) != 0x82) {
    ErrLog(ERR_FAILURE, "Failure in SESSION SETUP\n");
    smb_errno = BVAL(b, 4);
    close(conn->server);
    free(conn);
    pstr_delete(username);
    return NULL;
  }

  memset(b, 0, smb_size);
  
  /* To send a SMB Negotiate Protocol, setup protocol strings                */
  plength = 0;
  for (numprots = 0; prots[numprots].name != NULL; numprots++)
    plength += strlen(prots[numprots].name) + 2;
  
  setup_smb(conn, SMBnegprot, 0, plength);

  p = smb_buf(b);
  AssertConnSpace(conn, p, plength);
  for (numprots = 0; prots[numprots].name != NULL; numprots++) {
    *p++ = 2;
    strcpy(p, prots[numprots].name);
    p += strlen(p) + 1;
  }
 
  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBnegprot, 1, -1)
      || ((int)WVAL(b, smb_vwv0) >= numprots)) {
    ErrLog(ERR_FAILURE, "Error in SMBnegprot\n");
    smb_errno = smb2unix_errno(conn);
    close(conn->server);
    free(conn);
    pstr_delete(username);
    return NULL;
  }

  conn->prot = WVAL(b, smb_vwv0);

  if (conn->prot >= PROT_LANMAN1) {
    max_xmit = min(max_xmit, (int)WVAL(b, smb_vwv2));
    sesskey  = DVAL(b, smb_vwv6);

    setup_smb(conn, SMBsesssetupX,
              10, 2+strlen(username)+strlen(passwd));

    WSET(b, smb_vwv0, 0);       /* Patch to zero                             */
    BSET(b, smb_vwv0, 0xff);    /* No command follows                        */
    WSET(b, smb_vwv2, max_xmit);
    WSET(b, smb_vwv3, 2);       /* Max multiplexing requests                 */
    WSET(b, smb_vwv4, conn->pid); /* Additional session in VC                */
    DSET(b, smb_vwv5, sesskey);
    WSET(b, smb_vwv7, strlen(passwd)+1);

    p = smb_buf(b);
    strcpy(p, passwd);
    p += strlen(passwd)+1;
    strcpy(p, username);
    if (   smb_request(conn)
        || !smb_valid_answer(b, SMBsesssetupX, 3, 0)) {
      ErrLog(ERR_FAILURE, "Failure in SBMsesssetupX\n");
      smb_errno = smb2unix_errno(conn);
      close(conn->server);
      free(conn);
      pstr_delete(username);
      return NULL;
    }
    conn->server_uid = WVAL(b, smb_uid);
  }
    
  while (1) {
    /* Now we've got a connection -- send a tcon message
       First try with password, then without                                 */

    setup_smb(conn, SMBtcon, 0,
              6 + strlen(service) + strlen(passwd) + strlen(dev));

    p = smb_buf(b);

    AssertConnSpace(conn, p, strlen(service)+2);
    *p++ = 4;
    strcpy(p, service);
    p += strlen(p) + 1;

    AssertConnSpace(conn, p, strlen(passwd)+2);
    *p++ = 4;
    strcpy(p, passwd);
    p += strlen(p) + 1;

    AssertConnSpace(conn, p, strlen(dev)+2);
    *p++ = 4;
    strcpy(p, dev);

    if (   smb_request(conn)
        || !smb_valid_answer(b, SMBtcon, 2, 0)) {

      if (strlen(passwd) != 0) {
        /* Try again without password                                        */
        passwd = "";
        continue;
      }

      ErrLog(ERR_FAILURE, "Failure in SBMtcon\n");
      smb_errno = smb2unix_errno(conn);
      close(conn->server);
      free(conn);
      pstr_delete(username);
      return NULL;
    }
    break;
  }
  /* Everythings fine, we have a valid connection                            */
  /* max_xmit from server doesn't include TCP-SMB-Header of 4 bytes          */

  conn->tid = WVAL(b, smb_vwv1);
  max_xmit = WVAL(b, smb_vwv0) + 4;
  if (!(n_conn = new_conn(max_xmit))) {
    close(conn->server);
    free(conn);
    pstr_delete(username);
    return NULL;
  }

  memcpy(n_conn, conn, sizeof(struct smb_conn));
  n_conn->max_xmit = max_xmit;  /* Has just been overwritten                 */

  free(conn);
  pstr_delete(username);
  return n_conn;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_disconnect                                                           */
/*                                                                           */
/*****************************************************************************/
Status
smb_disconnect(struct smb_conn *conn)
{
  Status result = Ok;

  AssertConn(conn);
  
  setup_smb(conn, SMBtdis, 0, 0);

  if (   smb_request(conn)
      || !smb_valid_answer(conn->buffer, SMBtdis, 0, 0)) {
    ErrLog(ERR_PROTERR, "Failed to disconnect\n");
    result = Fail;
  }

  close(conn->server);
  free(conn);
  return result;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_dskattr                                                              */
/*                                                                           */
/*****************************************************************************/
Status
smb_dskattr(struct smb_conn *conn, struct smb_servattr *attr)
{
  char *b;
  
  AssertConn(conn);
  AssertPtr(attr);

  b = setup_smb(conn, SMBdskattr, 0, 0);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBdskattr, 5, 0)) {
    errno = smb2unix_errno(conn);
    return Fail;
  }

  attr->total       = WVAL(b, smb_vwv0);
  attr->allocblocks = WVAL(b, smb_vwv1);
  attr->blocksize   = WVAL(b, smb_vwv2);
  attr->free        = WVAL(b, smb_vwv3);

  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_getatr                                                               */
/*                                                                           */
/*****************************************************************************/
Status
smb_getatr(struct smb_conn *conn, const char *name, struct smb_finfo *finfo)
{
  char *b, *p;
  
  AssertConn(conn);
  AssertStr(name);
  AssertPtr(finfo);

  MemGarbage(finfo, sizeof(*finfo));
  
  b = setup_smb(conn, SMBgetatr, 0, 2+strlen(name));
  p = smb_buf(b);

  AssertConnSpace(conn, p, strlen(name)+1);
  *p++ = 4;
  strcpy(p, name);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBgetatr, 10, 0)) {
    return Fail;
  }

  finfo->attrib = WVAL(b, smb_vwv0);
  finfo->mtime  = DVAL(b, smb_vwv1);
  finfo->size   = DVAL(b, smb_vwv3);
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_setatr                                                               */
/*                                                                           */
/*****************************************************************************/
Status
smb_setatr(struct smb_conn *conn, const char *name, struct smb_finfo *finfo)
{
  char *b, *p;
  
  AssertConn(conn);
  AssertStr(name);
  AssertPtr(finfo);

  b = setup_smb(conn, SMBsetatr, 8, strlen(name) + 4);
  p = smb_buf(b);

  WSET(b, smb_vwv0, finfo->attrib);
  DSET(b, smb_vwv1, finfo->mtime);
  WSET(b, smb_vwv3, 0);
  WSET(b, smb_vwv4, 0);
  WSET(b, smb_vwv5, 0);
  WSET(b, smb_vwv6, 0);
  WSET(b, smb_vwv7, 0);

  AssertConnSpace(conn, p, strlen(name)+4);
  *p++ = 4;
  strcpy(p, name);
  p += strlen(name) + 1;

  *p++ = 4;
  *p++ = 0;

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBsetatr, 0, 0)) {
    return Fail;
  }

  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_do_dir                                                               */
/*                                                                           */
/*****************************************************************************/
Status
smb_do_dir(struct smb_conn *conn, const char *mask,
           void (*handler)(struct smb_finfo *finfo, void *data), void *data)
{
  const int dir_struct_size = 43;
  char *p, *b;
  int i;

  Bool first = True;
  char status[21];

  int asked = (conn->max_xmit - 100) / dir_struct_size;
  int received = 0;
  struct smb_finfo finfo;

  AssertConn(conn);
  AssertPtr(mask);
  AssertPtr(handler);
  
  while(1) {

    if (first) {
      b = setup_smb(conn, SMBsearch, 2, 5 + strlen(mask));
    } else {
      b = setup_smb(conn, SMBsearch, 2, 5 + 21);
    }

    WSET(b, smb_vwv0, asked);
    WSET(b, smb_vwv1, aDIR);

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

    if (first) {
      AssertConnSpace(conn, p, strlen(mask)+1);
      strcpy(p, mask);
    } else {
      AssertConnSpace(conn, p, strlen("")+1);
      strcpy(p, "");
    }

    p += strlen(p) + 1;

    *p++ = 5;
    if (first) {
      AssertConnSpace(conn, p, sizeof(WORD));
      WSET(p, 0, 0);
    } else {
      AssertConnSpace(conn, p, 21);
      WSET(p, 0, 21);
      p += 2;
      memcpy(p, status, 21);
    }

    if (smb_request(conn)) {
      break;
    }

    if (!smb_valid_answer(b, SMBsearch, 1, -1)) { PathTest;
      break;
    }
    
    received = WVAL(b, smb_vwv0);
    first = False;

    if (   (received <= 0)
        || (smb_bcc(b) != received * dir_struct_size + 3)) { PathTest;
      break;
    }

    memcpy(status,smb_buf(b) + 3 + ((received-1)*dir_struct_size), 21);
    
    for (p = smb_buf(b) + 3, i = 0; i < received; i++) {
      fill_finfo(p, &finfo);
      handler(&finfo, data);
      p += dir_struct_size;
    }
  }
  return False;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_open                                                                 */
/*                                                                           */
/*****************************************************************************/
Status
smb_open(struct smb_conn *conn, const char *name, struct smb_finfo *finfo)
{
  char *b;
  char *p;
  const char *base_name;
  const WORD o_attrib = aRONLY | aHIDDEN | aSYSTEM | aARCH;

  AssertConn(conn);
  AssertStr(name);
  AssertPtr(finfo);

  base_name = dos_base_name(name);
  AssertStr(base_name);
  
  if (strlen(base_name) > SMB_NAMELEN - 1) { /* Restriction of Protocol */
    errno = ENAMETOOLONG;
    return Fail;
  }

  b = setup_smb(conn, SMBopen, 2, 2 + strlen(name));

  WSET(b, smb_vwv0, 0x42);      /* open for r/w, deny none */
  WSET(b, smb_vwv1, o_attrib);
  
  p = smb_buf(b);
  AssertConnSpace(conn, p, strlen(name)+1);
  *p++ = 4;
  strcpy(p, name);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBopen, 7, 0)) {
    if (smb2unix_errno(conn) != EACCES) {
      return Fail;
    }
    
    /* Retry it with read/only */
  
    b = setup_smb(conn, SMBopen, 2, 2 + strlen(name));
    
    WSET(b, smb_vwv0, 0x40);	/* open for read, deny none */
    WSET(b, smb_vwv1, o_attrib);
  
    p = smb_buf(b);
    AssertConnSpace(conn, p, strlen(name)+1);
    *p++ = 4;
    strcpy(p, name);

    if (   smb_request(conn)
        || !smb_valid_answer(b, SMBopen, 7, 0)) {
      return Fail;
    }

    WSET(b, smb_vwv1, (WVAL(b, smb_vwv1) | aRONLY)); /* We can only read */
  }
  
  finfo->fid    = WVAL(b, smb_vwv0);
  finfo->attrib = WVAL(b, smb_vwv1);
  finfo->mtime  = DVAL(b, smb_vwv2);
  finfo->size   = DVAL(b, smb_vwv4);
  finfo->access = (WVAL(b, smb_vwv6) & 0x3);

  Assert(strlen(base_name) <= SMB_NAMELEN-1);

  MemGarbage(finfo->name, SMB_NAMELEN);

  strcpy(finfo->name, base_name);
  
  return Ok;
}
  
/*****************************************************************************/
/*                                                                           */
/*  smb_close                                                                */
/*                                                                           */
/*****************************************************************************/
Status
smb_close(struct smb_conn *conn, sfid_t fid, time_t time)
{
  char *b;

  AssertConn(conn);

  b = setup_smb(conn, SMBclose, 3, 0);
  
  WSET(b, smb_vwv0, fid);
  DSET(b, smb_vwv1, time);      /* TODO: convert unix to dos time */

  return (   (smb_request(conn) == Ok)
          && (smb_valid_answer(b, SMBclose, 0, 0)) ? Ok : Fail);
}

/*****************************************************************************/
/*                                                                           */
/*  smb_do_create                                                            */
/*                                                                           */
/*****************************************************************************/
static Status
smb_do_create(struct smb_conn *conn, const char *name, struct smb_finfo *finfo,
              WORD command)
{
  char *b;
  char *p;
  const char *base_name;

  AssertConn(conn);
  AssertStr(name);
  AssertPtr(finfo);

  base_name = dos_base_name(name);
  AssertStr(base_name);
  
  if (strlen(base_name) > SMB_NAMELEN - 1) { /* Restriction of Protocol */
    errno = ENAMETOOLONG;
    return Fail;
  }

  b = setup_smb(conn, command, 3, 2 + strlen(name));
  
  WSET(b, smb_vwv0, finfo->attrib);
  DSET(b, smb_vwv1, finfo->mtime);
  
  p = smb_buf(b);
  AssertConnSpace(conn, p, strlen(name)+1);
  *p++ = 4;
  strcpy(p, name);

  if (   smb_request(conn)
      || !smb_valid_answer(b, command, 1, 0)) {
    return Fail;
  }

  finfo->fid    = WVAL(b, smb_vwv0);
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_create                                                               */
/*                                                                           */
/*****************************************************************************/
Status
smb_create(struct smb_conn *conn, const char *name, struct smb_finfo *finfo)
{
  return smb_do_create(conn, name, finfo, SMBcreate);
}

/*****************************************************************************/
/*                                                                           */
/*  smb_mknew                                                                */
/*                                                                           */
/*****************************************************************************/
Status
smb_mknew(struct smb_conn *conn, const char *name, struct smb_finfo *finfo)
{
  return smb_do_create(conn, name, finfo, SMBmknew);
}

/*****************************************************************************/
/*                                                                           */
/*  smb_mkdir                                                                */
/*                                                                           */
/*****************************************************************************/
Status
smb_mkdir(struct smb_conn *conn, const char *name)
{
  char *b, *p;

  AssertConn(conn);
  AssertStr(name);

  b = setup_smb(conn, SMBmkdir, 0, 2 + strlen(name));

  p = smb_buf(b);
  AssertConnSpace(conn, p, strlen(name)+2);
  *p++=4;
  strcpy(p, name);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBmkdir, 0, 0)) {
    errno = smb2unix_errno(conn);
    return Fail;
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_rmdir                                                                */
/*                                                                           */
/*****************************************************************************/
Status
smb_rmdir(struct smb_conn *conn, const char *name)
{
  char *b, *p;

  AssertConn(conn);
  AssertStr(name);

  b = setup_smb(conn, SMBrmdir, 0, 2 + strlen(name));

  p = smb_buf(b);
  AssertConnSpace(conn, p, strlen(name)+2);
  *p++=4;
  strcpy(p, name);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBrmdir, 0, 0)) {
    errno = smb2unix_errno(conn);
    return Fail;
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_unlink                                                               */
/*                                                                           */
/*****************************************************************************/
Status
smb_unlink(struct smb_conn *conn, const char *name)
{
  char *b, *p;

  AssertConn(conn);
  AssertStr(name);

  b = setup_smb(conn, SMBunlink, 1, 2 + strlen(name));

  WSET(b, smb_vwv0, 0);

  p = smb_buf(b);
  AssertConnSpace(conn, p, strlen(name)+2);
  *p++=4;
  strcpy(p, name);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBunlink, 0, 0)) {
    errno = smb2unix_errno(conn);
    return Fail;
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_read                                                                 */
/*                                                                           */
/*****************************************************************************/
Status
smb_read(struct smb_conn *conn, sfid_t fid, off_t off, void *buf, size_t size,
         size_t *nread)
{
  char *b;
  size_t datalen, bufsize;

  AssertConn(conn);
  AssertPtr(buf);

  MemGarbage(buf, size);

  *nread = 0;

  b = setup_smb(conn, SMBread, 5, 0);

  /* This is determined by format of reply to SMBread */
  bufsize = conn->max_xmit - smb_size - 5 * sizeof(WORD) - 3;

  while (*nread < size) {

    WSET(b, smb_vwv0, fid);
    WSET(b, smb_vwv1, min(bufsize, size - *nread));
    DSET(b, smb_vwv2, off);
    WSET(b, smb_vwv4, size - *nread);

    if (   smb_request(conn)
        || !smb_valid_answer(b, SMBread, 5, -1)
        /* || (BVAL(smb_buf(b), 0) != 1) We do not get a 1 from samba here */
        ) {
      return Fail;
    }

    datalen = WVAL(smb_buf(b), 1);

    if (datalen == 0) {
      return Ok;
    }
      
    *nread += datalen;
    off    += datalen;

    memcpy(buf, smb_buf(b) + 3, datalen);
    buf += datalen;

    b = setup_smb(conn, SMBread, 5, 0);
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_write                                                                */
/*                                                                           */
/*****************************************************************************/
Status
smb_write(struct smb_conn *conn, sfid_t fid, off_t off, void *buf, size_t size,
          size_t *nwritten)
{
  char *b, *p;
  size_t datalen, bufsize, to_write;

  AssertConn(conn);
  AssertPtr(buf);

  *nwritten = 0;

  /* This is determined by format of reply to SMBread */
  bufsize = conn->max_xmit - smb_size - 5 * sizeof(WORD) - 3;

  while (*nwritten < size) {

    to_write = min(bufsize, size - *nwritten);

    b = setup_smb(conn, SMBwrite, 5, to_write+3);
    
    WSET(b, smb_vwv0, fid);
    WSET(b, smb_vwv1, to_write);
    DSET(b, smb_vwv2, off);
    WSET(b, smb_vwv4, size - *nwritten);

    p = smb_buf(b);
    AssertConnSpace(conn, p, to_write+3);
    *p++ = 1;                   /* Data buffer */
    WSET(p, 0, to_write);
    p+=2;
    memcpy(p, buf, to_write);

    if (   smb_request(conn)
        || !smb_valid_answer(b, SMBwrite, 1, 0)
        ) {
      return Fail;
    }

    datalen = WVAL(b, smb_vwv0);
    *nwritten += datalen;
    off    += datalen;
    buf    += datalen;

    if (datalen != to_write) {
      errno = ENOSPC;
      return Fail;
    }
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_truncate                                                             */
/*                                                                           */
/*****************************************************************************/
Status
smb_truncate(struct smb_conn *conn, sfid_t fid, size_t length)
{
  char *b, *p;
  
  AssertConn(conn);

  b = setup_smb(conn, SMBwrite, 5, 3);

  WSET(b, smb_vwv0, fid);
  WSET(b, smb_vwv1, 0);
  DSET(b, smb_vwv2, length);
  WSET(b, smb_vwv4, 0);
  
  p = smb_buf(b);
  *p++ = 1;
  WSET(p, 0, 0);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBwrite, 1, 0))
    return Fail;

  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_mv                                                                   */
/*                                                                           */
/*****************************************************************************/
Status
smb_mv(struct smb_conn *conn, const char *opath, const char *npath)
{
  char *b, *p;

  AssertConn(conn);
  AssertStr(opath);
  AssertStr(npath);

  b = setup_smb(conn, SMBmv, 1, strlen(opath)+strlen(npath)+4);

  WSET(b, smb_vwv0, 0);         /* no attrib */

  p = smb_buf(b);
  AssertConnSpace(conn, p, strlen(opath)+2);
  *p++ = 4;
  strcpy(p, opath);

  p += strlen(opath)+1;
  AssertConnSpace(conn, p, strlen(npath)+2);
  *p++ = 4;
  strcpy(p, npath);

  if (   smb_request(conn)
      || !smb_valid_answer(b, SMBmv, 0, 0)) {
    errno = smb2unix_errno(conn);
    return Fail;
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_valid_name                                                           */
/*                                                                           */
/*****************************************************************************/
Bool
smb_valid_name(const char *n)
{
  pstr name = n;
  static char bad_chars[] = "*?<>|\"+=,;:\\";
  int i;
  unsigned char c;
  if (*name == 0) return False;
  for (i=0; i<8; i++) {
    c = *name++;
    if (c == 0) return True;
    if (c <= ' ' || strchr(bad_chars, c) || c > 127)  return False;
    if (c == '.') {
      if (i == 0)
        return False;           /* No name starting with . */
      else
        break;
    }
  }
  if (i >= 8) return False;
  for (i=0; i<3; i++) {
    c = *name++;
    if (c == 0) return True;
    if (c <= ' ' || strchr(bad_chars, c) || c == '.' || c > 127) return False;
  }
  return (*name == 0);
}

/*****************************************************************************/
/*                                                                           */
/*  smb2unix_errno  */
/*                                                                           */
/*****************************************************************************/
int
smb2unix_errno(struct smb_conn *conn)
{
  int rcls, err;
  int e_dos[] = {
    EIO,
    EINVAL,                     /* eINVALID_FUNCTION */
    ENOENT,                     /* eFILE_NOT_FOUND */
    ENOENT,                     /* eINVALID_PATH */
    ENFILE,                     /* eTOO_MANY_FILES_OPEN */
    EACCES,                     /* eACCESS_DENIED */
    EBADF                       /* eINVALID_FILE_HANDLE */
  };
  int e_srv[] = {
    EIO,
    EIO,                        /* ERRerror */
    EACCES,                     /* ERRbadpw */
    EIO,                        /* ERRbadtype */
    EACCES                      /* ERRaccess */
  };

  AssertConn(conn);
  rcls = conn->rcls;
  err  = conn->err;
  
  if (rcls == ERRDOS) {
    if (err < sizeof(e_dos) / sizeof(e_dos[0])) {
      return e_dos[err];
    }
  }
  if (rcls == ERRSRV) {
    if (err < sizeof(e_srv) / sizeof(e_srv[0])) {
      return e_srv[err];
    }
  }
  return EIO;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_is_valid_buf                                                         */
/*                                                                           */
/*****************************************************************************/
static Bool
smb_is_valid_buf(const char *buf)
{
  AssertPtr(buf);

  return (   (BVAL(buf, 4) == 0xff)
          && (BVAL(buf, 5) == 'S')
          && (BVAL(buf, 6) == 'M')
          && (BVAL(buf, 7) == 'B')
          && (smb_len(buf) + 4
              == smb_size + BVAL(buf, smb_wct) * sizeof(WORD) + smb_bcc(buf)));
}

/*****************************************************************************/
/*                                                                           */
/*  smb_valid_answer                                                         */
/*  bcc == -1: don't care                                                    */
/*                                                                           */
/*****************************************************************************/
static Bool
smb_valid_answer(const char *buf, int cmd, int wct, int bcc)
{
  AssertPtr(buf);

  return (   (BVAL(buf, smb_com) == cmd)
          && (BVAL(buf, smb_wct) >= wct) /* Only assure we got enough */
          && (   (bcc == -1)
              || (smb_bcc(buf) == bcc))); /* In bcc we get data */
}


/*****************************************************************************/
/*                                                                           */
/*  smb_buf                                                                  */
/*                                                                           */
/*****************************************************************************/
static char *
smb_buf(const char *buf)
{
  if (!smb_is_valid_buf(buf)) { PathTest;
    ErrLog(ERR_PROTERR, "Invalid smb buffer\n");
    return NULL;
  }
  return ((char *)(buf + smb_size + BVAL(buf,smb_wct)*sizeof(WORD)));
}

/*****************************************************************************/
/*                                                                           */
/*  smb_len                                                                  */
/*                                                                           */
/*****************************************************************************/
static unsigned int
smb_len(const char *buf)
{
  int len;

  AssertPtr(buf);
  
  len = (BVAL(buf, 2) << 8) + (BVAL(buf, 3));

  if (BVAL(buf,1) & 1) { PathTest;
    len += 1<<16;
  }

  return len;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_setlen                                                               */
/*  Length for rfc 1002, not including the leading 4 bytes                   */
/*                                                                           */
/*****************************************************************************/
static void
smb_setlen(char *buf, int len)
{
  AssertPtr(buf);

  BSET(buf, 0, 0);
  BSET(buf, 1, 0);
  BSET(buf, 2, (len & (0xff<<8))>>8);
  BSET(buf, 3, len & 0xff);
  if (len >= (1 << 16)) { PathTest;
    BSET(buf, 1, (BVAL(buf, 1) | 1));
  }
}


/*****************************************************************************/
/*                                                                           */
/*  setup_smb                                                                */
/*                                                                           */
/*****************************************************************************/
static char *
setup_smb(struct smb_conn *conn, int command, int wct, int bcc)
{
  char *buf;
  unsigned int xmit_length;
  AssertConn(conn);
  buf = conn->buffer;

  MemGarbage(buf, conn->max_xmit);

  Assert( (wct & 0xff)   == wct );
  Assert( (bcc & 0xffff) == bcc );
  
  memset(buf, 0, smb_size);
  BSET(buf, smb_wct, wct);
  WSET(buf, smb_vwv + wct * sizeof(WORD), bcc);

  xmit_length = smb_size + wct * sizeof(WORD) + bcc;
  Assert(xmit_length < conn->max_xmit);
  smb_setlen(buf,xmit_length - 4);

  BSET(buf, 4, 0xff);
  BSET(buf, 5, 'S');
  BSET(buf, 6, 'M');
  BSET(buf, 7, 'B');

  BSET(buf, smb_com, command);
  WSET(buf, smb_pid, conn->pid);
  WSET(buf, smb_uid, conn->server_uid);
  WSET(buf, smb_mid, conn->mid);
  WSET(buf, smb_tid, conn->tid);
  if (conn->prot > PROT_CORE) {
    BSET(buf, smb_flg, 0x8);    /* Treat all filenames as caseless           */
  }

  return buf;
}

/*****************************************************************************/
/*                                                                           */
/*  smb_request                                                              */
/*                                                                           */
/*****************************************************************************/
static Status
smb_request(struct smb_conn *conn)
{
  char *b;
  
  AssertConn(conn);

  if (   send_smb(conn)
      || receive_smb(conn, 0)) {
    conn->rcls = ERRCMD;        /* could not transfer command                */
    conn->err  = 0;
    ErrLog(ERR_PROTERR, "Could not send/receive_smb\n");
    return Fail;
  }

  b = conn->buffer;
  if (BVAL(b, smb_rcls) != 0) {
    conn->rcls = BVAL(b, smb_rcls);
    conn->err  = WVAL(b, smb_err);
    ErrLog(ERR_PROTERR,
           "Received error response: rcls %d, err %d\n",
           conn->rcls, conn->err);
    return Fail;
  }

  return Ok;
}


/*****************************************************************************/
/*                                                                           */
/*  Write_data                                                               */
/*                                                                           */
/*****************************************************************************/
static Status
write_data(int fd, const char *buf, size_t count)
{
  size_t ret, nwritten;

  AssertPtr(buf);

  nwritten = 0;
  while (nwritten < count) {
    ret = write(fd, buf + nwritten, count - nwritten);
    if (ret <= 0) { PathTest;
      ErrLog(ERR_PANIC,
             "Error writing %d bytes: %s\n",
             count - nwritten,
             strerror(errno));
      return Fail;
    }
    nwritten += ret;
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  send_smb                                                                 */
/*                                                                           */
/*****************************************************************************/
static Bool
send_smb(struct smb_conn *conn)
{
  int len;
  char *b;

  AssertConn(conn);
  b = conn->buffer;
  
  len = smb_len(b) + 4;
  Assert(conn->max_xmit >= len);

  return write_data(conn->server, b, len);
}

/*****************************************************************************/
/*                                                                           */
/*  read_data                                                                */
/*                                                                           */
/*****************************************************************************/
static Status
read_data(int fd, char *buffer, size_t count)
{
  int nread = 0;
  int ret;

  MemGarbage(buffer, count);
  
  while (nread < count) {
    ret = read(fd, buffer+nread, count - nread);
    if (ret <= 0) {
      ErrLog(ERR_PANIC,
             "Error reading %d bytes: %s\n",
             count - nread,
             strerror(errno));
      return Fail;
    }
    nread += ret;
  }
  return Ok;
}

/*****************************************************************************/
/*                                                                           */
/*  receive_1002                                                             */
/*  receive a packet after RFC 1002                                          */
/*  timeout not implemented                                                  */
/*                                                                           */
/*****************************************************************************/
static Status
receive_1002(int fd, char *buf, int bufsize, int timeout)
{
  int len, msg_type;

  AssertPtr(buf);
  Assert(bufsize >= 4);

  MemGarbage(buf, bufsize);

 again:

  if (read_data(fd,buf,4)) return Fail;

  len = smb_len(buf);
  
  msg_type = BVAL(buf,0);
  if(len == 0 && msg_type == 0x85) { PathTest;
    ErrLog(ERR_STATUS, "Got keepalive message\n");
    goto again;
  }

  Assert(bufsize >= len + 4);
  
  return read_data(fd,buf+4,len);
}

/*****************************************************************************/
/*                                                                           */
/*  receive_smb                                                              */
/*  timeout is currently not implemented                                     */
/*                                                                           */
/*****************************************************************************/
static Status
receive_smb(struct smb_conn *conn, int timeout)
{
  AssertConn(conn);
  Assert(conn->max_xmit >= 4);

  if (receive_1002(conn->server, conn->buffer, conn->max_xmit, timeout))
    return Fail;
  
  Assert(smb_is_valid_buf(conn->buffer));

  return Ok;
}


/*****************************************************************************/
/*                                                                           */
/*  name_mangle                                                              */
/*                                                                           */
/*****************************************************************************/
static int
name_mangle(const char *In,char *Out)
{
  unsigned const char *in  = (unsigned const char *)In;
  unsigned       char *out = (unsigned       char *)Out;
  int len;
  int pad = 0;
  unsigned char c;

  AssertPtr(in);
  AssertPtr(out);

  len = 2*strlen(In);
  if (len/2 < 16)
    pad = 16 - (len/2);

  *out++ = 2*(strlen(In) + pad);
  while (*in)
    {
      out[0] = (in[0]>>4) + 'A';
      out[1] = (in[0] & 0xF) + 'A';
      in++;
      out+=2;
    }
  
  while (pad--)
    {
      out[0] = 'C';
      out[1] = 'A';
      out+=2;
    }
  
  *out = 0;

  c = *(unsigned char *)Out;

  if ((c & 0xC0) == 0xC0)
    return(2);
  
  return(strlen(Out) + 1);
}

/*****************************************************************************/
/*                                                                           */
/*  new_conn                                                                 */
/*                                                                           */
/*****************************************************************************/
static struct smb_conn *
new_conn(int bufsize)
{
  struct smb_conn *result;
  result = cmalloc(sizeof(struct smb_conn) + bufsize);
  AssertPtr(result);
  if (result) {
    result->max_xmit = bufsize;
  } else { PathTest;
    ErrLog(ERR_PANIC, "could not alloc a connection\n");
  }
  return result;
}

/*****************************************************************************/
/*                                                                           */
/*  fill_finfo                                                               */
/*                                                                           */
/*****************************************************************************/
static void
fill_finfo(const char *p, struct smb_finfo *finfo)
{
  AssertPtr(p);
  AssertPtr(finfo);
  
  MemGarbage(finfo, sizeof(*finfo));

  finfo->attrib = BVAL(p, 21);
  finfo->size   = WVAL(p, 26) + (WVAL(p, 28) << 16);

  finfo->mtime = make_unix_time((unsigned char *)(p+22));
  

  if (strlen(p+30) < SMB_NAMELEN) {
    strcpy(finfo->name, p+30);
  } else {
    memcpy(finfo->name, p+30, SMB_NAMELEN-1);
    (finfo->name)[SMB_NAMELEN] = 0;
  }  

  strlower(finfo->name);
  
  return;
}

/*****************************************************************************/
/*                                                                           */
/*  make_unix_time                                                           */
/*                                                                           */
/*****************************************************************************/
static time_t
make_unix_time(unsigned char *time)
{
  struct tm t;

  t.tm_sec  = 2*(time[0] & 0x1f);
  t.tm_min  = (time[0]>>5) + ((time[1]&0x7) << 3);
  t.tm_hour = (time[1]>>3);
  t.tm_mday = time[2]&0x1f;
  t.tm_mon  = (time[2]>>5) + ((time[3]&0x1)<<3) - 1;
  t.tm_year = (time[3]>>1) + 80;
  t.tm_wday = 1;
  t.tm_yday = 1;
  t.tm_isdst= 0;

  return mktime(&t);
}

/*****************************************************************************/
/*                                                                           */
/*  dos_base_name                                                            */
/*                                                                           */
/*****************************************************************************/
static const char *
dos_base_name(const char *filename)
{
  char *result = strrchr(filename, '\\');
  return result ? (result+1) : filename;
}

/*****************************************************************************/
/*                                                                           */
/*  _AssertConn                                                              */
/*                                                                           */
/*****************************************************************************/
#if DEBUG > 0
void
_AssertConn(const char *f, int l, struct smb_conn *conn)
{
  _AssertPtr(f,l,conn);
  if (!conn) return;

#if DEBUG > 1
  if (   (conn->pid != getpid())
      || (conn->uid != getuid())
      || (conn->gid != getgid())
      || (conn->mid != conn->pid + 20)) {
    AssertPrintf(f,l, "Conn values inconsistent\n");
    AssertFailed(f,l);
  }
#endif /* DEBUG > 1 */
}
#endif /* DEBUG > 0 */

/*****************************************************************************/
/*                                                                           */
/*  AssertConnSpace                                                          */
/*                                                                           */
/*****************************************************************************/
#if DEBUG > 0
void _AssertConnSpace(const char *f, int l,
                      struct smb_conn *conn,
                      const char *p,
                      int length)
{
  _AssertPtr(f,l,conn);
  _AssertPtr(f,l,p);

  if (   (p >= conn->buffer)
      && (conn->buffer + conn->max_xmit < p + length)) {
    AssertPrintf(f,l, "Not enough space in conn-buffer for:\n%s\n", p);
    AssertFailed(f,l);
  }
}
#endif /* DEBUG > 0 */

