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

static Pkt *
thdr(Session *s, Share *sp)
{
       Pkt *p;

       p = cifshdr(s, sp, SMB_COM_TRANSACTION);
       p->tbase = pl16(p, 0);  /* 0  Total parameter bytes to be sent, filled later */
       pl16(p, 0);             /* 2  Total data bytes to be sent, filled later */
       pl16(p, 64);                    /* 4  Max parameter to return */
       pl16(p, s->mtu - T2HDRLEN - 128);       /* 6  Max data to return */
       pl16(p, 1);                     /* 8  Max setup count to return */
       pl16(p, 0);                     /* 10 Flags */
       pl32(p, 1000);                  /* 12 Timeout (ms) */
       pl16(p, 0);                     /* 16 Reserved */
       pl16(p, 0);                     /* 18 Parameter count, filled later */
       pl16(p, 0);                     /* 20 Parameter offset, filled later */
       pl16(p, 0);                     /* 22 Data count, filled later */
       pl16(p, 0);                     /* 24 Data offset, filled later */
       pl16(p, 0);                     /* 26 Setup count (in words) */
       pbytes(p);                      /* end of cifs words section */
       return p;
}

static void
ptparam(Pkt *p)
{
       uchar *pos;

       if(((p->pos - p->tbase) % 2) != 0)
               p8(p, 0);                       /* pad to word boundry */
       pos = p->pos;
       p->pos = p->tbase + 20;
       pl16(p, pos - p->buf - NBHDRLEN);       /* param offset */
       p->tparam = p->pos = pos;
}

static void
ptdata(Pkt *p)
{
       uchar *pos = p->pos;

       assert(p->tparam != 0);
       if(((p->pos - p->tbase) % 2) != 0)
               p8(p, 0);               /* pad to word boundry */

       p->pos = p->tbase + 0;
       pl16(p, pos - p->tparam);       /* total param count */

       p->pos = p->tbase + 18;
       pl16(p, pos - p->tparam);       /* param count */

       p->pos = p->tbase + 24;
       pl16(p, pos - p->buf - NBHDRLEN); /* data offset */

       p->tdata = p->pos = pos;
}

static int
trpc(Pkt *p)
{
       int got;
       uchar *pos = p->pos;

       assert(p->tbase != 0);
       assert(p->tdata != 0);

       p->pos = p->tbase + 2;
       pl16(p, pos - p->tdata);        /* total data count */

       p->pos = p->tbase + 22;
       pl16(p, pos - p->tdata);        /* data count */

       p->pos = pos;
       if((got = cifsrpc(p)) == -1)
               return -1;

       gl16(p);                        /* Total parameter count */
       gl16(p);                        /* Total data count */
       gl16(p);                        /* Reserved */
       gl16(p);                        /* Parameter count in this buffer */
       p->tparam = p->buf + NBHDRLEN + gl16(p); /* Parameter offset */
       gl16(p);                        /* Parameter displacement */
       gl16(p);                        /* Data count (this buffer); */
       p->tdata = p->buf + NBHDRLEN + gl16(p); /* Data offset */
       gl16(p);                        /* Data displacement */
       g8(p);                          /* Setup count */
       g8(p);                          /* Reserved */
       return got;
}

static void
gtparam(Pkt *p)
{
       p->pos = p->tparam;
}

static void
gtdata(Pkt *p)
{
       p->pos = p->tdata;
}


int
RAPshareenum(Session *s, Share *sp, Share **ent)
{
       int ngot = 0, err, navail, nret;
       char tmp[1024];
       Pkt *p;
       Share *q;

       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WShareEnum);
       pascii(p, REMSmb_NetShareEnum_P);       /* request descriptor */
       pascii(p, REMSmb_share_info_0);         /* reply descriptor */
       pl16(p, 0);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       gl16(p);                                /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       if(ngot == 0){
               *ent = emalloc9p(sizeof(Share) * navail);
               memset(*ent, 0, sizeof(Share) * navail);
       }

       q = *ent + ngot;
       for (; ngot < navail && nret--; ngot++){
               gmem(p, tmp, 13);               /* name */
               tmp[13] = 0;
               q->name = estrdup9p(tmp);
               q++;
       }

       if(ngot < navail)
               fprint(2, "%s: %d/%d - share list incomplete\n", argv0, ngot, navail);

       free(p);
       return ngot;
}


