Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "cifs.h"

static char magic[] = { 0xff, 'S', 'M', 'B' };

Session *
cifsdial(char *host, char *called, char *sysname)
{
        int nbt, fd;
        char *addr;
        Session *s;

        if(Debug)
                fprint(2, "cifsdial: host=%s called=%s sysname=%s\n", host, called, sysname);

        if((addr = netmkaddr(host, "tcp", "cifs")) == nil)
                return nil;

        nbt = 0;
        if((fd = dial(addr, nil, nil, nil)) == -1){
                nbt = 1;
                if((fd = nbtdial(host, called, sysname)) == -1)
                        return nil;
        }

        s = emalloc9p(sizeof(Session));
        memset(s, 0, sizeof(Session));

        s->fd = fd;
        s->nbt = nbt;
        s->mtu = MTU;
        s->pid = getpid();
        s->mid = time(nil) ^ getpid();
        s->uid = NO_UID;
        s->seq = 0;
        s->seqrun = 0;
        s->secmode = SECMODE_SIGN_ENABLED;      /* hope for the best */
        s->flags2 = FL2_KNOWS_LONG_NAMES | FL2_HAS_LONG_NAMES | FL2_PAGEING_IO;
        s->macidx = -1;

        return s;
}

void
cifsclose(Session *s)
{
        if(s->fd)
                close(s->fd);
        free(s);
}

Pkt *
cifshdr(Session *s, Share *sp, int cmd)
{
        Pkt *p;
        int sign, tid, dfs;

        dfs = 0;
        tid = NO_TID;
        Active = IDLE_TIME;
        werrstr("");
        sign = s->secmode & SECMODE_SIGN_ENABLED? FL2_PACKET_SIGNATURES: 0;

        if(sp){
                tid = sp->tid;
// FIXME!               if(sp->options & SMB_SHARE_IS_IN_DFS)
// FIXME!                       dfs = FL2_DFS;
        }

        p = emalloc9p(sizeof(Pkt) + MTU);
        memset(p, 0, sizeof(Pkt) +MTU);

        p->buf = (uchar *)p + sizeof(Pkt);
        p->s = s;

        qlock(&s->seqlock);
        if(s->seqrun){
                p->seq = s->seq;
                s->seq = (s->seq + 2) % 0x10000;
        }
        qunlock(&s->seqlock);

        nbthdr(p);
        pmem(p, magic, nelem(magic));
        p8(p, cmd);
        pl32(p, 0);                             /* status (error) */
        p8(p, FL_CASELESS_NAMES | FL_CANNONICAL_NAMES); /* flags */
        pl16(p, s->flags2 | dfs | sign);        /* flags2 */
        pl16(p, (s->pid >> 16) & 0xffff);       /* PID MS bits */
        pl32(p, p->seq);                        /* MAC / sequence number */
        pl32(p, 0);                             /* MAC */
        pl16(p, 0);                             /* padding */

        pl16(p, tid);
        pl16(p, s->pid & 0xffff);
        pl16(p, s->uid);
        pl16(p, s->mid);

        p->wordbase = p8(p, 0);         /* filled in by pbytes() */

        return p;
}

void
pbytes(Pkt *p)
{
        int n;

        assert(p->wordbase != nil);     /* cifshdr not called */
        assert(p->bytebase == nil);     /* called twice */

        n = p->pos - p->wordbase;
        assert(n % 2 != 0);             /* even addr */
        *p->wordbase = n / 2;

        p->bytebase = pl16(p, 0);       /* filled in by cifsrpc() */
}

static void
dmp(int seq, uchar *buf)
{
        int i;

        if(seq == 99)
                print("\n   ");
        else
                print("%+2d ", seq);
        for(i = 0; i < 8; i++)
                print("%02x ", buf[i] & 0xff);
        print("\n");
}

