RAW-STREAMS: do what the comments say
[metze/samba/wip.git] / source4 / 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         io.ntcreatex.in.fname = sname2;
360         status = smb_raw_open(cli->tree, mem_ctx, &io);
361         CHECK_STATUS(status, NT_STATUS_OK);
362         fnum2 = io.ntcreatex.out.file.fnum;
363
364         /*
365          * ... whereas the same stream does with unchanged access/share_access
366          * flags
367          */
368
369         io.ntcreatex.in.fname = sname1;
370         io.ntcreatex.in.open_disposition = 0;
371         status = smb_raw_open(cli->tree, mem_ctx, &io);
372         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
373
374         io.ntcreatex.in.fname = sname2;
375         status = smb_raw_open(cli->tree, mem_ctx, &io);
376         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
377
378 done:
379         if (fnum1 != -1) smbcli_close(cli->tree, fnum1);
380         if (fnum2 != -1) smbcli_close(cli->tree, fnum2);
381         status = smbcli_unlink(cli->tree, fname);
382         return ret;
383 }
384
385 /* 
386  *  Test FILE_SHARE_DELETE on streams
387  *
388  * A stream opened with !FILE_SHARE_DELETE prevents the main file to be opened
389  * with SEC_STD_DELETE.
390  *
391  * The main file opened with !FILE_SHARE_DELETE does *not* prevent a stream to
392  * be opened with SEC_STD_DELETE.
393  *
394  * A stream held open with FILE_SHARE_DELETE allows the file to be
395  * deleted. After the main file is deleted, access to the open file descriptor
396  * still works, but all name-based access to both the main file as well as the
397  * stream is denied with DELETE ending.
398  *
399  * This means, an open of the main file with SEC_STD_DELETE should walk all
400  * streams and also open them with SEC_STD_DELETE. If any of these opens gives
401  * SHARING_VIOLATION, the main open fails.
402  *
403  * Closing the main file after delete_on_close has been set does not really
404  * unlink it but leaves the corresponding share mode entry with
405  * delete_on_close being set around until all streams are closed.
406  *
407  * Opening a stream must also look at the main file's share mode entry, look
408  * at the delete_on_close bit and potentially return DELETE_PENDING.
409  */
410
411 static bool test_stream_delete(struct torture_context *tctx,
412                                struct smbcli_state *cli, TALLOC_CTX *mem_ctx)
413 {
414         NTSTATUS status;
415         union smb_open io;
416         const char *fname = BASEDIR "\\stream.txt";
417         const char *sname1;
418         bool ret = true;
419         int fnum = -1;
420         uint8_t buf[9];
421         ssize_t retsize;
422         union smb_fileinfo finfo;
423
424         sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One");
425
426         printf("(%s) opening non-existant directory stream\n", __location__);
427         io.generic.level = RAW_OPEN_NTCREATEX;
428         io.ntcreatex.in.root_fid = 0;
429         io.ntcreatex.in.flags = 0;
430         io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA;
431         io.ntcreatex.in.create_options = 0;
432         io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL;
433         io.ntcreatex.in.share_access = 0;
434         io.ntcreatex.in.alloc_size = 0;
435         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
436         io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS;
437         io.ntcreatex.in.security_flags = 0;
438         io.ntcreatex.in.fname = sname1;
439
440         status = smb_raw_open(cli->tree, mem_ctx, &io);
441         CHECK_STATUS(status, NT_STATUS_OK);
442         fnum = io.ntcreatex.out.file.fnum;
443
444         retsize = smbcli_write(cli->tree, fnum, 0, "test data", 0, 9);
445         CHECK_VALUE(retsize, 9);
446
447         /*
448          * One stream opened without FILE_SHARE_DELETE prevents the main file
449          * to be deleted or even opened with DELETE access
450          */
451
452         status = smbcli_unlink(cli->tree, fname);
453         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
454
455         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN;
456         io.ntcreatex.in.fname = fname;
457         io.ntcreatex.in.access_mask = SEC_STD_DELETE;
458         status = smb_raw_open(cli->tree, mem_ctx, &io);
459         CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
460
461         smbcli_close(cli->tree, fnum);
462
463         /*
464          * ... but unlink works if a stream is opened with FILE_SHARE_DELETE
465          */
466
467         io.ntcreatex.in.fname = sname1;
468         io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA;
469         io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE;
470         status = smb_raw_open(cli->tree, mem_ctx, &io);
471         CHECK_STATUS(status, NT_STATUS_OK);
472         fnum = io.ntcreatex.out.file.fnum;
473
474         status = smbcli_unlink(cli->tree, fname);
475         CHECK_STATUS(status, NT_STATUS_OK);
476
477         /*
478          * file access still works on the stream while the main file is closed
479          */
480
481         retsize = smbcli_read(cli->tree, fnum, buf, 0, 9);
482         CHECK_VALUE(retsize, 9);
483
484         finfo.generic.level = RAW_FILEINFO_STANDARD;
485         finfo.generic.in.file.path = fname;
486
487         /*
488          * name-based access to both the main file and the stream does not
489          * work anymore but gives DELETE_PENDING
490          */
491
492         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
493         CHECK_STATUS(status, NT_STATUS_DELETE_PENDING);
494
495         if (!torture_setting_bool(tctx, "samba3", false)) {
496
497                 /*
498                  * S3 doesn't do this yet
499                  */
500
501                 finfo.generic.in.file.path = sname1;
502                 status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
503                 CHECK_STATUS(status, NT_STATUS_DELETE_PENDING);
504         }
505
506         /*
507          * fd-based qfileinfo on the stream still works, the stream does not
508          * have the delete-on-close bit set. This could mean that open on the
509          * stream first opens the main file
510          */
511
512         finfo.all_info.level = RAW_FILEINFO_ALL_INFO;
513         finfo.all_info.in.file.fnum = fnum;
514
515         status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo);
516         CHECK_STATUS(status, NT_STATUS_OK);
517         CHECK_VALUE(finfo.all_info.out.delete_pending, 0);
518
519         smbcli_close(cli->tree, fnum);
520
521         /*
522          * After closing the stream the file is really gone.
523          */
524
525         finfo.generic.in.file.path = fname;
526         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
527         CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
528
529         io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA
530                 |SEC_STD_DELETE;
531         io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
532         io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE;
533         status = smb_raw_open(cli->tree, mem_ctx, &io);
534         CHECK_STATUS(status, NT_STATUS_OK);
535         fnum = io.ntcreatex.out.file.fnum;
536
537         finfo.generic.in.file.path = fname;
538         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
539         CHECK_STATUS(status, NT_STATUS_OK);
540
541         smbcli_close(cli->tree, fnum);
542
543         status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo);
544         CHECK_STATUS(status, NT_STATUS_OK);
545 done:
546         smbcli_close(cli->tree, fnum);
547         return ret;
548 }
549
550 /* 
551    basic testing of streams calls
552 */
553 bool torture_raw_streams(struct torture_context *torture, 
554                          struct smbcli_state *cli)
555 {
556         bool ret = true;
557
558         if (!torture_setup_dir(cli, BASEDIR)) {
559                 return false;
560         }
561
562         ret &= test_stream_io(torture, cli, torture);
563         ret &= test_stream_sharemodes(torture, cli, torture);
564         if (!torture_setting_bool(torture, "samba4", false)) {
565                 ret &= test_stream_delete(torture, cli, torture);
566         }
567
568         smb_raw_exit(cli->session);
569         smbcli_deltree(cli->tree, BASEDIR);
570
571         return ret;
572 }