int
RAPshareinfo(Session *s, Share *sp, char *share, Shareinfo2 *si2p)
{
       int conv, err;
       char tmp[1024];
       Pkt *p;

       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");

       ptparam(p);
       pl16(p, API_WShareGetInfo);
       pascii(p, REMSmb_NetShareGetInfo_P);    /* request descriptor */
       pascii(p, REMSmb_share_info_2);         /* reply descriptor */
       pascii(p, share);
       pl16(p, 1);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */

       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       conv = gl16(p);                         /* rx buffer offset */
       gl16(p);                                /* number of entries returned */
       gl16(p);                                /* number of entries available */

       if(err){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       memset(si2p, 0, sizeof(Shareinfo2));

       gmem(p, tmp, 13);
       tmp[13] = 0;
       g8(p);                                  /* padding */
       si2p->name = estrdup9p(tmp);
       si2p->type = gl16(p);
       gconv(p, conv, tmp, sizeof tmp);
       si2p->comment = estrdup9p(tmp);
       gl16(p);                                /* comment offset high (unused) */
       si2p->perms = gl16(p);
       si2p->maxusrs = gl16(p);
       si2p->activeusrs = gl16(p);
       gconv(p, conv, tmp, sizeof tmp);
       si2p->path = estrdup9p(tmp);
       gl16(p);                                /* path offset high (unused) */
       gmem(p, tmp, 9);
       tmp[9] = 0;
       si2p->passwd = estrdup9p(tmp);

       free(p);
       return 0;
}

/*
* Tried to split sessionenum into two passes, one getting the names
* of the connected workstations and the other collecting the detailed info,
* however API_WSessionGetInfo doesn't seem to work agains win2k3 for infolevel
* ten and infolevel one and two are priviledged calls.  This means this code
* will work for small numbers of sessions agains win2k3 and fail for samba 3.0
* as it supports info levels zero and two only.
*/
int
RAPsessionenum(Session *s, Share *sp, Sessinfo **sip)
{
       int ngot = 0, conv, err, navail, nret;
       char tmp[1024];
       Pkt *p;
       Sessinfo *q;

       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WSessionEnum);
       pascii(p, REMSmb_NetSessionEnum_P);     /* request descriptor */
       pascii(p, REMSmb_session_info_10);      /* reply descriptor */
       pl16(p, 10);                            /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       conv = gl16(p);                         /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       if(ngot == 0){
               *sip = emalloc9p(sizeof(Sessinfo) * navail);
               memset(*sip, 0, sizeof(Sessinfo) * navail);
       }

       q = *sip + ngot;
       while(nret-- != 0){
               gconv(p, conv, tmp, sizeof tmp);
               q->wrkstn = estrdup9p(tmp);
               gconv(p, conv, tmp, sizeof tmp);
               q->user = estrdup9p(tmp);
               q->sesstime = gl32(p);
               q->idletime = gl32(p);
               ngot++;
               q++;
       }
//      if(ngot < navail)
//              fprint(2, "warning: %d/%d - session list incomplete\n", ngot, navail);
       free(p);
       return ngot;
}


int
RAPgroupenum(Session *s, Share *sp, Namelist **nlp)
{
       int ngot, err, navail, nret;
       char tmp[1024];
       Pkt *p;
       Namelist *q;

       ngot = 0;
       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WGroupEnum);
       pascii(p, REMSmb_NetGroupEnum_P);       /* request descriptor */
       pascii(p, REMSmb_group_info_0);         /* reply descriptor */
       pl16(p, 0);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       gl16(p);                                /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       *nlp = emalloc9p(sizeof(Namelist) * navail);
       memset(*nlp, 0, sizeof(Namelist) * navail);

       q = *nlp + ngot;
       while(ngot < navail && nret--){
               gmem(p, tmp, 21);
               tmp[21] = 0;
               q->name = estrdup9p(tmp);
               ngot++;
               q++;
               if(p->pos >= p->eop)            /* Windows seems to lie somtimes */
                       break;
       }
       free(p);
       return ngot;
}


int
RAPgroupusers(Session *s, Share *sp, char *group, Namelist **nlp)
{
       int ngot, err, navail, nret;
       char tmp[1024];
       Pkt *p;
       Namelist *q;

       ngot = 0;
       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WGroupGetUsers);
       pascii(p, REMSmb_NetGroupGetUsers_P);   /* request descriptor */
       pascii(p, REMSmb_user_info_0);          /* reply descriptor */
       pascii(p, group);                       /* group name for list */
       pl16(p, 0);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       gl16(p);                                /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       *nlp = emalloc9p(sizeof(Namelist) * navail);
       memset(*nlp, 0, sizeof(Namelist) * navail);

       q = *nlp + ngot;
       while(ngot < navail && nret--){
               gmem(p, tmp, 21);
               tmp[21] = 0;
               q->name = estrdup9p(tmp);
               ngot++;
               q++;
               if(p->pos >= p->eop)            /* Windows seems to lie somtimes */
                       break;
       }
       free(p);
       return ngot;
}