int
cifsrpc(Pkt *p)
{
        int flags2, got, err;
        uint tid, uid, seq;
        uchar *pos;
        char m[nelem(magic)];

        pos = p->pos;
        if(p->bytebase){
                p->pos = p->bytebase;
                pl16(p, pos - (p->bytebase + 2)); /* 2 = sizeof bytecount */
        }
        p->pos = pos;

        if(p->s->secmode & SECMODE_SIGN_ENABLED)
                macsign(p, p->seq);

        qlock(&p->s->rpclock);
        got = nbtrpc(p);
        qunlock(&p->s->rpclock);
        if(got == -1)
                return -1;

        gmem(p, m, nelem(magic));
        if(memcmp(m, magic, nelem(magic)) != 0){
                werrstr("cifsrpc: bad magic number in packet %20ux%02ux%02ux%02ux",
                        m[0], m[1], m[2], m[3]);
                return -1;
        }

        g8(p);                          /* cmd */
        err = gl32(p);                  /* errcode */
        g8(p);                          /* flags */
        flags2 = gl16(p);               /* flags2 */
        gl16(p);                        /* PID MS bits */
        seq = gl32(p);                  /* reserved */
        gl32(p);                        /* MAC (if in use) */
        gl16(p);                        /* Padding */
        tid = gl16(p);                  /* TID */
        gl16(p);                        /* PID lsbs */
        uid = gl16(p);                  /* UID */
        gl16(p);                        /* mid */
        g8(p);                          /* word count */

        if(p->s->secmode & SECMODE_SIGN_ENABLED){
                if(macsign(p, p->seq+1) != 0 && p->s->seqrun){
                        werrstr("cifsrpc: invalid packet signature");
print("MAC signature bad\n");
// FIXME: for debug only                        return -1;
                }
        }else{
                /*
                 * We allow the sequence number of zero as some old samba
                 * servers seem to fall back to this unexpectedly
                 * after reporting sequence numbers correctly for a while.
                 *
                 * Some other samba servers seem to always report a sequence
                 * number of zero if MAC signing is disabled, so we have to
                 * catch that too.
                 */
                if(p->s->seqrun && seq != p->seq && seq != 0){
                        print("%ux != %ux bad sequence number\n", seq, p->seq);
                        return -1;
                }
        }

        p->tid = tid;
        if(p->s->uid == NO_UID)
                p->s->uid = uid;

        if(flags2 & FL2_NT_ERRCODES){
                /* is it a real error rather than info/warning/chatter? */
                if((err & 0xF0000000) == 0xC0000000){
                        werrstr("%s", nterrstr(err));
                        return -1;
                }
        }else{
                if(err){
                        werrstr("%s", doserrstr(err));
                        return -1;
                }
        }
        return got;
}


/*
 * Some older servers (old samba) prefer to talk older
 * dialects but if given no choice they will talk the
 * more modern ones, so we don't give them the choice.
 */
int
CIFSnegotiate(Session *s, long *svrtime, char *domain, int domlen, char *cname,
        int cnamlen)
{
        int d, i;
        char *ispeak = "NT LM 0.12";
        static char *dialects[] = {
//              { "PC NETWORK PROGRAM 1.0"},
//              { "MICROSOFT NETWORKS 1.03"},
//              { "MICROSOFT NETWORKS 3.0"},
//              { "LANMAN1.0"},
//              { "LM1.2X002"},
//              { "NT LANMAN 1.0"},
                { "NT LM 0.12" },
        };
        Pkt *p;

        p = cifshdr(s, nil, SMB_COM_NEGOTIATE);
        pbytes(p);
        for(i = 0; i < nelem(dialects); i++){
                p8(p, STR_DIALECT);
                pstr(p, dialects[i]);
        }

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }

        d = gl16(p);
        if(d < 0 || d > nelem(dialects)){
                werrstr("no CIFS dialect in common");
                free(p);
                return -1;
        }

        if(strcmp(dialects[d], ispeak) != 0){
                werrstr("%s dialect unsupported", dialects[d]);
                free(p);
                return -1;
        }

        s->secmode = g8(p);                     /* Security mode */

        gl16(p);                                /* Max outstanding requests */
        gl16(p);                                /* Max VCs */
        s->mtu = gl32(p);                       /* Max buffer size */
        gl32(p);                                /* Max raw buffer size (depricated) */
        gl32(p);                                /* Session key */
        s->caps = gl32(p);                      /* Server capabilities */
        *svrtime = gvtime(p);                   /* fileserver time */
        s->tz = (short)gl16(p) * 60; /* TZ in mins, is signed (SNIA doc is wrong) */
        s->challen = g8(p);                     /* Encryption key length */
        gl16(p);
        gmem(p, s->chal, s->challen);           /* Get the challenge */
        gstr(p, domain, domlen);                /* source domain */

        {               /* NetApp Filer seem not to report its called name */
                char *cn = emalloc9p(cnamlen);

                gstr(p, cn, cnamlen);           /* their name */
                if(strlen(cn) > 0)
                        memcpy(cname, cn, cnamlen);
                free(cn);
        }

        if(s->caps & CAP_UNICODE)
                s->flags2 |= FL2_UNICODE;

        free(p);
        return 0;
}

