bbc0bcae82b3870200eb5ef578585fe77ab94fa3
[ira/wip.git] / source / torture / raw / streams.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    test alternate data streams
5
6    Copyright (C) Andrew Tridgell 2004
7    
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "torture/torture.h"
24 #include "libcli/raw/libcliraw.h"
25 #include "system/filesys.h"
26 #include "libcli/libcli.h"
27 #include "torture/util.h"
28
29 #define BASEDIR "\\teststreams"
30
31 #define CHECK_STATUS(status, correct) do { \
32         if (!NT_STATUS_EQUAL(status, correct)) { \
33                 printf("(%s) Incorrect status %s - should be %s\n", \
34                        __location__, nt_errstr(status), nt_errstr(correct)); \
35                 ret = false; \
36                 goto done; \
37         }} while (0)
38
39 #define CHECK_VALUE(v, correct) do { \
40         if ((v) != (correct)) { \
41                 printf("(%s) Incorrect value %s=%d - should be %d\n", \
42                        __location__, #v, (int)v, (int)correct); \
43                 ret = false; \
44         }} while (0)
45
46 /*
47   check that a stream has the right contents
48 */
49 static bool check_stream(struct smbcli_state *cli, const char *location,
50                          TALLOC_CTX *mem_ctx,
51                          const char *fname, const char *sname, 
52                          const char *value)
53 {
54         int fnum;
55         const char *full_name;
56         uint8_t *buf;
57         ssize_t ret;
58
59         full_name = talloc_asprintf(mem_ctx, "%s:%s", fname, sname);
60
61         fnum = smbcli_open(cli->tree, full_name, O_RDONLY, DENY_NONE);
62
63         if (value == NULL) {
64                 if (fnum != -1) {
65                         printf("(%s) should have failed stream open of %s\n",
66                                location, full_name);
67                         return false;
68                 }
69                 return true;
70         }
71             
72         if (fnum == -1) {
73                 printf("(%s) Failed to open stream '%s' - %s\n",
74                        location, full_name, smbcli_errstr(cli->tree));
75                 return false;
76         }
77
78         buf = talloc_array(mem_ctx, uint8_t, strlen(value)+11);
79         
80         ret = smbcli_read(cli->tree, fnum, buf, 0, strlen(value)+11);
81         if (ret != strlen(value)) {
82                 printf("(%s) Failed to read %lu bytes from stream '%s' - got %d\n",
83                        location, (long)strlen(value), full_name, (int)ret);
84                 return false;
85         }
86
87         if (memcmp(buf, value, strlen(value)) != 0) {
88                 printf("(%s) Bad data in stream\n", location);
89                 return false;
90         }
91
92         smbcli_close(cli->tree, fnum);
93         return true;
94 }
95
96 static int qsort_string(const void *v1, const void *v2)
97 {
98         char * const *s1 = v1;
99         char * const *s2 = v2;
100         return strcmp(*s1, *s2);
101 }
102
103 static int qsort_stream(const void *v1, const void *v2)
104 {
105         const struct stream_struct * s1 = v1;
106         const struct stream_struct * s2 = v2;
107         return strcmp(s1->stream_name.s, s2->stream_name.s);
108 }
109
110 static bool check_stream_list(struct smbcli_state *cli, const char *fname,
111                               int num_exp, const char **exp)
112 {
113         union smb_fileinfo finfo;
114         NTSTATUS status;
115         int i;
116         TALLOC_CTX *tmp_ctx = talloc_new(cli);
117         char **exp_sort;
118         struct stream_struct *stream_sort;
119         bool ret = false;
120
121         finfo.generic.level = RAW_FILEINFO_STREAM_INFO;
122         finfo.generic.in.file.path = fname;
123
124         status = smb_raw_pathinfo(cli->tree, tmp_ctx, &finfo);
125         if (!NT_STATUS_IS_OK(status)) {
126                 d_fprintf(stderr, "(%s) smb_raw_pathinfo failed: %s\n",
127                           __location__, nt_errstr(status));
128                 goto fail;
129         }
130
131         if (finfo.stream_info.out.num_streams != num_exp) {
132                 d_fprintf(stderr, "(%s) expected %d streams, got %d\n",
133                           __location__, num_exp,
134                           finfo.stream_info.out.num_streams);
135                 goto fail;
136         }
137
138         exp_sort = talloc_memdup(tmp_ctx, exp, num_exp * sizeof(*exp));
139
140         if (exp_sort == NULL) {
141                 goto fail;
142         }
143
144         qsort(exp_sort, num_exp, sizeof(*exp_sort), qsort_string);
145
146         stream_sort = talloc_memdup(tmp_ctx, finfo.stream_info.out.streams,
147                                     finfo.stream_info.out.num_streams *
148                                     sizeof(*stream_sort));
149
150         if (stream_sort == NULL) {
151                 goto fail;
152         }
153
154         qsort(stream_sort, finfo.stream_info.out.num_streams,
155               sizeof(*stream_sort), qsort_stream);
156
157         for (i=0; i<num_exp; i++) {
158                 if (strcmp(exp_sort[i], stream_sort[i].stream_name.s) != 0) {
159                         d_fprintf(stderr, "(%s) expected stream name %s, got "
160                                   "%s\n", __location__, exp_sort[i],
161                                   stream_sort[i].stream_name.s);
162                         goto fail;
163                 }
164         }
165
166         ret = true;
167  fail:
168         talloc_free(tmp_ctx);
169         return ret;
170 }
171
172 /*
173   test basic io on streams
174 */
175 static bool test_stream_io(struct torture_context *tctx,
176                            struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
177 {
178         NTSTATUS status;
179         union smb_open io;
180         const char *fname = BASEDIR "\\stream.txt";
181         const char *sname1, *sname2;
182         bool ret = true;
183         int fnum = -1;
184         ssize_t retsize;
185
186         const char *one[] = { "::$DATA" };
187         const char *two[] = { "::$DATA", ":Second Stream:$DATA" };
188         const char *three[] = { "::$DATA", ":Stream One:$DATA",
189                                 ":Second Stream:$DATA" };
190
191         sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One");
192         sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, "Second Stream");
193
194         printf("(%s) opening non-existant directory stream\n", __location__);
195         io.generic.level = RAW_OPEN_NTCREATEX;
196         io.ntcreatex.in.root_fid = 0;
197         io.ntcreatex.in.flags = 0;
198         io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA;
199         io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
200         io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL;
201         io.ntcreatex.in.share_access = 0;
202         io.ntcreatex.in.alloc_size = 0;
203         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
204         io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS;
205         io.ntcreatex.in.security_flags = 0;
206         io.ntcreatex.in.fname = sname1;
207         status = smb_raw_open(cli->tree, mem_ctx, &io);
208         CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY);
209
210         printf("(%s) creating a stream on a non-existant file\n", __location__);
211         io.ntcreatex.in.create_options = 0;
212         io.ntcreatex.in.fname = sname1;
213         status = smb_raw_open(cli->tree, mem_ctx, &io);
214         CHECK_STATUS(status, NT_STATUS_OK);
215         fnum = io.ntcreatex.out.file.fnum;
216
217         ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One", NULL);
218
219         printf("(%s) check that open of base file is allowed\n", __location__);
220         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN;
221         io.ntcreatex.in.fname = fname;
222         status = smb_raw_open(cli->tree, mem_ctx, &io);
223         CHECK_STATUS(status, NT_STATUS_OK);
224         smbcli_close(cli->tree, io.ntcreatex.out.file.fnum);
225
226         printf("(%s) writing to stream\n", __location__);
227         retsize = smbcli_write(cli->tree, fnum, 0, "test data", 0, 9);
228         CHECK_VALUE(retsize, 9);
229
230         smbcli_close(cli->tree, fnum);
231
232         ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One", "test data");
233
234         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN;
235         io.ntcreatex.in.fname = sname1;
236         status = smb_raw_open(cli->tree, mem_ctx, &io);
237         CHECK_STATUS(status, NT_STATUS_OK);
238         fnum = io.ntcreatex.out.file.fnum;
239
240         printf("(%s) modifying stream\n", __location__);
241         retsize = smbcli_write(cli->tree, fnum, 0, "MORE DATA ", 5, 10);
242         CHECK_VALUE(retsize, 10);
243
244         smbcli_close(cli->tree, fnum);
245
246         ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One:$FOO", NULL);
247
248         printf("(%s) creating a stream2 on a existing file\n", __location__);
249         io.ntcreatex.in.fname = sname2;
250         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF;
251         status = smb_raw_open(cli->tree, mem_ctx, &io);
252         CHECK_STATUS(status, NT_STATUS_OK);
253         fnum = io.ntcreatex.out.file.fnum;
254
255         printf("(%s) modifying stream\n", __location__);
256         retsize = smbcli_write(cli->tree, fnum, 0, "SECOND STREAM", 0, 13);
257         CHECK_VALUE(retsize, 13);
258
259         smbcli_close(cli->tree, fnum);
260
261         ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One", "test MORE DATA ");
262         ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One:$DATA", "test MORE DATA ");
263         ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One:", NULL);
264         ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream", "SECOND STREAM");
265         if (!torture_setting_bool(tctx, "samba4", false)) {
266                 ret &= check_stream(cli, __location__, mem_ctx, fname,
267                                     "SECOND STREAM:$DATA", "SECOND STREAM");
268         }
269         ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream:$DATA", "SECOND STREAM");
270         ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream:", NULL);
271         ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream:$FOO", NULL);
272
273         check_stream_list(cli, fname, 3, three);
274
275         printf("(%s) deleting stream\n", __location__);
276         status = smbcli_unlink(cli->tree, sname1);
277         CHECK_STATUS(status, NT_STATUS_OK);
278
279         check_stream_list(cli, fname, 2, two);
280
281         printf("(%s) delete a stream via delete-on-close\n", __location__);
282         io.ntcreatex.in.fname = sname2;
283         io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
284         io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE;
285         io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL;
286         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN;
287
288         status = smb_raw_open(cli->tree, mem_ctx, &io);
289         CHECK_STATUS(status, NT_STATUS_OK);
290         fnum = io.ntcreatex.out.file.fnum;
291         
292         smbcli_close(cli->tree, fnum);
293         status = smbcli_unlink(cli->tree, sname2);
294         CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
295
296         check_stream_list(cli, fname, 1, one);
297
298         if (!torture_setting_bool(tctx, "samba4", false)) {
299                 io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
300                 io.ntcreatex.in.fname = sname1;
301                 status = smb_raw_open(cli->tree, mem_ctx, &io);
302                 CHECK_STATUS(status, NT_STATUS_OK);
303                 smbcli_close(cli->tree, io.ntcreatex.out.file.fnum);
304                 io.ntcreatex.in.fname = sname2;
305                 status = smb_raw_open(cli->tree, mem_ctx, &io);
306                 CHECK_STATUS(status, NT_STATUS_OK);
307                 smbcli_close(cli->tree, io.ntcreatex.out.file.fnum);
308         }
309
310         printf("(%s) deleting file\n", __location__);
311         status = smbcli_unlink(cli->tree, fname);
312         CHECK_STATUS(status, NT_STATUS_OK);
313
314 done:
315         smbcli_close(cli->tree, fnum);
316         return ret;
317 }
318
319 /*
320   test stream sharemodes
321 */
322 static bool test_stream_sharemodes(struct torture_context *tctx,
323                                    struct smbcli_state *cli,
324                                    TALLOC_CTX *mem_ctx)
325 {
326         NTSTATUS status;
327         union smb_open io;
328         const char *fname = BASEDIR "\\stream.txt";
329         const char *sname1, *sname2;
330         bool ret = true;
331         int fnum1 = -1;
332         int fnum2 = -1;
333
334         sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One");
335         sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, "Second Stream");
336
337         printf("(%s) testing stream share mode conflicts\n", __location__);
338         io.generic.level = RAW_OPEN_NTCREATEX;
339         io.ntcreatex.in.root_fid = 0;
340         io.ntcreatex.in.flags = 0;
341         io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA;
342         io.ntcreatex.in.create_options = 0;
343         io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL;
344         io.ntcreatex.in.share_access = 0;
345         io.ntcreatex.in.alloc_size = 0;
346         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
347         io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS;
348         io.ntcreatex.in.security_flags = 0;
349         io.ntcreatex.in.fname = sname1;
350
351         status = smb_raw_open(cli->tree, mem_ctx, &io);
352         CHECK_STATUS(status, NT_STATUS_OK);
353         fnum1 = io.ntcreatex.out.file.fnum;
354
355         /*
356          * A different stream does not give a sharing violation
357          */
358
359         CHECK_STATUS(status, NT_STATUS_OK);
360         fnum1 = io.ntcreatex.out.file.fnum;
361
362         /*
363          * ... whereas the same stream does with unchanged access/share_access
364          * flags
365          */
366
367         io.ntcreatex.in.open_disposition = 0;
368         status = smb_raw_open(cli->tree, mem_ctx, &io);
369         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
370
371         io.ntcreatex.in.fname = sname2;
372         status = smb_raw_open(cli->tree, mem_ctx, &io);
373         CHECK_STATUS(status, NT_STATUS_OK);
374         fnum2 = io.ntcreatex.out.file.fnum;
375
376 done:
377         if (fnum1 != -1) smbcli_close(cli->tree, fnum1);
378         if (fnum2 != -1) smbcli_close(cli->tree, fnum2);
379         status = smbcli_unlink(cli->tree, fname);
380         return ret;
381 }
382
383 /* 
384  *  Test FILE_SHARE_DELETE on streams
385  *
386  * A stream opened with !FILE_SHARE_DELETE prevents the main file to be opened
387  * with SEC_STD_DELETE.
388  *
389  * The main file opened with !FILE_SHARE_DELETE does *not* prevent a stream to
390  * be opened with SEC_STD_DELETE.
391  *
392  * A stream held open with FILE_SHARE_DELETE allows the file to be
393  * deleted. After the main file is deleted, access to the open file descriptor
394  * still works, but all name-based access to both the main file as well as the
395  * stream is denied with DELETE ending.
396  *
397  * This means, an open of the main file with SEC_STD_DELETE should walk all
398  * streams and also open them with SEC_STD_DELETE. If any of these opens gives
399  * SHARING_VIOLATION, the main open fails.
400  *
401  * Closing the main file after delete_on_close has been set does not really
402  * unlink it but leaves the corresponding share mode entry with
403  * delete_on_close being set around until all streams are closed.
404  *
405  * Opening a stream must also look at the main file's share mode entry, look
406  * at the delete_on_close bit and potentially return DELETE_PENDING.
407  */
408
409 static bool test_stream_delete(struct torture_context *tctx,
410                                struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
411 {
412         NTSTATUS status;
413         union smb_open io;
414         const char *fname = BASEDIR "\\stream.txt";
415         const char *sname1;
416         bool ret = true;
417         int fnum = -1;
418         uint8_t buf[9];
419         ssize_t retsize;
420         union smb_fileinfo finfo;
421
422         sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One");
423
424         printf("(%s) opening non-existant directory stream\n", __location__);
425         io.generic.level = RAW_OPEN_NTCREATEX;
426         io.ntcreatex.in.root_fid = 0;
427         io.ntcreatex.in.flags = 0;
428         io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA;
429         io.ntcreatex.in.create_options = 0;
430         io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL;
431         io.ntcreatex.in.share_access = 0;
432         io.ntcreatex.in.alloc_size = 0;
433         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
434         io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS;
435         io.ntcreatex.in.security_flags = 0;
436         io.ntcreatex.in.fname = sname1;
437
438         status = smb_raw_open(cli->tree, mem_ctx, &io);
439         CHECK_STATUS(status, NT_STATUS_OK);
440         fnum = io.ntcreatex.out.file.fnum;
441
442         retsize = smbcli_write(cli->tree, fnum, 0, "test data", 0, 9);
443         CHECK_VALUE(retsize, 9);
444
445         /*
446          * One stream opened without FILE_SHARE_DELETE prevents the main file
447          * to be deleted or even opened with DELETE access
448          */
449
450         status = smbcli_unlink(cli->tree, fname);
451         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
452
453         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN;
454         io.ntcreatex.in.fname = fname;
455         io.ntcreatex.in.access_mask = SEC_STD_DELETE;
456         status = smb_raw_open(cli->tree, mem_ctx, &io);
457         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
458
459         smbcli_close(cli->tree, fnum);
460
461         /*
462          * ... but unlink works if a stream is opened with FILE_SHARE_DELETE
463          */
464
465         io.ntcreatex.in.fname = sname1;
466         io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA;
467         io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE;
468         status = smb_raw_open(cli->tree, mem_ctx, &io);
469         CHECK_STATUS(status, NT_STATUS_OK);
470         fnum = io.ntcreatex.out.file.fnum;
471
472         status = smbcli_unlink(cli->tree, fname);
473         CHECK_STATUS(status, NT_STATUS_OK);
474
475         /*
476          * file access still works on the stream while the main file is closed
477          */
478
479         retsize = smbcli_read(cli->tree, fnum, buf, 0, 9);
480         CHECK_VALUE(retsize, 9);
481
482         finfo.generic.level = RAW_FILEINFO_STANDARD;
483         finfo.generic.in.file.path = fname;
484
485         /*
486          * name-based access to both the main file and the stream does not
487          * work anymore but gives DELETE_PENDING
488          */
489
490         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
491         CHECK_STATUS(status, NT_STATUS_DELETE_PENDING);
492
493         finfo.generic.in.file.path = sname1;
494         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
495         CHECK_STATUS(status, NT_STATUS_DELETE_PENDING);
496
497         /*
498          * fd-based qfileinfo on the stream still works, the stream does not
499          * have the delete-on-close bit set. This could mean that open on the
500          * stream first opens the main file
501          */
502
503         finfo.all_info.level = RAW_FILEINFO_ALL_INFO;
504         finfo.all_info.in.file.fnum = fnum;
505
506         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo);
507         CHECK_STATUS(status, NT_STATUS_OK);
508         CHECK_VALUE(finfo.all_info.out.delete_pending, 0);
509
510         smbcli_close(cli->tree, fnum);
511
512         /*
513          * After closing the stream the file is really gone.
514          */
515
516         finfo.generic.in.file.path = fname;
517         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
518         CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
519
520         io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA
521                 |SEC_STD_DELETE;
522         io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
523         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
524         status = smb_raw_open(cli->tree, mem_ctx, &io);
525         CHECK_STATUS(status, NT_STATUS_OK);
526         fnum = io.ntcreatex.out.file.fnum;
527
528         finfo.generic.in.file.path = fname;
529         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
530         CHECK_STATUS(status, NT_STATUS_OK);
531
532         smbcli_close(cli->tree, fnum);
533
534         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
535         CHECK_STATUS(status, NT_STATUS_OK);
536 done:
537         smbcli_close(cli->tree, fnum);
538         return ret;
539 }
540
541 /* 
542    basic testing of streams calls
543 */
544 bool torture_raw_streams(struct torture_context *torture, 
545                          struct smbcli_state *cli)
546 {
547         bool ret = true;
548
549         if (!torture_setup_dir(cli, BASEDIR)) {
550                 return false;
551         }
552
553         ret &= test_stream_io(torture, cli, torture);
554         ret &= test_stream_sharemodes(torture, cli, torture);
555         if (!torture_setting_bool(torture, "samba4", false)) {
556                 ret &= test_stream_delete(torture, cli, torture);
557         }
558
559         smb_raw_exit(cli->session);
560         smbcli_deltree(cli->tree, BASEDIR);
561
562         return ret;
563 }