int
RAPuserenum(Session *s, Share *sp, Namelist **nlp)
{
       int ngot, err, navail, nret;
       char tmp[1024];
       Pkt *p;
       Namelist *q;

       ngot = 0;
       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WUserEnum);
       pascii(p, REMSmb_NetUserEnum_P);        /* request descriptor */
       pascii(p, REMSmb_user_info_0);          /* reply descriptor */
       pl16(p, 0);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       gl16(p);                                /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       *nlp = emalloc9p(sizeof(Namelist) * navail);
       memset(*nlp, 0, sizeof(Namelist) * navail);

       q = *nlp + ngot;
       while(ngot < navail && nret--){
               gmem(p, tmp, 21);
               tmp[21] = 0;
               q->name = estrdup9p(tmp);
               ngot++;
               q++;
               if(p->pos >= p->eop)            /* Windows seems to lie somtimes */
                       break;
       }
       free(p);
       return ngot;
}

int
RAPuserenum2(Session *s, Share *sp, Namelist **nlp)
{
       int ngot, resume, err, navail, nret;
       char tmp[1024];
       Pkt *p;
       Namelist *q;

       ngot = 0;
       resume = 0;
more:
       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WUserEnum2);
       pascii(p, REMSmb_NetUserEnum2_P);       /* request descriptor */
       pascii(p, REMSmb_user_info_0);          /* reply descriptor */
       pl16(p, 0);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       pl32(p, resume);                        /* resume key to allow multiple fetches */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       gl16(p);                                /* rx buffer offset */
       resume = gl32(p);                       /* resume key returned */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       if(ngot == 0){
               *nlp = emalloc9p(sizeof(Namelist) * navail);
               memset(*nlp, 0, sizeof(Namelist) * navail);
       }
       q = *nlp + ngot;
       while(ngot < navail && nret--){
               gmem(p, tmp, 21);
               tmp[21] = 0;
               q->name = estrdup9p(tmp);
               ngot++;
               q++;
               if(p->pos >= p->eop)            /* Windows seems to lie somtimes */
                       break;
       }
       free(p);
       if(ngot < navail)
               goto more;
       return ngot;
}

int
RAPuserinfo(Session *s, Share *sp, char *user, Userinfo *uip)
{
       int conv, err;
       char tmp[1024];
       Pkt *p;

       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");
       ptparam(p);

       pl16(p, API_WUserGetInfo);
       pascii(p, REMSmb_NetUserGetInfo_P);     /* request descriptor */
       pascii(p, REMSmb_user_info_10);         /* reply descriptor */
       pascii(p, user);                        /* username */
       pl16(p, 10);                            /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       conv = gl16(p);                         /* rx buffer offset */
       gl16(p);                                /* number of entries returned */
       gl16(p);                                /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       gmem(p, tmp, 21);
       tmp[21] = 0;
       uip->user = estrdup9p(tmp);
       g8(p);                          /* padding */
       gconv(p, conv, tmp, sizeof tmp);
       uip->comment = estrdup9p(tmp);
       gconv(p, conv, tmp, sizeof tmp);
       uip->user_comment = estrdup9p(tmp);
       gconv(p, conv, tmp, sizeof tmp);
       uip->fullname = estrdup9p(tmp);

       free(p);
       return 0;
}

/*
* This works agains win2k3 but fails
* against XP with the undocumented error 71/0x47
*/
int
RAPServerenum2(Session *s, Share *sp, char *workgroup, int type, int *more,
       Serverinfo **si)
{
       int ngot = 0, conv, err, nret, navail;
       char tmp[1024];
       Pkt *p;
       Serverinfo *q;

       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");

       ptparam(p);
       pl16(p, API_NetServerEnum2);
       pascii(p, REMSmb_NetServerEnum2_P);     /* request descriptor */
       pascii(p, REMSmb_server_info_1);        /* reply descriptor */
       pl16(p, 1);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       pl32(p, type);
       pascii(p, workgroup);

       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       conv = gl16(p);                         /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       *si = emalloc9p(sizeof(Serverinfo) * navail);
       memset(*si, 0, sizeof(Serverinfo) * navail);

       q = *si;
       for (; nret-- != 0 && ngot < navail; ngot++){
               gmem(p, tmp, 16);
               tmp[16] = 0;
               q->name = estrdup9p(tmp);
               q->major = g8(p);
               q->minor = g8(p);
               q->type = gl32(p);
               gconv(p, conv, tmp, sizeof tmp);
               q->comment = estrdup9p(tmp);
               q++;
       }
       free(p);
       *more = err == RAP_ERR_MOREINFO;
       return ngot;
}