int
CIFSsession(Session *s)
{
        char os[64], *q;
        Rune r;
        Pkt *p;
        enum {
                mycaps = CAP_UNICODE | CAP_LARGE_FILES | CAP_NT_SMBS |
                        CAP_NT_FIND | CAP_STATUS32,
        };

        s->seqrun = 1;  /* activate the sequence number generation/checking */

        p = cifshdr(s, nil, SMB_COM_SESSION_SETUP_ANDX);
        p8(p, 0xFF);                    /* No secondary command */
        p8(p, 0);                       /* Reserved (must be zero) */
        pl16(p, 0);                     /* Offset to next command */
        pl16(p, MTU);                   /* my max buffer size */
        pl16(p, 1);                     /* my max multiplexed pending requests */
        pl16(p, 0);                     /* Virtual connection # */
        pl32(p, 0);                     /* Session key (if vc != 0) */


        if((s->secmode & SECMODE_PW_ENCRYPT) == 0) {
                pl16(p, utflen(Sess->auth->resp[0])*2 + 2); /* passwd size */
                pl16(p, utflen(Sess->auth->resp[0])*2 + 2); /* passwd size (UPPER CASE) */
                pl32(p, 0);                     /* Reserved */
                pl32(p, mycaps);
                pbytes(p);

                for(q = Sess->auth->resp[0]; *q; ){
                        q += chartorune(&r, q);
                        pl16(p, toupperrune(r));
                }
                pl16(p, 0);

                for(q = Sess->auth->resp[0]; *q; ){
                        q += chartorune(&r, q);
                        pl16(p, r);
                }
                pl16(p, 0);
        }else{
                pl16(p, Sess->auth->len[0]);    /* LM passwd size */
                pl16(p, Sess->auth->len[1]);    /* NTLM passwd size */
                pl32(p, 0);                     /* Reserved  */
                pl32(p, mycaps);
                pbytes(p);

                pmem(p, Sess->auth->resp[0], Sess->auth->len[0]);
                pmem(p, Sess->auth->resp[1], Sess->auth->len[1]);
        }

        pstr(p, Sess->auth->user);      /* Account name */
        pstr(p, Sess->auth->windom);    /* Primary domain */
        pstr(p, "plan9");               /* Client OS */
        pstr(p, argv0);                 /* Client LAN Manager type */

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }

        g8(p);                          /* Reserved (0) */
        gl16(p);                        /* Offset to next command wordcount */
        Sess->isguest = gl16(p) & 1;    /* logged in as guest */

        gl16(p);
        gl16(p);
        /* no security blob here - we don't understand extended security anyway */
        gstr(p, os, sizeof(os));
        s->remos = estrdup9p(os);

        free(p);
        return 0;
}


