Fix bug #5953 - smbclient crashes: cli_list_new segmentation fault.
[kai/samba.git] / source3 / libsmb / clilist.c
index 64cb3e8fe352db4f381028beba90bf305e934aac..cebafc6919a0d522f38f6ca6a1704a4050746a30 100644 (file)
@@ -19,8 +19,6 @@
 
 #include "includes.h"
 
-extern file_info def_finfo;
-
 /****************************************************************************
  Calculate a safe next_entry_offset.
 ****************************************************************************/
@@ -44,7 +42,8 @@ static size_t calc_next_entry_offset(const char *base, const char *pdata_end)
  by NT and 2 is used by OS/2
 ****************************************************************************/
 
-static size_t interpret_long_filename(struct cli_state *cli,
+static size_t interpret_long_filename(TALLOC_CTX *ctx,
+                                       struct cli_state *cli,
                                        int level,
                                        const char *p,
                                        const char *pdata_end,
@@ -52,20 +51,16 @@ static size_t interpret_long_filename(struct cli_state *cli,
                                        uint32 *p_resume_key,
                                        DATA_BLOB *p_last_name_raw)
 {
-       file_info finfo2;
        int len;
+       size_t ret;
        const char *base = p;
 
        data_blob_free(p_last_name_raw);
 
-       if (!finfo) {
-               finfo = &finfo2;
-       }
-
        if (p_resume_key) {
                *p_resume_key = 0;
        }
-       memcpy(finfo,&def_finfo,sizeof(*finfo));
+       ZERO_STRUCTP(finfo);
        finfo->cli = cli;
 
        switch (level) {
@@ -83,17 +78,34 @@ static size_t interpret_long_filename(struct cli_state *cli,
                        len = CVAL(p, 26);
                        p += 27;
                        p += clistr_align_in(cli, p, 0);
-                       if (p + len + 2 > pdata_end) {
+
+                       /* We can safely use +1 here (which is required by OS/2)
+                        * instead of +2 as the STR_TERMINATE flag below is
+                        * actually used as the length calculation.
+                        * The len+2 is merely an upper bound.
+                        * Due to the explicit 2 byte null termination
+                        * in cli_receive_trans/cli_receive_nt_trans
+                        * we know this is safe. JRA + kukks
+                        */
+
+                       if (p + len + 1 > pdata_end) {
                                return pdata_end - base;
                        }
+
                        /* the len+2 below looks strange but it is
                           important to cope with the differences
                           between win2000 and win9x for this call
                           (tridge) */
-                       p += clistr_pull(cli, finfo->name, p,
-                                        sizeof(finfo->name),
-                                        len+2,
-                                        STR_TERMINATE);
+                       ret = clistr_pull_talloc(ctx,
+                                               cli,
+                                               &finfo->name,
+                                               p,
+                                               len+2,
+                                               STR_TERMINATE);
+                       if (ret == (size_t)-1) {
+                               return pdata_end - base;
+                       }
+                       p += ret;
                        return PTR_DIFF(p, base);
 
                case 2: /* this is what OS/2 uses mostly */
@@ -113,10 +125,16 @@ static size_t interpret_long_filename(struct cli_state *cli,
                        if (p + len + 1 > pdata_end) {
                                return pdata_end - base;
                        }
-                       p += clistr_pull(cli, finfo->name, p,
-                                        sizeof(finfo->name),
-                                        len,
-                                        STR_NOALIGN);
+                       ret = clistr_pull_talloc(ctx,
+                                               cli,
+                                               &finfo->name,
+                                               p,
+                                               len,
+                                               STR_NOALIGN);
+                       if (ret == (size_t)-1) {
+                               return pdata_end - base;
+                       }
+                       p += ret;
                        return PTR_DIFF(p, base) + 1;
 
                case 260: /* NT uses this, but also accepts 2 */
@@ -168,9 +186,15 @@ static size_t interpret_long_filename(struct cli_state *cli,
                        if (p + namelen < p || p + namelen > pdata_end) {
                                return pdata_end - base;
                        }
-                       clistr_pull(cli, finfo->name, p,
-                                   sizeof(finfo->name),
-                                   namelen, 0);
+                       ret = clistr_pull_talloc(ctx,
+                                               cli,
+                                               &finfo->name,
+                                               p,
+                                               namelen,
+                                               0);
+                       if (ret == (size_t)-1) {
+                               return pdata_end - base;
+                       }
 
                        /* To be robust in the face of unicode conversion failures
                           we need to copy the raw bytes of the last name seen here.
@@ -218,9 +242,10 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
        char *rparam=NULL, *rdata=NULL;
        unsigned int param_len, data_len;
        uint16 setup;
-       char param[1024];
+       char *param;
        const char *mnt;
        uint32 resume_key = 0;
+       TALLOC_CTX *frame = talloc_stackframe();
        DATA_BLOB last_name_raw = data_blob(NULL, 0);
 
        /* NT uses 260, OS/2 uses 2. Both accept 1. */
@@ -228,16 +253,24 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
 
        mask = SMB_STRDUP(Mask);
        if (!mask) {
+               TALLOC_FREE(frame);
                return -1;
        }
 
        while (ff_eos == 0) {
+               size_t nlen = 2*(strlen(mask)+1);
+
                loop_count++;
                if (loop_count > 200) {
                        DEBUG(0,("Error: Looping in FIND_NEXT??\n"));
                        break;
                }
 
+               param = SMB_MALLOC_ARRAY(char, 12+nlen+last_name_raw.length+2);
+               if (!param) {
+                       break;
+               }
+
                if (First) {
                        setup = TRANSACT2_FINDFIRST;
                        SSVAL(param,0,attribute); /* attribute */
@@ -246,8 +279,8 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                        SSVAL(param,6,info_level);
                        SIVAL(param,8,0);
                        p = param+12;
-                       p += clistr_push(cli, param+12, mask, sizeof(param)-12,
-                                        STR_TERMINATE);
+                       p += clistr_push(cli, param+12, mask,
+                                        nlen, STR_TERMINATE);
                } else {
                        setup = TRANSACT2_FINDNEXT;
                        SSVAL(param,0,ff_dir_handle);
@@ -260,11 +293,12 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                           can miss filenames. Use last filename continue instead. JRA */
                        SSVAL(param,10,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END));        /* resume required + close on end */
                        p = param+12;
-                       if (last_name_raw.length && (last_name_raw.length < (sizeof(param)-12))) {
+                       if (last_name_raw.length) {
                                memcpy(p, last_name_raw.data, last_name_raw.length);
                                p += last_name_raw.length;
                        } else {
-                               p += clistr_push(cli, param+12, mask, sizeof(param)-12, STR_TERMINATE);
+                               p += clistr_push(cli, param+12, mask,
+                                               nlen, STR_TERMINATE);
                        }
                }
 
@@ -283,14 +317,18 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                                    cli->max_xmit           /* data, length, max. */
 #endif
                                    )) {
+                       SAFE_FREE(param);
+                       TALLOC_FREE(frame);
                        break;
                }
 
+               SAFE_FREE(param);
+
                if (!cli_receive_trans(cli, SMBtrans2,
                                       &rparam, &param_len,
                                       &rdata, &data_len) &&
                     cli_is_dos_error(cli)) {
-                       /* we need to work around a Win95 bug - sometimes
+                       /* We need to work around a Win95 bug - sometimes
                           it gives ERRSRV/ERRerror temprarily */
                        uint8 eclass;
                        uint32 ecode;
@@ -299,6 +337,20 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                        SAFE_FREE(rparam);
 
                        cli_dos_error(cli, &eclass, &ecode);
+
+                       /*
+                        * OS/2 might return "no more files",
+                        * which just tells us, that searchcount is zero
+                        * in this search.
+                        * Guenter Kukkukk <linux@kukkukk.com>
+                        */
+
+                       if (eclass == ERRDOS && ecode == ERRnofiles) {
+                               ff_searchcount = 0;
+                               cli_reset_error(cli);
+                               break;
+                       }
+
                        if (eclass != ERRSRV || ecode != ERRerror)
                                break;
                        smb_msleep(100);
@@ -341,7 +393,8 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                                /* Last entry - fixup the last offset length. */
                                SIVAL(p2,0,PTR_DIFF((rdata + data_len),p2));
                        }
-                       p2 += interpret_long_filename(cli,
+                       p2 += interpret_long_filename(frame,
+                                                       cli,
                                                        info_level,
                                                        p2,
                                                        rdata_end,
@@ -349,6 +402,12 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                                                        &resume_key,
                                                        &last_name_raw);
 
+                       if (!finfo.name) {
+                               DEBUG(0,("cli_list_new: Error: unable to parse name from info level %d\n",
+                                       info_level));
+                               ff_eos = 1;
+                               break;
+                       }
                        if (!First && *mask && strcsequal(finfo.name, mask)) {
                                DEBUG(0,("Error: Looping in FIND_NEXT as name %s has already been seen?\n",
                                        finfo.name));
@@ -358,7 +417,7 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                }
 
                SAFE_FREE(mask);
-               if (ff_searchcount > 0) {
+               if (ff_searchcount > 0 && ff_eos == 0 && finfo.name) {
                        mask = SMB_STRDUP(finfo.name);
                } else {
                        mask = SMB_STRDUP("");
@@ -406,14 +465,20 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
                 /* no connection problem.  let user function add each entry */
                rdata_end = dirlist + dirlist_len;
                 for (p=dirlist,i=0;i<total_received;i++) {
-                        p += interpret_long_filename(cli,
+                        p += interpret_long_filename(frame,
+                                                       cli,
                                                        info_level,
                                                        p,
                                                        rdata_end,
                                                        &finfo,
                                                        NULL,
                                                        NULL);
-                        fn( mnt,&finfo, Mask, state );
+                       if (!finfo.name) {
+                               DEBUG(0,("cli_list_new: unable to parse name from info level %d\n",
+                                       info_level));
+                               break;
+                       }
+                        fn(mnt,&finfo, Mask, state);
                 }
         }
 
@@ -421,6 +486,7 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
        SAFE_FREE(dirlist);
        data_blob_free(&last_name_raw);
        SAFE_FREE(mask);
+       TALLOC_FREE(frame);
        return(total_received);
 }
 
@@ -429,10 +495,13 @@ int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute,
  The length of the structure is returned.
 ****************************************************************************/
 
-static int interpret_short_filename(struct cli_state *cli, char *p,file_info *finfo)
+static bool interpret_short_filename(TALLOC_CTX *ctx,
+                               struct cli_state *cli,
+                               char *p,
+                               file_info *finfo)
 {
-
-       *finfo = def_finfo;
+       size_t ret;
+       ZERO_STRUCTP(finfo);
 
        finfo->cli = cli;
        finfo->mode = CVAL(p,21);
@@ -443,16 +512,25 @@ static int interpret_short_filename(struct cli_state *cli, char *p,file_info *fi
        finfo->mtime_ts.tv_sec = finfo->atime_ts.tv_sec = finfo->ctime_ts.tv_sec;
        finfo->mtime_ts.tv_nsec = finfo->atime_ts.tv_nsec = 0;
        finfo->size = IVAL(p,26);
-       clistr_pull(cli, finfo->name, p+30, sizeof(finfo->name), 12, STR_ASCII);
-       if (strcmp(finfo->name, "..") && strcmp(finfo->name, ".")) {
-               strncpy(finfo->short_name,finfo->name, sizeof(finfo->short_name)-1);
-               finfo->short_name[sizeof(finfo->short_name)-1] = '\0';
+       ret = clistr_pull_talloc(ctx,
+                       cli,
+                       &finfo->name,
+                       p+30,
+                       12,
+                       STR_ASCII);
+       if (ret == (size_t)-1) {
+               return false;
        }
 
+       if (finfo->name) {
+               strlcpy(finfo->short_name,
+                       finfo->name,
+                       sizeof(finfo->short_name));
+       }
+       return true;
        return(DIR_STRUCT_SIZE);
 }
 
-
 /****************************************************************************
  Do a directory listing, calling fn on each file found.
  this uses the old SMBsearch interface. It is needed for testing Samba,
@@ -471,6 +549,7 @@ int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
        int i;
        char *dirlist = NULL;
        char *mask = NULL;
+       TALLOC_CTX *frame = NULL;
 
        ZERO_ARRAY(status);
 
@@ -483,7 +562,7 @@ int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
                memset(cli->outbuf,'\0',smb_size);
                memset(cli->inbuf,'\0',smb_size);
 
-               set_message(cli->outbuf,2,0,True);
+               cli_set_message(cli->outbuf,2,0,True);
 
                SCVAL(cli->outbuf,smb_com,SMBsearch);
 
@@ -496,7 +575,9 @@ int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
                p = smb_buf(cli->outbuf);
                *p++ = 4;
 
-               p += clistr_push(cli, p, first?mask:"", -1, STR_TERMINATE);
+               p += clistr_push(cli, p, first?mask:"",
+                               cli->bufsize - PTR_DIFF(p,cli->outbuf),
+                               STR_TERMINATE);
                *p++ = 5;
                if (first) {
                        SSVAL(p,0,0);
@@ -515,6 +596,12 @@ int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
                received = SVAL(cli->inbuf,smb_vwv0);
                if (received <= 0) break;
 
+               /* Ensure we received enough data. */
+               if ((cli->inbuf+4+smb_len(cli->inbuf) - (smb_buf(cli->inbuf)+3)) <
+                               received*DIR_STRUCT_SIZE) {
+                       break;
+               }
+
                first = False;
 
                dirlist = (char *)SMB_REALLOC(
@@ -541,7 +628,7 @@ int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
                memset(cli->outbuf,'\0',smb_size);
                memset(cli->inbuf,'\0',smb_size);
 
-               set_message(cli->outbuf,2,0,True);
+               cli_set_message(cli->outbuf,2,0,True);
                SCVAL(cli->outbuf,smb_com,SMBfclose);
                SSVAL(cli->outbuf,smb_tid,cli->cnum);
                cli_setup_packet(cli);
@@ -566,11 +653,16 @@ int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute,
                }
        }
 
+       frame = talloc_stackframe();
        for (p=dirlist,i=0;i<num_received;i++) {
                file_info finfo;
-               p += interpret_short_filename(cli, p,&finfo);
+               if (!interpret_short_filename(frame, cli, p, &finfo)) {
+                       break;
+               }
+               p += DIR_STRUCT_SIZE;
                fn("\\", &finfo, Mask, state);
        }
+       TALLOC_FREE(frame);
 
        SAFE_FREE(mask);
        SAFE_FREE(dirlist);