int
RAPServerenum3(Session *s, Share *sp, char *workgroup, int type, int last,
       Serverinfo *si)
{
       int conv, err, ngot, nret, navail;
       char *first, tmp[1024];
       Pkt *p;
       Serverinfo *q;

       ngot = last +1;
       first = si[last].name;
more:
       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");

       ptparam(p);
       pl16(p, API_NetServerEnum3);
       pascii(p, REMSmb_NetServerEnum3_P);     /* request descriptor */
       pascii(p, REMSmb_server_info_1);        /* reply descriptor */
       pl16(p, 1);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       pl32(p, type);
       pascii(p, workgroup);
       pascii(p, first);

       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       conv = gl16(p);                         /* rx buffer offset */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       if(nret < 2){                           /* paranoia */
               free(p);
               return ngot;
       }

       q = si+ngot;
       while(nret-- != 0 && ngot < navail){
               gmem(p, tmp, 16);
               tmp[16] = 0;
               q->name = estrdup9p(tmp);
               q->major = g8(p);
               q->minor = g8(p);
               q->type = gl32(p);
               gconv(p, conv, tmp, sizeof tmp);
               tmp[sizeof tmp - 1] = 0;
               q->comment = estrdup9p(tmp);
               if(strcmp(first, tmp) == 0){ /* 1st one thru _may_ be a repeat */
                       free(q->name);
                       free(q->comment);
                       continue;
               }
               ngot++;
               q++;
       }
       free(p);
       if(ngot < navail)
               goto more;
       return ngot;
}

/* Only the Administrator has permission to do this */
int
RAPFileenum2(Session *s, Share *sp, char *user, char *path, Fileinfo **fip)
{
       int conv, err, ngot, resume, nret, navail;
       char tmp[1024];
       Pkt *p;
       Fileinfo *q;

       ngot = 0;
       resume = 0;
more:
       p = thdr(s, sp);
       pstr(p, "\\PIPE\\LANMAN");

       ptparam(p);
       pl16(p, API_WFileEnum2);
       pascii(p, REMSmb_NetFileEnum2_P);       /* request descriptor */
       pascii(p, REMSmb_file_info_1);          /* reply descriptor */
       pascii(p, path);
       pascii(p, user);
       pl16(p, 1);                             /* detail level */
       pl16(p, s->mtu - 1024);                 /* receive buffer length */
       pl32(p, resume);                        /* resume key */
/* FIXME: maybe the padding and resume key are the wrong way around? */
       pl32(p, 0);                             /* padding ? */

       ptdata(p);

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

       gtparam(p);
       err = gl16(p);                          /* error code */
       conv = gl16(p);                         /* rx buffer offset */
       resume = gl32(p);                       /* resume key returned */
       nret = gl16(p);                         /* number of entries returned */
       navail = gl16(p);                       /* number of entries available */

       if(err && err != RAP_ERR_MOREINFO){
               werrstr("%s", raperrstr(err));
               free(p);
               return -1;
       }

       if(nret < 2){                           /* paranoia */
               free(p);
               return ngot;
       }

       if(ngot == 0){
               *fip = emalloc9p(sizeof(Fileinfo) * navail);
               memset(*fip, 0, sizeof(Fileinfo) * navail);
       }
       q = *fip + ngot;
       for(; nret-- && ngot < navail; ngot++){
               q->ident = gl16(p);
               q->perms = gl16(p);
               q->locks = gl16(p);
               gconv(p, conv, tmp, sizeof tmp);
               tmp[sizeof tmp - 1] = 0;
               q->path = estrdup9p(tmp);
               gconv(p, conv, tmp, sizeof tmp);
               tmp[sizeof tmp - 1] = 0;
               q->user = estrdup9p(tmp);
               q++;
       }
       free(p);
       if(ngot < navail)
               goto more;
       return ngot;
}