CIFStreeconnect(Session *s, char *cname, char *tree, Share *sp)
{
        int len;
        char *resp, *path;
        char zeros[24];
        Pkt *p;

        resp = Sess->auth->resp[0];
        len  = Sess->auth->len[0];
        if((s->secmode & SECMODE_USER) != SECMODE_USER){
                memset(zeros, 0, sizeof(zeros));
                resp = zeros;
                len = sizeof(zeros);
        }

        p = cifshdr(s, nil, SMB_COM_TREE_CONNECT_ANDX);
        p8(p, 0xFF);                    /* Secondary command */
        p8(p, 0);                       /* Reserved */
        pl16(p, 0);                     /* Offset to next Word Count */
        pl16(p, 0);                     /* Flags */

        if((s->secmode & SECMODE_PW_ENCRYPT) == 0){
                pl16(p, len+1);         /* password len, including null */
                pbytes(p);
                pascii(p, resp);
        }else{
                pl16(p, len);
                pbytes(p);
                pmem(p, resp, len);
        }

        path = smprint("//%s/%s", cname, tree);
        strupr(path);
        ppath(p, path);                 /* path */
        free(path);

        pascii(p, "?????");     /* service type any (so we can do RAP calls) */

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }
        g8(p);                          /* Secondary command */
        g8(p);                          /* Reserved */
        gl16(p);                        /* Offset to next command */
        sp->options = g8(p);            /* options supported */
        sp->tid = p->tid;               /* get received TID from packet header */
        free(p);
        return 0;
}

int
CIFSlogoff(Session *s)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, nil, SMB_COM_LOGOFF_ANDX);
        p8(p, 0xFF);                    /* No ANDX command */
        p8(p, 0);                       /* Reserved (must be zero) */
        pl16(p, 0);                     /* offset ot ANDX */
        pbytes(p);
        rc = cifsrpc(p);

        free(p);
        return rc;
}

int
CIFStreedisconnect(Session *s, Share *sp)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_TREE_DISCONNECT);
        pbytes(p);
        rc = cifsrpc(p);

        free(p);
        return rc;
}


int
CIFSdeletefile(Session *s, Share *sp, char *name)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_DELETE);
        pl16(p, ATTR_HIDDEN|ATTR_SYSTEM);       /* search attributes */
        pbytes(p);
        p8(p, STR_ASCII);                       /* buffer format */
        ppath(p, name);
        rc = cifsrpc(p);

        free(p);
        return rc;
}

int
CIFSdeletedirectory(Session *s, Share *sp, char *name)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_DELETE_DIRECTORY);
        pbytes(p);
        p8(p, STR_ASCII);               /* buffer format */
        ppath(p, name);
        rc = cifsrpc(p);

        free(p);
        return rc;
}

int
CIFScreatedirectory(Session *s, Share *sp, char *name)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_CREATE_DIRECTORY);
        pbytes(p);
        p8(p, STR_ASCII);
        ppath(p, name);
        rc = cifsrpc(p);

        free(p);
        return rc;
}

int
CIFSrename(Session *s, Share *sp, char *old, char *new)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_RENAME);
        pl16(p, ATTR_HIDDEN|ATTR_SYSTEM|ATTR_DIRECTORY); /* search attributes */
        pbytes(p);
        p8(p, STR_ASCII);
        ppath(p, old);
        p8(p, STR_ASCII);
        ppath(p, new);
        rc = cifsrpc(p);

        free(p);
        return rc;
}


/* for NT4/Win2k/XP */
int
CIFS_NT_opencreate(Session *s, Share *sp, char *name, int flags, int options,
        int attrs, int access, int share, int action, int *result, FInfo *fi)
{
        Pkt *p;
        int fh;

        p = cifshdr(s, sp, SMB_COM_NT_CREATE_ANDX);
        p8(p, 0xFF);                    /* Secondary command */
        p8(p, 0);                       /* Reserved */
        pl16(p, 0);                     /* Offset to next command */
        p8(p, 0);                       /* Reserved */
        pl16(p, utflen(name) *2);       /* file name len */
        pl32(p, flags);                 /* Flags */
        pl32(p, 0);                     /* fid of cwd, if relative path */
        pl32(p, access);                /* access desired */
        pl64(p, 0);                     /* initial allocation size */
        pl32(p, attrs);                 /* Extended attributes */
        pl32(p, share);                 /* Share Access */
        pl32(p, action);                /* What to do on success/failure */
        pl32(p, options);               /* Options */
        pl32(p, SECURITY_IMPERSONATION); /* Impersonation level */
        p8(p, SECURITY_CONTEXT_TRACKING | SECURITY_EFFECTIVE_ONLY); /* security flags */
        pbytes(p);
        p8(p, 0);                       /* FIXME: padding? */
        ppath(p, name);                 /* filename */

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }

        memset(fi, 0, sizeof(FInfo));
        g8(p);                          /* Secondary command */
        g8(p);                          /* Reserved */
        gl16(p);                        /* Offset to next command */
        g8(p);                          /* oplock granted */
        fh = gl16(p);                   /* FID for opened object */
        *result = gl32(p);              /* create action taken */
        gl64(p);                        /* creation time */
        fi->accessed = gvtime(p);       /* last access time */
        fi->written = gvtime(p);        /* last written time */
        fi->changed = gvtime(p);        /* change time */
        fi->attribs = gl32(p);          /* extended attributes */
        gl64(p);                        /* bytes allocated */
        fi->size = gl64(p);             /* file size */

        free(p);
        return fh;
}

/* for Win95/98/ME */
CIFS_SMB_opencreate(Session *s, Share *sp, char *name, int access,
        int attrs, int action, int *result)
{
        Pkt *p;
        int fh;

        p = cifshdr(s, sp, SMB_COM_OPEN_ANDX);
        p8(p, 0xFF);                    /* Secondary command */
        p8(p, 0);                       /* Reserved */
        pl16(p, 0);                     /* Offset to next command */
        pl16(p, 0);                     /* Flags (0 == no stat(2) info) */
        pl16(p, access);                /* desired access */
        pl16(p, ATTR_HIDDEN|ATTR_SYSTEM);/* search attributes */
        pl16(p, attrs);                 /* file attribytes */
        pdatetime(p, 0);                /* creation time (0 == now) */
        pl16(p, action);                /* What to do on success/failure */
        pl32(p, 0);                     /* allocation size */
        pl32(p, 0);                     /* reserved */
        pl32(p, 0);                     /* reserved */
        pbytes(p);
        ppath(p, name);                 /* filename */

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }

        g8(p);                          /* Secondary command */
        g8(p);                          /* Reserved */
        gl16(p);                        /* Offset to next command */
        fh = gl16(p);                   /* FID for opened object */
        gl16(p);                        /* extended attributes */
        gvtime(p);                      /* last written time */
        gl32(p);                        /* file size */
        gl16(p);                        /* file type (disk/fifo/printer etc) */
        gl16(p);                        /* device status (for fifos) */
        *result = gl16(p);              /* access granted */

        free(p);
        return fh;
}

vlong
CIFSwrite(Session *s, Share *sp, int fh, uvlong off, void *buf, vlong n)
{
        Pkt *p;
        vlong got;

        /* FIXME: Payload should be padded to long boundary */
        assert((n   & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
        assert((off & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
        assert(n < s->mtu - T2HDRLEN || s->caps & CAP_LARGE_WRITEX);

        p = cifshdr(s, sp, SMB_COM_WRITE_ANDX);
        p8(p, 0xFF);                    /* Secondary command */
        p8(p, 0);                       /* Reserved */
        pl16(p, 0);                     /* Offset to next command */
        pl16(p, fh);                    /* File handle */
        pl32(p, off & 0xffffffff);      /* LSBs of Offset */
        pl32(p, 0);                     /* Reserved (0) */
        pl16(p, s->nocache);            /* Write mode (0 - write through) */
        pl16(p, 0);                     /* Bytes remaining */
        pl16(p, n >> 16);               /* MSBs of length */
        pl16(p, n & 0xffffffff);        /* LSBs of length */
        pl16(p, T2HDRLEN);              /* Offset to data, in bytes */
        pl32(p, off >> 32);             /* MSBs of offset */
        pbytes(p);

        p->pos = p->buf +T2HDRLEN +NBHDRLEN;
        pmem(p, buf, n);                /* Data */

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }

        g8(p);                          /* Secondary command */
        g8(p);                          /* Reserved */
        gl16(p);                        /* Offset to next command */
        got = gl16(p);                  /* LSWs of bytes written */
        gl16(p);                        /* remaining (space ?) */
        got |= (gl16(p) << 16);         /* MSWs of bytes written */

        free(p);
        return got;
}

vlong
CIFSread(Session *s, Share *sp, int fh, uvlong off, void *buf, vlong n,
        vlong minlen)
{
        int doff;
        vlong got;
        Pkt *p;

        assert((n   & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
        assert((off & 0xffffffff00000000LL) == 0 || s->caps & CAP_LARGE_FILES);
        assert(n < s->mtu - T2HDRLEN || s->caps & CAP_LARGE_READX);

        p = cifshdr(s, sp, SMB_COM_READ_ANDX);
        p8(p, 0xFF);                    /* Secondary command */
        p8(p, 0);                       /* Reserved */
        pl16(p, 0);                     /* Offset to next command */
        pl16(p, fh);                    /* File handle */
        pl32(p, off & 0xffffffff);      /* Offset to beginning of write */
        pl16(p, n);                     /* Maximum number of bytes to return */
        pl16(p, minlen);                /* Minimum number of bytes to return */
        pl32(p, (uint)n >> 16);         /* MSBs of maxlen */
        pl16(p, 0);                     /* Bytes remaining to satisfy request */
        pl32(p, off >> 32);             /* MS 32 bits of offset */
        pbytes(p);

        if(cifsrpc(p) == -1){
                free(p);
                return -1;
        }

        g8(p);                          /* Secondary command */
        g8(p);                          /* Reserved */
        gl16(p);                        /* Offset to next command */
        gl16(p);                        /* Remaining */
        gl16(p);                        /* Compression mode */
        gl16(p);                        /* Reserved */
        got = gl16(p);                  /* length */
        doff = gl16(p);                 /* Offset from header to data */
        got |= gl16(p) << 16;

        p->pos = p->buf + doff + NBHDRLEN;

        gmem(p, buf, got);               /* data */
        free(p);
        return got;
}

int
CIFSflush(Session *s, Share *sp, int fh)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_FLUSH);
        pl16(p, fh);                    /* fid */
        pbytes(p);
        rc = cifsrpc(p);

        free(p);
        return rc;
}

/*
 * Setting the time of last write to -1 gives "now" if the file
 * was written and leaves it the same if the file wasn't written.
 */
int
CIFSclose(Session *s, Share *sp, int fh)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_CLOSE);
        pl16(p, fh);                    /* fid */
        pl32(p, ~0L);                   /* Time of last write (none) */
        pbytes(p);
        rc = cifsrpc(p);

        free(p);
        return rc;
}


int
CIFSfindclose2(Session *s, Share *sp, int sh)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_FIND_CLOSE2);
        pl16(p, sh);                    /* sid */
        pbytes(p);
        rc = cifsrpc(p);

        free(p);
        return rc;
}


int
CIFSecho(Session *s)
{
        Pkt *p;
        int rc;

        p = cifshdr(s, nil, SMB_COM_ECHO);
        pl16(p, 1);                             /* number of replies */
        pbytes(p);
        pascii(p, "abcdefghijklmnopqrstuvwxyz"); /* data */

        rc = cifsrpc(p);
        free(p);
        return rc;
}


int
CIFSsetinfo(Session *s, Share *sp, char *path, FInfo *fip)
{
        int rc;
        Pkt *p;

        p = cifshdr(s, sp, SMB_COM_SET_INFORMATION);
        pl16(p, fip->attribs);
        pl32(p, time(nil) - s->tz);     /* modified time */
        pl64(p, 0);                     /* reserved */
        pl16(p, 0);                     /* reserved */

        pbytes(p);
        p8(p, STR_ASCII);               /* buffer format */
        ppath(p, path);

        rc = cifsrpc(p);
        free(p);
        return rc;
}