/* Unix SMB/CIFS implementation. vfs_fruit tests Copyright (C) Ralph Boehme 2014 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "system/filesys.h" #include "libcli/libcli.h" #include "libcli/smb2/smb2.h" #include "libcli/smb2/smb2_calls.h" #include "libcli/smb/smb2_create_ctx.h" #include "lib/cmdline/popt_common.h" #include "param/param.h" #include "libcli/resolve/resolve.h" #include "MacExtensions.h" #include "lib/util/tsort.h" #include "torture/torture.h" #include "torture/util.h" #include "torture/smb2/proto.h" #include "torture/vfs/proto.h" #include "librpc/gen_ndr/ndr_ioctl.h" #include "libcli/security/dom_sid.h" #include "../librpc/gen_ndr/ndr_security.h" #include "libcli/security/secace.h" #include "libcli/security/security_descriptor.h" #define BASEDIR "vfs_fruit_dir" #define FNAME_CC_SRC "testfsctl.dat" #define FNAME_CC_DST "testfsctl2.dat" #define CHECK_STATUS(status, correct) do { \ if (!NT_STATUS_EQUAL(status, correct)) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) Incorrect status %s - should be %s\n", \ __location__, nt_errstr(status), nt_errstr(correct)); \ ret = false; \ goto done; \ }} while (0) #define CHECK_VALUE(v, correct) do { \ if ((v) != (correct)) { \ torture_result(tctx, TORTURE_FAIL, \ "(%s) Incorrect value %s=%u - should be %u\n", \ __location__, #v, (unsigned)v, (unsigned)correct); \ ret = false; \ goto done; \ }} while (0) static bool check_stream_list(struct smb2_tree *tree, struct torture_context *tctx, const char *fname, int num_exp, const char **exp, bool is_dir); static int qsort_string(char * const *s1, char * const *s2) { return strcmp(*s1, *s2); } static int qsort_stream(const struct stream_struct * s1, const struct stream_struct *s2) { return strcmp(s1->stream_name.s, s2->stream_name.s); } /* * REVIEW: * This is hokey, but what else can we do? */ #if defined(HAVE_ATTROPEN) || defined(FREEBSD) #define AFPINFO_EA_NETATALK "org.netatalk.Metadata" #define AFPRESOURCE_EA_NETATALK "org.netatalk.ResourceFork" #else #define AFPINFO_EA_NETATALK "user.org.netatalk.Metadata" #define AFPRESOURCE_EA_NETATALK "user.org.netatalk.ResourceFork" #endif /* The metadata xattr char buf below contains the following attributes: ------------------------------------------------------------------------------- Entry ID : 00000008 : File Dates Info Offset : 00000162 : 354 Length : 00000010 : 16 -DATE------: : (GMT) : (Local) create : 1B442169 : Mon Jun 30 13:23:53 2014 : Mon Jun 30 15:23:53 2014 modify : 1B442169 : Mon Jun 30 13:23:53 2014 : Mon Jun 30 15:23:53 2014 backup : 80000000 : Unknown or Initial access : 1B442169 : Mon Jun 30 13:23:53 2014 : Mon Jun 30 15:23:53 2014 -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 1B 44 21 69 1B 44 21 69 80 00 00 00 1B 44 21 69 : .D!i.D!i.....D!i ------------------------------------------------------------------------------- Entry ID : 00000009 : Finder Info Offset : 0000007A : 122 Length : 00000020 : 32 -FInfo-----: Type : 42415252 : BARR Creator : 464F4F4F : FOOO isAlias : 0 Invisible : 1 hasBundle : 0 nameLocked : 0 Stationery : 0 CustomIcon : 0 Reserved : 0 Inited : 0 NoINITS : 0 Shared : 0 SwitchLaunc: 0 Hidden Ext : 0 color : 000 : none isOnDesk : 0 Location v : 0000 : 0 Location h : 0000 : 0 Fldr : 0000 : .. -FXInfo----: Rsvd|IconID: 0000 : 0 Rsvd : 0000 : .. Rsvd : 0000 : .. Rsvd : 0000 : .. AreInvalid : 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 CustomBadge: 0 ObjctIsBusy: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 RoutingInfo: 0 unknown bit: 0 unknown bit: 0 Rsvd|commnt: 0000 : 0 PutAway : 00000000 : 0 -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 42 41 52 52 46 4F 4F 4F 40 00 00 00 00 00 00 00 : BARRFOOO@....... 00000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ ------------------------------------------------------------------------------- Entry ID : 0000000E : AFP File Info Offset : 00000172 : 370 Length : 00000004 : 4 -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 00 00 01 A1 : .... */ char metadata_xattr[] = { 0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x62, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x72, 0x00, 0x00, 0x00, 0x04, 0x80, 0x44, 0x45, 0x56, 0x00, 0x00, 0x01, 0x76, 0x00, 0x00, 0x00, 0x08, 0x80, 0x49, 0x4e, 0x4f, 0x00, 0x00, 0x01, 0x7e, 0x00, 0x00, 0x00, 0x08, 0x80, 0x53, 0x59, 0x4e, 0x00, 0x00, 0x01, 0x86, 0x00, 0x00, 0x00, 0x08, 0x80, 0x53, 0x56, 0x7e, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x04, 0x42, 0x41, 0x52, 0x52, 0x46, 0x4f, 0x4f, 0x4f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x44, 0x21, 0x69, 0x1b, 0x44, 0x21, 0x69, 0x80, 0x00, 0x00, 0x00, 0x1b, 0x44, 0x21, 0x69, 0x00, 0x00, 0x01, 0xa1, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x20, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0xe3, 0x86, 0x53, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x01, 0x00, 0x00 }; /* The buf below contains the following AppleDouble encoded data: ------------------------------------------------------------------------------- MagicNumber: 00051607 : AppleDouble Version : 00020000 : Version 2 Filler : 4D 61 63 20 4F 53 20 58 20 20 20 20 20 20 20 20 : Mac OS X Num. of ent: 0002 : 2 ------------------------------------------------------------------------------- Entry ID : 00000009 : Finder Info Offset : 00000032 : 50 Length : 00000EB0 : 3760 -FInfo-----: Type : 54455354 : TEST Creator : 534C4F57 : SLOW isAlias : 0 Invisible : 0 hasBundle : 0 nameLocked : 0 Stationery : 0 CustomIcon : 0 Reserved : 0 Inited : 0 NoINITS : 0 Shared : 0 SwitchLaunc: 0 Hidden Ext : 0 color : 100 : blue isOnDesk : 0 Location v : 0000 : 0 Location h : 0000 : 0 Fldr : 0000 : .. -FXInfo----: Rsvd|IconID: 0000 : 0 Rsvd : 0000 : .. Rsvd : 0000 : .. Rsvd : 0000 : .. AreInvalid : 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 CustomBadge: 0 ObjctIsBusy: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 unknown bit: 0 RoutingInfo: 0 unknown bit: 0 unknown bit: 0 Rsvd|commnt: 0000 : 0 PutAway : 00000000 : 0 -EA--------: pad : 0000 : .. magic : 41545452 : ATTR debug_tag : 53D4580C : 1406425100 total_size : 00000EE2 : 3810 data_start : 000000BC : 188 data_length: 0000005E : 94 reserved[0]: 00000000 : .... reserved[1]: 00000000 : .... reserved[2]: 00000000 : .... flags : 0000 : .. num_attrs : 0002 : 2 -EA ENTRY--: offset : 000000BC : 188 length : 0000005B : 91 flags : 0000 : .. namelen : 24 : 36 -EA NAME---: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 63 6F 6D 2E 61 70 70 6C 65 2E 6D 65 74 61 64 61 : com.apple.metada 00000010 : 74 61 3A 5F 6B 4D 44 49 74 65 6D 55 73 65 72 54 : ta:_kMDItemUserT 00000020 : 61 67 73 00 : ags. -EA VALUE--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 62 70 6C 69 73 74 30 30 A5 01 02 03 04 05 54 74 : bplist00......Tt 00000010 : 65 73 74 66 00 47 00 72 00 FC 00 6E 00 0A 00 32 : estf.G.r...n...2 00000020 : 56 4C 69 6C 61 0A 33 56 47 65 6C 62 0A 35 56 42 : VLila.3VGelb.5VB 00000030 : 6C 61 75 0A 34 08 0E 13 20 27 2E 00 00 00 00 00 : lau.4... '...... 00000040 : 00 01 01 00 00 00 00 00 00 00 06 00 00 00 00 00 : ................ 00000050 : 00 00 00 00 00 00 00 00 00 00 35 : ..........5 -EA ENTRY--: offset : 00000117 : 279 length : 00000003 : 3 flags : 0000 : .. namelen : 08 : 8 -EA NAME---: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 66 6F 6F 3A 62 61 72 00 : foo:bar. -EA VALUE--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 62 61 7A : baz -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 54 45 53 54 53 4C 4F 57 00 08 00 00 00 00 00 00 : TESTSLOW........ 00000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000020 : 00 00 41 54 54 52 53 D4 58 0C 00 00 0E E2 00 00 : ..ATTRS.X....... 00000030 : 00 BC 00 00 00 5E 00 00 00 00 00 00 00 00 00 00 : .....^.......... 00000040 : 00 00 00 00 00 02 00 00 00 BC 00 00 00 5B 00 00 : .............[.. 00000050 : 24 63 6F 6D 2E 61 70 70 6C 65 2E 6D 65 74 61 64 : $com.apple.metad 00000060 : 61 74 61 3A 5F 6B 4D 44 49 74 65 6D 55 73 65 72 : ata:_kMDItemUser 00000070 : 54 61 67 73 00 00 00 00 01 17 00 00 00 03 00 00 : Tags............ 00000080 : 08 66 6F 6F 3A 62 61 72 00 66 62 70 6C 69 73 74 : .foo:bar.fbplist 00000090 : 30 30 A5 01 02 03 04 05 54 74 65 73 74 66 00 47 : 00......Ttestf.G 000000A0 : 00 72 00 FC 00 6E 00 0A 00 32 56 4C 69 6C 61 0A : .r...n...2VLila. 000000B0 : 33 56 47 65 6C 62 0A 35 56 42 6C 61 75 0A 34 08 : 3VGelb.5VBlau.4. 000000C0 : 0E 13 20 27 2E 00 00 00 00 00 00 01 01 00 00 00 : .. '............ 000000D0 : 00 00 00 00 06 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000E0 : 00 00 00 00 35 62 61 7A 00 00 00 00 00 00 00 00 : ....5baz........ 000000F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ ... all zeroes ... 00000EA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ ------------------------------------------------------------------------------- Entry ID : 00000002 : Resource Fork Offset : 00000EE2 : 3810 Length : 0000011E : 286 -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) 00000000 : 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 1E : ................ 00000010 : 54 68 69 73 20 72 65 73 6F 75 72 63 65 20 66 6F : This resource fo 00000020 : 72 6B 20 69 6E 74 65 6E 74 69 6F 6E 61 6C 6C 79 : rk intentionally 00000030 : 20 6C 65 66 74 20 62 6C 61 6E 6B 20 20 20 00 00 : left blank .. 00000040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000090 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 000000F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ 00000100 : 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 1E : ................ 00000110 : 00 00 00 00 00 00 00 00 00 1C 00 1E FF FF : .............. It was created with: $ hexdump -ve '"\t" 7/1 "0x%02x, " 1/1 " 0x%02x," "\n"' */ static char osx_adouble_w_xattr[] = { 0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x0e, 0xb0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x0e, 0xe2, 0x00, 0x00, 0x01, 0x1e, 0x54, 0x45, 0x53, 0x54, 0x53, 0x4c, 0x4f, 0x57, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x54, 0x54, 0x52, 0x53, 0xd4, 0x58, 0x0c, 0x00, 0x00, 0x0e, 0xe2, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x24, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x5f, 0x6b, 0x4d, 0x44, 0x49, 0x74, 0x65, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x08, 0x66, 0x6f, 0x6f, 0x3a, 0x62, 0x61, 0x72, 0x00, 0x66, 0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa5, 0x01, 0x02, 0x03, 0x04, 0x05, 0x54, 0x74, 0x65, 0x73, 0x74, 0x66, 0x00, 0x47, 0x00, 0x72, 0x00, 0xfc, 0x00, 0x6e, 0x00, 0x0a, 0x00, 0x32, 0x56, 0x4c, 0x69, 0x6c, 0x61, 0x0a, 0x33, 0x56, 0x47, 0x65, 0x6c, 0x62, 0x0a, 0x35, 0x56, 0x42, 0x6c, 0x61, 0x75, 0x0a, 0x34, 0x08, 0x0e, 0x13, 0x20, 0x27, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x62, 0x61, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x54, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6b, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x1e, 0xff, 0xff }; /* * The buf below contains the following AppleDouble encoded data: * * ------------------------------------------------------------------------------- * MagicNumber: 00051607 : AppleDouble * Version : 00020000 : Version 2 * Filler : 4D 61 63 20 4F 53 20 58 20 20 20 20 20 20 20 20 : Mac OS X * Num. of ent: 0002 : 2 * * ------------------------------------------------------------------------------- * Entry ID : 00000002 : Resource Fork * Offset : 00000052 : 82 * Length : 0000011E : 286 * * -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) * 00000000 : 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 1E : ................ * 00000010 : 54 68 69 73 20 72 65 73 6F 75 72 63 65 20 66 6F : This resource fo * 00000020 : 72 6B 20 69 6E 74 65 6E 74 69 6F 6E 61 6C 6C 79 : rk intentionally * 00000030 : 20 6C 65 66 74 20 62 6C 61 6E 6B 20 20 20 00 00 : left blank .. * 00000040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 00000050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 00000060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 00000070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 00000080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 00000090 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 000000A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 000000B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 000000C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 000000D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 000000E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 000000F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * 00000100 : 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 1E : ................ * 00000110 : 00 00 00 00 00 00 00 00 00 1C 00 1E FF FF : .............. * * Entry ID : 00000009 : Finder Info * Offset : 00000032 : 50 * Length : 00000020 : 32 * * -NOTE------: cannot detect whether FInfo or DInfo. assume FInfo. * * -FInfo-----: * Type : 57415645 : WAVE * Creator : 5054756C : PTul * isAlias : 0 * Invisible : 0 * hasBundle : 0 * nameLocked : 0 * Stationery : 0 * CustomIcon : 0 * Reserved : 0 * Inited : 0 * NoINITS : 0 * Shared : 0 * SwitchLaunc: 0 * Hidden Ext : 0 * color : 000 : none * isOnDesk : 0 * Location v : 0000 : 0 * Location h : 0000 : 0 * Fldr : 0000 : .. * * -FXInfo----: * Rsvd|IconID: 0000 : 0 * Rsvd : 0000 : .. * Rsvd : 0000 : .. * Rsvd : 0000 : .. * AreInvalid : 0 * unknown bit: 0 * unknown bit: 0 * unknown bit: 0 * unknown bit: 0 * unknown bit: 0 * unknown bit: 0 * CustomBadge: 0 * ObjctIsBusy: 0 * unknown bit: 0 * unknown bit: 0 * unknown bit: 0 * unknown bit: 0 * RoutingInfo: 0 * unknown bit: 0 * unknown bit: 0 * Rsvd|commnt: 0000 : 0 * PutAway : 00000000 : 0 * * -RAW DUMP--: 0 1 2 3 4 5 6 7 8 9 A B C D E F : (ASCII) * 00000000 : 57 41 56 45 50 54 75 6C 00 00 00 00 00 00 00 00 : WAVEPTul........ * 00000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................ * * * It was created with: * $ hexdump -ve '"\t" 7/1 "0x%02x, " 1/1 " 0x%02x," "\n"' */ static char osx_adouble_without_xattr[] = { 0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x01, 0x1e, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x20, 0x57, 0x41, 0x56, 0x45, 0x50, 0x54, 0x75, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x54, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6b, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x1e, 0xff, 0xff }; /** * talloc and intialize an AfpInfo **/ static AfpInfo *torture_afpinfo_new(TALLOC_CTX *mem_ctx) { AfpInfo *info; info = talloc_zero(mem_ctx, AfpInfo); if (info == NULL) { return NULL; } info->afpi_Signature = AFP_Signature; info->afpi_Version = AFP_Version; info->afpi_BackupTime = AFP_BackupTime; return info; } /** * Pack AfpInfo into a talloced buffer **/ static char *torture_afpinfo_pack(TALLOC_CTX *mem_ctx, AfpInfo *info) { char *buf; buf = talloc_zero_array(mem_ctx, char, AFP_INFO_SIZE); if (buf == NULL) { return NULL; } RSIVAL(buf, 0, info->afpi_Signature); RSIVAL(buf, 4, info->afpi_Version); RSIVAL(buf, 12, info->afpi_BackupTime); memcpy(buf + 16, info->afpi_FinderInfo, sizeof(info->afpi_FinderInfo)); return buf; } /** * Unpack AfpInfo **/ #if 0 static void torture_afpinfo_unpack(AfpInfo *info, char *data) { info->afpi_Signature = RIVAL(data, 0); info->afpi_Version = RIVAL(data, 4); info->afpi_BackupTime = RIVAL(data, 12); memcpy(info->afpi_FinderInfo, (const char *)data + 16, sizeof(info->afpi_FinderInfo)); } #endif static bool torture_write_afpinfo(struct smb2_tree *tree, struct torture_context *tctx, TALLOC_CTX *mem_ctx, const char *fname, AfpInfo *info) { struct smb2_handle handle; struct smb2_create io; NTSTATUS status; const char *full_name; char *infobuf; bool ret = true; full_name = talloc_asprintf(mem_ctx, "%s%s", fname, AFPINFO_STREAM_NAME); if (full_name == NULL) { torture_comment(tctx, "talloc_asprintf error\n"); return false; } ZERO_STRUCT(io); io.in.desired_access = SEC_FILE_WRITE_DATA; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; io.in.create_options = 0; io.in.fname = full_name; status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); handle = io.out.file.handle; infobuf = torture_afpinfo_pack(mem_ctx, info); if (infobuf == NULL) { return false; } status = smb2_util_write(tree, handle, infobuf, 0, AFP_INFO_SIZE); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, handle); done: return ret; } /** * Read 'count' bytes at 'offset' from stream 'fname:sname' and * compare against buffer 'value' **/ static bool check_stream(struct smb2_tree *tree, const char *location, struct torture_context *tctx, TALLOC_CTX *mem_ctx, const char *fname, const char *sname, off_t read_offset, size_t read_count, off_t comp_offset, size_t comp_count, const char *value) { struct smb2_handle handle; struct smb2_create create; struct smb2_read r; NTSTATUS status; char *full_name; bool ret = true; full_name = talloc_asprintf(mem_ctx, "%s%s", fname, sname); if (full_name == NULL) { torture_comment(tctx, "talloc_asprintf error\n"); return false; } ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_DATA; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = full_name; torture_comment(tctx, "Open stream %s\n", full_name); status = smb2_create(tree, mem_ctx, &create); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(full_name); if (value == NULL) { return true; } torture_comment(tctx, "Unable to open stream %s\n", full_name); return false; } handle = create.out.file.handle; if (value == NULL) { TALLOC_FREE(full_name); smb2_util_close(tree, handle); return true; } ZERO_STRUCT(r); r.in.file.handle = handle; r.in.length = read_count; r.in.offset = read_offset; status = smb2_read(tree, tree, &r); torture_assert_ntstatus_ok_goto( tctx, status, ret, done, talloc_asprintf(tctx, "(%s) Failed to read %lu bytes from stream '%s'\n", location, (long)strlen(value), full_name)); torture_assert_goto(tctx, r.out.data.length == read_count, ret, done, talloc_asprintf(tctx, "smb2_read returned %jd bytes, expected %jd\n", (intmax_t)r.out.data.length, (intmax_t)read_count)); torture_assert_goto( tctx, memcmp(r.out.data.data + comp_offset, value, comp_count) == 0, ret, done, talloc_asprintf(tctx, "(%s) Bad data in stream\n", location)); done: TALLOC_FREE(full_name); smb2_util_close(tree, handle); return ret; } /** * Read 'count' bytes at 'offset' from stream 'fname:sname' and * compare against buffer 'value' **/ static ssize_t read_stream(struct smb2_tree *tree, const char *location, struct torture_context *tctx, TALLOC_CTX *mem_ctx, const char *fname, const char *sname, off_t read_offset, size_t read_count) { struct smb2_handle handle; struct smb2_create create; struct smb2_read r; NTSTATUS status; const char *full_name; bool ret = true; full_name = talloc_asprintf(mem_ctx, "%s%s", fname, sname); if (full_name == NULL) { torture_comment(tctx, "talloc_asprintf error\n"); return -1; } ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_DATA; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = full_name; torture_comment(tctx, "Open stream %s\n", full_name); status = smb2_create(tree, mem_ctx, &create); if (!NT_STATUS_IS_OK(status)) { torture_comment(tctx, "Unable to open stream %s\n", full_name); return -1; } handle = create.out.file.handle; ZERO_STRUCT(r); r.in.file.handle = handle; r.in.length = read_count; r.in.offset = read_offset; status = smb2_read(tree, tree, &r); if (!NT_STATUS_IS_OK(status)) { CHECK_STATUS(status, NT_STATUS_END_OF_FILE); } smb2_util_close(tree, handle); done: if (ret == false) { return -1; } return r.out.data.length; } /** * Read 'count' bytes at 'offset' from stream 'fname:sname' and * compare against buffer 'value' **/ static bool write_stream(struct smb2_tree *tree, const char *location, struct torture_context *tctx, TALLOC_CTX *mem_ctx, const char *fname, const char *sname, off_t offset, size_t size, const char *value) { struct smb2_handle handle; struct smb2_create create; NTSTATUS status; const char *full_name; full_name = talloc_asprintf(mem_ctx, "%s%s", fname, sname ? sname : ""); if (full_name == NULL) { torture_comment(tctx, "talloc_asprintf error\n"); return false; } ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_WRITE_DATA; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; create.in.fname = full_name; status = smb2_create(tree, mem_ctx, &create); if (!NT_STATUS_IS_OK(status)) { if (value == NULL) { return true; } else { torture_comment(tctx, "Unable to open stream %s\n", full_name); sleep(10000000); return false; } } handle = create.out.file.handle; if (value == NULL) { return true; } status = smb2_util_write(tree, handle, value, offset, size); if (!NT_STATUS_IS_OK(status)) { torture_comment(tctx, "(%s) Failed to write %lu bytes to " "stream '%s'\n", location, (long)size, full_name); return false; } smb2_util_close(tree, handle); return true; } static bool torture_setup_local_xattr(struct torture_context *tctx, const char *path_option, const char *name, const char *xattr, const char *metadata, size_t size) { int ret = true; int result; const char *spath; char *path; spath = torture_setting_string(tctx, path_option, NULL); if (spath == NULL) { printf("No sharepath for option %s\n", path_option); return false; } path = talloc_asprintf(tctx, "%s/%s", spath, name); result = setxattr(path, xattr, metadata, size, 0); if (result != 0) { ret = false; } TALLOC_FREE(path); return ret; } /** * Create a file or directory **/ static bool torture_setup_file(TALLOC_CTX *mem_ctx, struct smb2_tree *tree, const char *name, bool dir) { struct smb2_create io; NTSTATUS status; smb2_util_unlink(tree, name); ZERO_STRUCT(io); io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE| NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE; io.in.create_options = 0; io.in.fname = name; if (dir) { io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io.in.share_access &= ~NTCREATEX_SHARE_ACCESS_DELETE; io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; io.in.create_disposition = NTCREATEX_DISP_CREATE; } status = smb2_create(tree, mem_ctx, &io); if (!NT_STATUS_IS_OK(status)) { return false; } status = smb2_util_close(tree, io.out.file.handle); if (!NT_STATUS_IS_OK(status)) { return false; } return true; } static bool enable_aapl(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); NTSTATUS status; bool ret = true; struct smb2_create io; DATA_BLOB data; struct smb2_create_blob *aapl = NULL; uint32_t aapl_server_caps; uint32_t expexted_scaps = (SMB2_CRTCTX_AAPL_UNIX_BASED | SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR | SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE | SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE); bool is_osx_server = torture_setting_bool(tctx, "osx", false); ZERO_STRUCT(io); io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; io.in.create_disposition = NTCREATEX_DISP_OPEN; io.in.share_access = (NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE); io.in.fname = ""; /* * Issuing an SMB2/CREATE with a suitably formed AAPL context, * controls behaviour of Apple's SMB2 extensions for the whole * session! */ data = data_blob_talloc(mem_ctx, NULL, 3 * sizeof(uint64_t)); SBVAL(data.data, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY); SBVAL(data.data, 8, (SMB2_CRTCTX_AAPL_SERVER_CAPS | SMB2_CRTCTX_AAPL_VOLUME_CAPS | SMB2_CRTCTX_AAPL_MODEL_INFO)); SBVAL(data.data, 16, (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR | SMB2_CRTCTX_AAPL_UNIX_BASED | SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE)); status = smb2_create_blob_add(tctx, &io.in.blobs, "AAPL", data); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create_blob_add"); status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create"); status = smb2_util_close(tree, io.out.file.handle); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close"); /* * Now check returned AAPL context */ torture_comment(tctx, "Comparing returned AAPL capabilities\n"); aapl = smb2_create_blob_find(&io.out.blobs, SMB2_CREATE_TAG_AAPL); torture_assert_goto(tctx, aapl != NULL, ret, done, "missing AAPL context"); if (!is_osx_server) { size_t exptected_aapl_ctx_size; exptected_aapl_ctx_size = strlen("MacSamba") * 2 + 40; torture_assert_goto( tctx, aapl->data.length == exptected_aapl_ctx_size, ret, done, "bad AAPL size"); } aapl_server_caps = BVAL(aapl->data.data, 16); torture_assert_goto(tctx, aapl_server_caps == expexted_scaps, ret, done, "bad AAPL caps"); done: talloc_free(mem_ctx); return ret; } static bool test_read_netatalk_metadata(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_read_metadata"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; ssize_t len; const char *localdir = NULL; torture_comment(tctx, "Checking metadata access\n"); localdir = torture_setting_string(tctx, "localdir", NULL); if (localdir == NULL) { torture_skip(tctx, "Need localdir for test"); } smb2_util_unlink(tree, fname); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { goto done; } ret = torture_setup_local_xattr(tctx, "localdir", BASEDIR "/torture_read_metadata", AFPINFO_EA_NETATALK, metadata_xattr, sizeof(metadata_xattr)); if (ret == false) { goto done; } ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 0, 4, "AFP"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, "BARRFOOO"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 16, 8, 0, 3, "AFP"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); /* Check reading offset and read size > sizeof(AFPINFO_STREAM) */ len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 61); CHECK_VALUE(len, 60); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 59, 2); CHECK_VALUE(len, 2); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 60, 1); CHECK_VALUE(len, 1); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 61, 1); CHECK_VALUE(len, 0); done: smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_read_afpinfo(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_read_metadata"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; ssize_t len; AfpInfo *info; const char *type_creator = "SMB,OLE!"; torture_comment(tctx, "Checking metadata access\n"); smb2_util_unlink(tree, fname); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed"); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed"); info = torture_afpinfo_new(mem_ctx); torture_assert_goto(tctx, info != NULL, ret, done, "torture_afpinfo_new failed"); memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); torture_assert_goto(tctx, ret == true, ret, done, "torture_write_afpinfo failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 0, 4, "AFP"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); /* * OS X ignores offset <= 60 and treats the as * offset=0. Reading from offsets > 60 returns EOF=0. */ ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 16, 8, 0, 8, "AFP\0\0\0\001\0"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 61); torture_assert_goto(tctx, len == 60, ret, done, "read_stream failed"); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 59, 2); torture_assert_goto(tctx, len == 2, ret, done, "read_stream failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 59, 2, 0, 2, "AF"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 60, 1); torture_assert_goto(tctx, len == 1, ret, done, "read_stream failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 60, 1, 0, 1, "A"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); len = read_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 61, 1); torture_assert_goto(tctx, len == 0, ret, done, "read_stream failed"); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_write_atalk_metadata(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_write_metadata"; const char *type_creator = "SMB,OLE!"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; AfpInfo *info; smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { goto done; } info = torture_afpinfo_new(mem_ctx); if (info == NULL) { goto done; } memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); ret &= check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_write_atalk_rfork_io(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_write_rfork_io"; const char *rfork = BASEDIR "\\torture_write_rfork_io" AFPRESOURCE_STREAM_NAME; const char *rfork_content = "1234567890"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; union smb_open io; struct smb2_handle filehandle; union smb_fileinfo finfo; union smb_setfileinfo sinfo; smb2_util_unlink(tree, fname); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { goto done; } torture_comment(tctx, "(%s) writing to resource fork\n", __location__); ret &= write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 10, 10, rfork_content); ret &= check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 0, 20, 10, 10, rfork_content); /* Check size after write */ ZERO_STRUCT(io); io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_ATTRIBUTE; io.smb2.in.fname = rfork; status = smb2_create(tree, mem_ctx, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_OK); filehandle = io.smb2.out.file.handle; torture_comment(tctx, "(%s) check resource fork size after write\n", __location__); ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; finfo.generic.in.file.handle = filehandle; status = smb2_getinfo_file(tree, mem_ctx, &finfo); CHECK_STATUS(status, NT_STATUS_OK); if (finfo.all_info.out.size != 20) { torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect resource fork size\n", __location__); ret = false; smb2_util_close(tree, filehandle); goto done; } smb2_util_close(tree, filehandle); /* Write at large offset */ torture_comment(tctx, "(%s) writing to resource fork at large offset\n", __location__); ret &= write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, (off_t)64*1024*1024, 10, rfork_content); ret &= check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, (off_t)64*1024*1024, 10, 0, 10, rfork_content); /* Truncate back to size of 1 byte */ torture_comment(tctx, "(%s) truncate resource fork and check size\n", __location__); ZERO_STRUCT(io); io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; io.smb2.in.desired_access = SEC_FILE_ALL; io.smb2.in.fname = rfork; status = smb2_create(tree, mem_ctx, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_OK); filehandle = io.smb2.out.file.handle; ZERO_STRUCT(sinfo); sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sinfo.end_of_file_info.in.file.handle = filehandle; sinfo.end_of_file_info.in.size = 1; status = smb2_setinfo_file(tree, &sinfo); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, filehandle); /* Now check size */ ZERO_STRUCT(io); io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_ATTRIBUTE; io.smb2.in.fname = rfork; status = smb2_create(tree, mem_ctx, &(io.smb2)); CHECK_STATUS(status, NT_STATUS_OK); filehandle = io.smb2.out.file.handle; ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; finfo.generic.in.file.handle = filehandle; status = smb2_getinfo_file(tree, mem_ctx, &finfo); CHECK_STATUS(status, NT_STATUS_OK); if (finfo.all_info.out.size != 1) { torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect resource fork size\n", __location__); ret = false; smb2_util_close(tree, filehandle); goto done; } smb2_util_close(tree, filehandle); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_rfork_truncate(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_rfork_truncate"; const char *rfork = BASEDIR "\\torture_rfork_truncate" AFPRESOURCE_STREAM; const char *rfork_content = "1234567890"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; struct smb2_create create; struct smb2_handle fh1, fh2, fh3; union smb_setfileinfo sinfo; ret = enable_aapl(tctx, tree); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); smb2_util_unlink(tree, fname); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { goto done; } ret &= write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM, 10, 10, rfork_content); /* Truncate back to size 0, further access MUST return ENOENT */ torture_comment(tctx, "(%s) truncate resource fork to size 0\n", __location__); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = fname; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create"); fh1 = create.out.file.handle; ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create"); fh2 = create.out.file.handle; ZERO_STRUCT(sinfo); sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sinfo.end_of_file_info.in.file.handle = fh2; sinfo.end_of_file_info.in.size = 0; status = smb2_setinfo_file(tree, &sinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_setinfo_file"); /* * Now check size, we should get OBJECT_NAME_NOT_FOUND (!) */ ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "smb2_create"); /* * Do another open on the rfork and write to the new handle. A * naive server might unlink the AppleDouble resource fork * file when its truncated to 0 bytes above, so in case both * open handles share the same underlying fd, the unlink would * cause the below write to be lost. */ ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create"); fh3 = create.out.file.handle; status = smb2_util_write(tree, fh3, "foo", 0, 3); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write"); smb2_util_close(tree, fh3); smb2_util_close(tree, fh2); smb2_util_close(tree, fh1); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM, 0, 3, 0, 3, "foo"); torture_assert_goto(tctx, ret == true, ret, done, "check_stream"); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_rfork_create(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_rfork_create"; const char *rfork = BASEDIR "\\torture_rfork_create" AFPRESOURCE_STREAM; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; struct smb2_create create; struct smb2_handle fh1; const char *streams[] = { "::$DATA" }; union smb_fileinfo finfo; ret = enable_aapl(tctx, tree); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); smb2_util_unlink(tree, fname); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { goto done; } torture_comment(tctx, "(%s) open rfork, should return ENOENT\n", __location__); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "smb2_create"); torture_comment(tctx, "(%s) create resource fork\n", __location__); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create"); fh1 = create.out.file.handle; torture_comment(tctx, "(%s) getinfo on create handle\n", __location__); ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; finfo.generic.in.file.handle = fh1; status = smb2_getinfo_file(tree, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file"); if (finfo.all_info.out.size != 0) { torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect resource fork size\n", __location__); ret = false; smb2_util_close(tree, fh1); goto done; } torture_comment(tctx, "(%s) open rfork, should still return ENOENT\n", __location__); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "smb2_create"); ret = check_stream_list(tree, tctx, fname, 1, streams, false); torture_assert_goto(tctx, ret == true, ret, done, "check_stream_list"); torture_comment(tctx, "(%s) close empty created rfork, open should return ENOENT\n", __location__); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.fname = rfork; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "smb2_create"); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_rfork_create_ro(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\torture_rfork_create"; const char *rfork = BASEDIR "\\torture_rfork_create" AFPRESOURCE_STREAM; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; struct smb2_create create; smb2_util_unlink(tree, fname); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, testdirh); ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { goto done; } torture_comment(tctx, "(%s) Try opening read-only with " "open_if create disposition, should work\n", __location__); ZERO_STRUCT(create); create.in.fname = rfork; create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; create.in.desired_access = SEC_FILE_READ_DATA | SEC_STD_READ_CONTROL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.share_access = FILE_SHARE_READ | FILE_SHARE_DELETE; status = smb2_create(tree, mem_ctx, &(create)); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); smb2_util_close(tree, create.out.file.handle); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_adouble_conversion(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\test_adouble_conversion"; const char *adname = BASEDIR "/._test_adouble_conversion"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; const char *data = "This resource fork intentionally left blank"; size_t datalen = strlen(data); const char *streams[] = { "::$DATA", AFPINFO_STREAM, AFPRESOURCE_STREAM, ":com.apple.metadata" "\xef\x80\xa2" "_kMDItemUserTags:$DATA", ":foo" "\xef\x80\xa2" "bar:$DATA", /* "foo:bar:$DATA" */ }; bool is_osx = torture_setting_bool(tctx, "osx", false); if (is_osx) { torture_skip(tctx, "Test only works with Samba\n"); } smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, testdirh); ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = torture_setup_file(tctx, tree, adname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = write_stream(tree, __location__, tctx, mem_ctx, adname, NULL, 0, sizeof(osx_adouble_w_xattr), osx_adouble_w_xattr); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed\n"); torture_comment(tctx, "(%s) test OS X AppleDouble conversion\n", __location__); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM, 16, datalen, 0, datalen, data); torture_assert_goto(tctx, ret == true, ret, done, "check AFPRESOURCE_STREAM failed\n"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, "TESTSLOW"); torture_assert_goto(tctx, ret == true, ret, done, "check AFPINFO_STREAM failed\n"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, ":foo" "\xef\x80\xa2" "bar:$DATA", /* "foo:bar:$DATA" */ 0, 3, 0, 3, "baz"); torture_assert_goto(tctx, ret == true, ret, done, "check foo:bar stream failed\n"); ret = check_stream_list(tree, tctx, fname, 5, streams, false); torture_assert_goto(tctx, ret == true, ret, done, "check_stream_list"); done: smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } /* * Test conversion of AppleDouble file without embedded xattr data */ static bool test_adouble_conversion_wo_xattr(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\test_adouble_conversion"; const char *adname = BASEDIR "/._test_adouble_conversion"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; const char *streams[] = { "::$DATA", AFPINFO_STREAM, AFPRESOURCE_STREAM }; struct smb2_create create; struct smb2_find find; unsigned int count; union smb_search_data *d; const char *data = "This resource fork intentionally left blank"; size_t datalen = strlen(data); bool is_osx = torture_setting_bool(tctx, "osx", false); if (is_osx) { torture_skip(tctx, "Test only works with Samba\n"); } smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed\n"); smb2_util_close(tree, testdirh); ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = torture_setup_file(tctx, tree, adname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = write_stream(tree, __location__, tctx, mem_ctx, adname, NULL, 0, sizeof(osx_adouble_without_xattr), osx_adouble_without_xattr); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed\n"); ret = enable_aapl(tctx, tree); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); /* * Issue a smb2_find(), this triggers the server-side conversion */ create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_DIR_READ, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.share_access = NTCREATEX_SHARE_ACCESS_READ, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = BASEDIR, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); find = (struct smb2_find) { .in.file.handle = create.out.file.handle, .in.pattern = "*", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, }; status = smb2_find_level(tree, tree, &find, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); status = smb2_util_close(tree, create.out.file.handle); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed"); /* * Check number of streams */ ret = check_stream_list(tree, tctx, fname, 3, streams, false); torture_assert_goto(tctx, ret == true, ret, done, "check_stream_list"); /* * Check Resourcefork data can be read. */ ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM, 16, datalen, 0, datalen, data); torture_assert_goto(tctx, ret == true, ret, done, "check AFPRESOURCE_STREAM failed\n"); /* * Check FinderInfo data has been migrated to stream. */ ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, "WAVEPTul"); torture_assert_goto(tctx, ret == true, ret, done, "check AFPINFO_STREAM failed\n"); done: smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_aapl(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\test_aapl"; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; struct smb2_create io; DATA_BLOB data; struct smb2_create_blob *aapl = NULL; AfpInfo *info; const char *type_creator = "SMB,OLE!"; char type_creator_buf[9]; uint32_t aapl_cmd; uint32_t aapl_reply_bitmap; uint32_t aapl_server_caps; uint32_t aapl_vol_caps; uint32_t expected_vol_caps = 0; char *model; struct smb2_find f; unsigned int count; union smb_search_data *d; uint64_t rfork_len; bool is_osx_server = torture_setting_bool(tctx, "osx", false); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, testdirh); ZERO_STRUCT(io); io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; io.in.share_access = (NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE); io.in.fname = fname; /* * Issuing an SMB2/CREATE with a suitably formed AAPL context, * controls behaviour of Apple's SMB2 extensions for the whole * session! */ data = data_blob_talloc(mem_ctx, NULL, 3 * sizeof(uint64_t)); SBVAL(data.data, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY); SBVAL(data.data, 8, (SMB2_CRTCTX_AAPL_SERVER_CAPS | SMB2_CRTCTX_AAPL_VOLUME_CAPS | SMB2_CRTCTX_AAPL_MODEL_INFO)); SBVAL(data.data, 16, (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR | SMB2_CRTCTX_AAPL_UNIX_BASED | SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE)); torture_comment(tctx, "Testing SMB2 create context AAPL\n"); status = smb2_create_blob_add(tctx, &io.in.blobs, "AAPL", data); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_close(tree, io.out.file.handle); CHECK_STATUS(status, NT_STATUS_OK); /* * Now check returned AAPL context */ torture_comment(tctx, "Comparing returned AAPL capabilities\n"); aapl = smb2_create_blob_find(&io.out.blobs, SMB2_CREATE_TAG_AAPL); if (aapl == NULL) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpectedly no AAPL capabilities were returned.", __location__); ret = false; goto done; } if (!is_osx_server) { size_t expected_aapl_ctx_size; bool size_ok; /* * uint32_t CommandCode = kAAPL_SERVER_QUERY * uint32_t Reserved = 0; * uint64_t ReplyBitmap = kAAPL_SERVER_CAPS | * kAAPL_VOLUME_CAPS | * kAAPL_MODEL_INFO; * uint64_t ServerCaps = kAAPL_SUPPORTS_READDIR_ATTR | * kAAPL_SUPPORTS_OSX_COPYFILE; * uint64_t VolumeCaps = kAAPL_SUPPORT_RESOLVE_ID | * kAAPL_CASE_SENSITIVE; * uint32_t Pad2 = 0; * uint32_t ModelStringLen = 10; * ucs2_t ModelString[5] = "MacSamba"; */ expected_aapl_ctx_size = strlen("MacSamba") * 2 + 40; size_ok = aapl->data.length == expected_aapl_ctx_size; torture_assert_goto(tctx, size_ok, ret, done, "bad AAPL size"); } aapl_cmd = IVAL(aapl->data.data, 0); if (aapl_cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpected cmd: %d", __location__, (int)aapl_cmd); ret = false; goto done; } aapl_reply_bitmap = BVAL(aapl->data.data, 8); if (aapl_reply_bitmap != (SMB2_CRTCTX_AAPL_SERVER_CAPS | SMB2_CRTCTX_AAPL_VOLUME_CAPS | SMB2_CRTCTX_AAPL_MODEL_INFO)) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpected reply_bitmap: %d", __location__, (int)aapl_reply_bitmap); ret = false; goto done; } aapl_server_caps = BVAL(aapl->data.data, 16); if (aapl_server_caps != (SMB2_CRTCTX_AAPL_UNIX_BASED | SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR | SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE | SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE)) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpected server_caps: %d", __location__, (int)aapl_server_caps); ret = false; goto done; } if (is_osx_server) { expected_vol_caps = 5; } aapl_vol_caps = BVAL(aapl->data.data, 24); if (aapl_vol_caps != expected_vol_caps) { /* this will fail on a case insensitive fs ... */ torture_result(tctx, TORTURE_FAIL, "(%s) unexpected vol_caps: %d", __location__, (int)aapl_vol_caps); } ret = convert_string_talloc(mem_ctx, CH_UTF16LE, CH_UNIX, aapl->data.data + 40, 10, &model, NULL); if (ret == false) { torture_result(tctx, TORTURE_FAIL, "(%s) convert_string_talloc() failed", __location__); goto done; } torture_comment(tctx, "Got server model: \"%s\"\n", model); /* * Now that Requested AAPL extensions are enabled, setup some * Mac files with metadata and resource fork */ ret = torture_setup_file(mem_ctx, tree, fname, false); if (ret == false) { torture_result(tctx, TORTURE_FAIL, "(%s) torture_setup_file() failed", __location__); goto done; } info = torture_afpinfo_new(mem_ctx); if (info == NULL) { torture_result(tctx, TORTURE_FAIL, "(%s) torture_afpinfo_new() failed", __location__); ret = false; goto done; } memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); if (ret == false) { torture_result(tctx, TORTURE_FAIL, "(%s) torture_write_afpinfo() failed", __location__); goto done; } ret = write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 0, 3, "foo"); if (ret == false) { torture_result(tctx, TORTURE_FAIL, "(%s) write_stream() failed", __location__); goto done; } /* * Ok, file is prepared, now call smb2/find */ ZERO_STRUCT(io); io.in.desired_access = SEC_RIGHTS_DIR_READ; io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; io.in.share_access = (NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE); io.in.create_disposition = NTCREATEX_DISP_OPEN; io.in.fname = BASEDIR; status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(f); f.in.file.handle = io.out.file.handle; f.in.pattern = "test_aapl"; f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; f.in.max_response_size = 0x1000; f.in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO; status = smb2_find_level(tree, tree, &f, &count, &d); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_close(tree, io.out.file.handle); CHECK_STATUS(status, NT_STATUS_OK); if (strcmp(d[0].id_both_directory_info.name.s, "test_aapl") != 0) { torture_result(tctx, TORTURE_FAIL, "(%s) write_stream() failed", __location__); ret = false; goto done; } if (d[0].id_both_directory_info.short_name.private_length != 24) { torture_result(tctx, TORTURE_FAIL, "(%s) bad short_name length %" PRIu32 ", expected 24", __location__, d[0].id_both_directory_info.short_name.private_length); ret = false; goto done; } torture_comment(tctx, "short_name buffer:\n"); dump_data(0, d[0].id_both_directory_info.short_name_buf, 24); /* * Extract data as specified by the AAPL extension: * - ea_size contains max_access * - short_name contains resource fork length + FinderInfo * - reserved2 contains the unix mode */ torture_comment(tctx, "mac_access: %" PRIx32 "\n", d[0].id_both_directory_info.ea_size); rfork_len = BVAL(d[0].id_both_directory_info.short_name_buf, 0); if (rfork_len != 3) { torture_result(tctx, TORTURE_FAIL, "(%s) expected resource fork length 3, got: %" PRIu64, __location__, rfork_len); ret = false; goto done; } memcpy(type_creator_buf, d[0].id_both_directory_info.short_name_buf + 8, 8); type_creator_buf[8] = 0; if (strcmp(type_creator, type_creator_buf) != 0) { torture_result(tctx, TORTURE_FAIL, "(%s) expected type/creator \"%s\" , got: %s", __location__, type_creator, type_creator_buf); ret = false; goto done; } done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static uint64_t patt_hash(uint64_t off) { return off; } static bool write_pattern(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *mem_ctx, struct smb2_handle h, uint64_t off, uint64_t len, uint64_t patt_off) { NTSTATUS status; uint64_t i; uint8_t *buf; uint64_t io_sz = MIN(1024 * 64, len); if (len == 0) { return true; } torture_assert(torture, (len % 8) == 0, "invalid write len"); buf = talloc_zero_size(mem_ctx, io_sz); torture_assert(torture, (buf != NULL), "no memory for file data buf"); while (len > 0) { for (i = 0; i <= io_sz - 8; i += 8) { SBVAL(buf, i, patt_hash(patt_off)); patt_off += 8; } status = smb2_util_write(tree, h, buf, off, io_sz); torture_assert_ntstatus_ok(torture, status, "file write"); len -= io_sz; off += io_sz; } talloc_free(buf); return true; } static bool check_pattern(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *mem_ctx, struct smb2_handle h, uint64_t off, uint64_t len, uint64_t patt_off) { if (len == 0) { return true; } torture_assert(torture, (len % 8) == 0, "invalid read len"); while (len > 0) { uint64_t i; struct smb2_read r; NTSTATUS status; uint64_t io_sz = MIN(1024 * 64, len); ZERO_STRUCT(r); r.in.file.handle = h; r.in.length = io_sz; r.in.offset = off; status = smb2_read(tree, mem_ctx, &r); torture_assert_ntstatus_ok(torture, status, "read"); torture_assert_u64_equal(torture, r.out.data.length, io_sz, "read data len mismatch"); for (i = 0; i <= io_sz - 8; i += 8, patt_off += 8) { uint64_t data = BVAL(r.out.data.data, i); torture_assert_u64_equal(torture, data, patt_hash(patt_off), talloc_asprintf(torture, "read data " "pattern bad at %llu\n", (unsigned long long)off + i)); } talloc_free(r.out.data.data); len -= io_sz; off += io_sz; } return true; } static bool test_setup_open(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *mem_ctx, const char *fname, struct smb2_handle *fh, uint32_t desired_access, uint32_t file_attributes) { struct smb2_create io; NTSTATUS status; ZERO_STRUCT(io); io.in.desired_access = desired_access; io.in.file_attributes = file_attributes; io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE| NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE; if (file_attributes & FILE_ATTRIBUTE_DIRECTORY) { io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; } io.in.fname = fname; status = smb2_create(tree, mem_ctx, &io); torture_assert_ntstatus_ok(torture, status, "file create"); *fh = io.out.file.handle; return true; } static bool test_setup_create_fill(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *mem_ctx, const char *fname, struct smb2_handle *fh, uint64_t size, uint32_t desired_access, uint32_t file_attributes) { bool ok; ok = test_setup_open(torture, tree, mem_ctx, fname, fh, desired_access, file_attributes); torture_assert(torture, ok, "file open"); if (size > 0) { ok = write_pattern(torture, tree, mem_ctx, *fh, 0, size, 0); torture_assert(torture, ok, "write pattern"); } return true; } static bool test_setup_copy_chunk(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *mem_ctx, uint32_t nchunks, const char *src_name, struct smb2_handle *src_h, uint64_t src_size, uint32_t src_desired_access, const char *dst_name, struct smb2_handle *dest_h, uint64_t dest_size, uint32_t dest_desired_access, struct srv_copychunk_copy *cc_copy, union smb_ioctl *io) { struct req_resume_key_rsp res_key; bool ok; NTSTATUS status; enum ndr_err_code ndr_ret; ok = test_setup_create_fill(torture, tree, mem_ctx, src_name, src_h, src_size, src_desired_access, FILE_ATTRIBUTE_NORMAL); torture_assert(torture, ok, "src file create fill"); ok = test_setup_create_fill(torture, tree, mem_ctx, dst_name, dest_h, dest_size, dest_desired_access, FILE_ATTRIBUTE_NORMAL); torture_assert(torture, ok, "dest file create fill"); ZERO_STRUCTPN(io); io->smb2.level = RAW_IOCTL_SMB2; io->smb2.in.file.handle = *src_h; io->smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; /* Allow for Key + ContextLength + Context */ io->smb2.in.max_response_size = 32; io->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; status = smb2_ioctl(tree, mem_ctx, &io->smb2); torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); ndr_ret = ndr_pull_struct_blob(&io->smb2.out.out, mem_ctx, &res_key, (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); torture_assert_ndr_success(torture, ndr_ret, "ndr_pull_req_resume_key_rsp"); ZERO_STRUCTPN(io); io->smb2.level = RAW_IOCTL_SMB2; io->smb2.in.file.handle = *dest_h; io->smb2.in.function = FSCTL_SRV_COPYCHUNK; io->smb2.in.max_response_size = sizeof(struct srv_copychunk_rsp); io->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; ZERO_STRUCTPN(cc_copy); memcpy(cc_copy->source_key, res_key.resume_key, ARRAY_SIZE(cc_copy->source_key)); cc_copy->chunk_count = nchunks; cc_copy->chunks = talloc_zero_array(mem_ctx, struct srv_copychunk, nchunks); torture_assert(torture, (cc_copy->chunks != NULL), "no memory for chunks"); return true; } static bool check_copy_chunk_rsp(struct torture_context *torture, struct srv_copychunk_rsp *cc_rsp, uint32_t ex_chunks_written, uint32_t ex_chunk_bytes_written, uint32_t ex_total_bytes_written) { torture_assert_int_equal(torture, cc_rsp->chunks_written, ex_chunks_written, "num chunks"); torture_assert_int_equal(torture, cc_rsp->chunk_bytes_written, ex_chunk_bytes_written, "chunk bytes written"); torture_assert_int_equal(torture, cc_rsp->total_bytes_written, ex_total_bytes_written, "chunk total bytes"); return true; } static bool neg_aapl_copyfile(struct torture_context *tctx, struct smb2_tree *tree, uint64_t flags) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = "aapl"; NTSTATUS status; struct smb2_create io; DATA_BLOB data; struct smb2_create_blob *aapl = NULL; uint32_t aapl_cmd; uint32_t aapl_reply_bitmap; uint32_t aapl_server_caps; bool ret = true; ZERO_STRUCT(io); io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; io.in.share_access = (NTCREATEX_SHARE_ACCESS_DELETE | NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE); io.in.fname = fname; data = data_blob_talloc(mem_ctx, NULL, 3 * sizeof(uint64_t)); SBVAL(data.data, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY); SBVAL(data.data, 8, (SMB2_CRTCTX_AAPL_SERVER_CAPS)); SBVAL(data.data, 16, flags); status = smb2_create_blob_add(tctx, &io.in.blobs, "AAPL", data); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_create(tree, tctx, &io); CHECK_STATUS(status, NT_STATUS_OK); aapl = smb2_create_blob_find(&io.out.blobs, SMB2_CREATE_TAG_AAPL); if (aapl == NULL) { ret = false; goto done; } if (aapl->data.length < 24) { ret = false; goto done; } aapl_cmd = IVAL(aapl->data.data, 0); if (aapl_cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpected cmd: %d", __location__, (int)aapl_cmd); ret = false; goto done; } aapl_reply_bitmap = BVAL(aapl->data.data, 8); if (!(aapl_reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS)) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpected reply_bitmap: %d", __location__, (int)aapl_reply_bitmap); ret = false; goto done; } aapl_server_caps = BVAL(aapl->data.data, 16); if (!(aapl_server_caps & flags)) { torture_result(tctx, TORTURE_FAIL, "(%s) unexpected server_caps: %d", __location__, (int)aapl_server_caps); ret = false; goto done; } done: status = smb2_util_close(tree, io.out.file.handle); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_unlink(tree, "aapl"); talloc_free(mem_ctx); return ret; } static bool test_copyfile(struct torture_context *torture, struct smb2_tree *tree) { struct smb2_handle src_h; struct smb2_handle dest_h; NTSTATUS status; union smb_ioctl io; TALLOC_CTX *tmp_ctx = talloc_new(tree); struct srv_copychunk_copy cc_copy; struct srv_copychunk_rsp cc_rsp; enum ndr_err_code ndr_ret; bool ok; const char *sname = ":foo" "\xef\x80\xa2" "bar:$DATA"; /* * First test a copy_chunk with a 0 chunk count without having * enabled this via AAPL. The request must not fail and the * copied length in the response must be 0. This is verified * against Windows 2008r2. */ ok = test_setup_copy_chunk(torture, tree, tmp_ctx, 0, /* 0 chunks, copyfile semantics */ FNAME_CC_SRC, &src_h, 4096, /* fill 4096 byte src file */ SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, FNAME_CC_DST, &dest_h, 0, /* 0 byte dest file */ SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, &cc_copy, &io); if (!ok) { torture_fail_goto(torture, done, "setup copy chunk error"); } ndr_ret = ndr_push_struct_blob(&io.smb2.in.out, tmp_ctx, &cc_copy, (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); torture_assert_ndr_success(torture, ndr_ret, "ndr_push_srv_copychunk_copy"); status = smb2_ioctl(tree, tmp_ctx, &io.smb2); torture_assert_ntstatus_ok_goto(torture, status, ok, done, "FSCTL_SRV_COPYCHUNK"); ndr_ret = ndr_pull_struct_blob(&io.smb2.out.out, tmp_ctx, &cc_rsp, (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); torture_assert_ndr_success(torture, ndr_ret, "ndr_pull_srv_copychunk_rsp"); ok = check_copy_chunk_rsp(torture, &cc_rsp, 0, /* chunks written */ 0, /* chunk bytes unsuccessfully written */ 0); /* total bytes written */ if (!ok) { torture_fail_goto(torture, done, "bad copy chunk response data"); } /* * Now enable AAPL copyfile and test again, the file and the * stream must be copied by the server. */ ok = neg_aapl_copyfile(torture, tree, SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE); if (!ok) { torture_skip_goto(torture, done, "missing AAPL copyfile"); goto done; } smb2_util_close(tree, src_h); smb2_util_close(tree, dest_h); smb2_util_unlink(tree, FNAME_CC_SRC); smb2_util_unlink(tree, FNAME_CC_DST); ok = torture_setup_file(tmp_ctx, tree, FNAME_CC_SRC, false); if (!ok) { torture_fail(torture, "setup file error"); } ok = write_stream(tree, __location__, torture, tmp_ctx, FNAME_CC_SRC, AFPRESOURCE_STREAM, 10, 10, "1234567890"); if (!ok) { torture_fail(torture, "setup stream error"); } ok = write_stream(tree, __location__, torture, tmp_ctx, FNAME_CC_SRC, sname, 10, 10, "abcdefghij"); torture_assert_goto(torture, ok == true, ok, done, "write_stream failed\n"); ok = test_setup_copy_chunk(torture, tree, tmp_ctx, 0, /* 0 chunks, copyfile semantics */ FNAME_CC_SRC, &src_h, 4096, /* fill 4096 byte src file */ SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, FNAME_CC_DST, &dest_h, 0, /* 0 byte dest file */ SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, &cc_copy, &io); if (!ok) { torture_fail_goto(torture, done, "setup copy chunk error"); } ndr_ret = ndr_push_struct_blob(&io.smb2.in.out, tmp_ctx, &cc_copy, (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); torture_assert_ndr_success(torture, ndr_ret, "ndr_push_srv_copychunk_copy"); status = smb2_ioctl(tree, tmp_ctx, &io.smb2); torture_assert_ntstatus_ok_goto(torture, status, ok, done, "FSCTL_SRV_COPYCHUNK"); ndr_ret = ndr_pull_struct_blob(&io.smb2.out.out, tmp_ctx, &cc_rsp, (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); torture_assert_ndr_success(torture, ndr_ret, "ndr_pull_srv_copychunk_rsp"); ok = check_copy_chunk_rsp(torture, &cc_rsp, 0, /* chunks written */ 0, /* chunk bytes unsuccessfully written */ 4096); /* total bytes written */ if (!ok) { torture_fail_goto(torture, done, "bad copy chunk response data"); } ok = test_setup_open(torture, tree, tmp_ctx, FNAME_CC_DST, &dest_h, SEC_FILE_READ_DATA, FILE_ATTRIBUTE_NORMAL); if (!ok) { torture_fail_goto(torture, done,"open failed"); } ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); if (!ok) { torture_fail_goto(torture, done, "inconsistent file data"); } ok = check_stream(tree, __location__, torture, tmp_ctx, FNAME_CC_DST, AFPRESOURCE_STREAM, 0, 20, 10, 10, "1234567890"); if (!ok) { torture_fail_goto(torture, done, "inconsistent stream data"); } ok = check_stream(tree, __location__, torture, tmp_ctx, FNAME_CC_DST, sname, 0, 20, 10, 10, "abcdefghij"); torture_assert_goto(torture, ok == true, ok, done, "check_stream failed\n"); done: smb2_util_close(tree, src_h); smb2_util_close(tree, dest_h); smb2_util_unlink(tree, FNAME_CC_SRC); smb2_util_unlink(tree, FNAME_CC_DST); talloc_free(tmp_ctx); return true; } static bool check_stream_list(struct smb2_tree *tree, struct torture_context *tctx, const char *fname, int num_exp, const char **exp, bool is_dir) { bool ret = true; union smb_fileinfo finfo; NTSTATUS status; int i; TALLOC_CTX *tmp_ctx = talloc_new(tctx); char **exp_sort; struct stream_struct *stream_sort; struct smb2_create create; struct smb2_handle h; ZERO_STRUCT(h); torture_assert_goto(tctx, tmp_ctx != NULL, ret, done, "talloc_new failed"); ZERO_STRUCT(create); create.in.fname = fname; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.create_options = is_dir ? NTCREATEX_OPTIONS_DIRECTORY : 0; create.in.file_attributes = is_dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; status = smb2_create(tree, tmp_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create"); h = create.out.file.handle; finfo.generic.level = RAW_FILEINFO_STREAM_INFORMATION; finfo.generic.in.file.handle = h; status = smb2_getinfo_file(tree, tctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "get stream info"); smb2_util_close(tree, h); torture_assert_int_equal_goto(tctx, finfo.stream_info.out.num_streams, num_exp, ret, done, "stream count"); if (num_exp == 0) { TALLOC_FREE(tmp_ctx); goto done; } exp_sort = talloc_memdup(tmp_ctx, exp, num_exp * sizeof(*exp)); torture_assert_goto(tctx, exp_sort != NULL, ret, done, __location__); TYPESAFE_QSORT(exp_sort, num_exp, qsort_string); stream_sort = talloc_memdup(tmp_ctx, finfo.stream_info.out.streams, finfo.stream_info.out.num_streams * sizeof(*stream_sort)); torture_assert_goto(tctx, stream_sort != NULL, ret, done, __location__); TYPESAFE_QSORT(stream_sort, finfo.stream_info.out.num_streams, qsort_stream); for (i=0; iafpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); torture_assert_goto(tctx, ret == true, ret, done, "torture_write_afpinfo failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); torture_assert_goto(tctx, ret == true, ret, done, "Bad type/creator in AFP_AfpInfo"); ret = check_stream_list(tree, tctx, fname, 2, streams_afpinfo, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE; create.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_STD_SYNCHRONIZE | SEC_STD_DELETE; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; smb2_util_close(tree, h1); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Got unexpected AFP_AfpInfo stream"); ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } static bool test_setinfo_delete_on_close(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; union smb_setfileinfo sfinfo; struct smb2_handle h1; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file" AFPINFO_STREAM_NAME; const char *type_creator = "SMB,OLE!"; AfpInfo *info = NULL; const char *streams[] = { AFPINFO_STREAM, "::$DATA" }; const char *streams_basic[] = { "::$DATA" }; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new"); torture_comment(tctx, "Deleting AFP_AfpInfo via setinfo with delete-on-close\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, h1); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); info = torture_afpinfo_new(mem_ctx); torture_assert_goto(tctx, info != NULL, ret, done, "torture_afpinfo_new failed"); memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); torture_assert_goto(tctx, ret == true, ret, done, "torture_write_afpinfo failed"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_STD_SYNCHRONIZE | SEC_STD_DELETE; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; /* Delete stream via setinfo delete-on-close */ ZERO_STRUCT(sfinfo); sfinfo.disposition_info.in.delete_on_close = 1; sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; sfinfo.generic.in.file.handle = h1; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set delete-on-close failed"); ret = check_stream_list(tree, tctx, fname, 2, streams, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_DELETE_PENDING, ret, done, "Got unexpected AFP_AfpInfo stream"); smb2_util_close(tree, h1); ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Got unexpected AFP_AfpInfo stream"); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } static bool test_setinfo_eof(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; union smb_setfileinfo sfinfo; struct smb2_handle h1; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file" AFPINFO_STREAM_NAME; const char *type_creator = "SMB,OLE!"; AfpInfo *info = NULL; const char *streams_afpinfo[] = { "::$DATA", AFPINFO_STREAM }; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new"); torture_comment(tctx, "Set AFP_AfpInfo EOF to 61, 1 and 0\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, h1); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); info = torture_afpinfo_new(mem_ctx); torture_assert_goto(tctx, info != NULL, ret, done, "torture_afpinfo_new failed"); memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); torture_assert_goto(tctx, ret == true, ret, done, "torture_write_afpinfo failed"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; torture_comment(tctx, "Set AFP_AfpInfo EOF to 61\n"); /* Test setinfo end-of-file info */ ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 61; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ALLOTTED_SPACE_EXCEEDED, ret, done, "set eof 61 failed"); torture_comment(tctx, "Set AFP_AfpInfo EOF to 1\n"); /* Truncation returns success, but has no effect */ ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 1; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 1 failed"); smb2_util_close(tree, h1); ret = check_stream_list(tree, tctx, fname, 2, streams_afpinfo, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); torture_assert_goto(tctx, ret == true, ret, done, "FinderInfo changed"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; /* * Delete stream via setinfo end-of-file info to 0, should * return success but stream MUST NOT deleted */ ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 0; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed"); smb2_util_close(tree, h1); ret = check_stream_list(tree, tctx, fname, 2, streams_afpinfo, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); torture_assert_goto(tctx, ret == true, ret, done, "FinderInfo changed"); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } static bool test_afpinfo_all0(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; struct smb2_handle h1 = {{0}}; struct smb2_handle baseh = {{0}}; union smb_setfileinfo setfinfo; union smb_fileinfo getfinfo; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file" AFPINFO_STREAM; const char *type_creator = "SMB,OLE!"; AfpInfo *info = NULL; char *infobuf = NULL; const char *streams_basic[] = { "::$DATA" }; const char *streams_afpinfo[] = { "::$DATA", AFPINFO_STREAM }; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new"); torture_comment(tctx, "Write all 0 to AFP_AfpInfo and see what happens\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, h1); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); info = torture_afpinfo_new(mem_ctx); torture_assert_goto(tctx, info != NULL, ret, done, "torture_afpinfo_new failed"); memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); torture_assert_goto(tctx, ret == true, ret, done, "torture_write_afpinfo failed"); ret = check_stream_list(tree, tctx, fname, 2, streams_afpinfo, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); /* Write all 0 to AFP_AfpInfo */ memset(info->afpi_FinderInfo, 0, AFP_FinderSize); infobuf = torture_afpinfo_pack(mem_ctx, info); torture_assert_not_null_goto(tctx, infobuf, ret, done, "torture_afpinfo_pack failed\n"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.fname = fname; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); baseh = create.out.file.handle; ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; create.in.fname = sname; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); h1 = create.out.file.handle; status = smb2_util_write(tree, h1, infobuf, 0, AFP_INFO_SIZE); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); /* * Get stream information on open handle, must return only default * stream, the AFP_AfpInfo stream must not be returned. */ ZERO_STRUCT(getfinfo); getfinfo.generic.level = RAW_FILEINFO_STREAM_INFORMATION; getfinfo.generic.in.file.handle = baseh; status = smb2_getinfo_file(tree, tctx, &getfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "get stream info\n"); torture_assert_int_equal_goto(tctx, getfinfo.stream_info.out.num_streams, 1, ret, done, "stream count"); smb2_util_close(tree, baseh); ZERO_STRUCT(baseh); /* * Try to set some file-basic-info (time) on the stream. This catches * naive implementation mistakes that simply deleted the backing store * from the filesystem in the zero-out step. */ ZERO_STRUCT(setfinfo); unix_to_nt_time(&setfinfo.basic_info.in.write_time, time(NULL)); setfinfo.basic_info.in.attrib = 0x20; setfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; setfinfo.generic.in.file.handle = h1; status = smb2_setinfo_file(tree, &setfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "check_stream_list"); smb2_util_close(tree, h1); ZERO_STRUCT(h1); ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); done: if (!smb2_util_handle_empty(h1)) { smb2_util_close(tree, h1); } if (!smb2_util_handle_empty(baseh)) { smb2_util_close(tree, baseh); } smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } static bool test_create_delete_on_close_resource(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; struct smb2_handle h1; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file" AFPRESOURCE_STREAM_NAME; const char *streams_basic[] = { "::$DATA" }; const char *streams_afpresource[] = { "::$DATA", AFPRESOURCE_STREAM }; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new"); torture_comment(tctx, "Checking whether create with delete-on-close is ignored for AFP_AfpResource\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok(tctx, status, "torture_smb2_testdir"); smb2_util_close(tree, h1); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); torture_comment(tctx, "Opening not existing AFP_AfpResource\n"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; /* stat open */ create.in.fname = sname; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Got unexpected AFP_AfpResource stream"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Got unexpected AFP_AfpResource stream"); ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); torture_comment(tctx, "Trying to delete AFP_AfpResource via create with delete-on-close\n"); ret = write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 0, 10, "1234567890"); torture_assert_goto(tctx, ret == true, ret, done, "Writing to AFP_AfpResource failed"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 0, 10, 0, 10, "1234567890"); torture_assert_goto(tctx, ret == true, ret, done, "Bad content from AFP_AfpResource"); ret = check_stream_list(tree, tctx, fname, 2, streams_afpresource, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE; create.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_STD_SYNCHRONIZE | SEC_STD_DELETE; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; smb2_util_close(tree, h1); ret = check_stream_list(tree, tctx, fname, 2, streams_afpresource, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 0, 10, 0, 10, "1234567890"); torture_assert_goto(tctx, ret == true, ret, done, "Bad content from AFP_AfpResource"); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } static bool test_setinfo_delete_on_close_resource(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; union smb_setfileinfo sfinfo; struct smb2_handle h1; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file" AFPRESOURCE_STREAM_NAME; const char *streams_afpresource[] = { "::$DATA", AFPRESOURCE_STREAM }; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new"); torture_comment(tctx, "Trying to delete AFP_AfpResource via setinfo with delete-on-close\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, h1); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); ret = write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 10, 10, "1234567890"); torture_assert_goto(tctx, ret == true, ret, done, "Writing to AFP_AfpResource failed"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_READ_ATTRIBUTE | SEC_STD_SYNCHRONIZE | SEC_STD_DELETE; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; /* Try to delete stream via setinfo delete-on-close */ ZERO_STRUCT(sfinfo); sfinfo.disposition_info.in.delete_on_close = 1; sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; sfinfo.generic.in.file.handle = h1; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set delete-on-close failed"); smb2_util_close(tree, h1); ret = check_stream_list(tree, tctx, fname, 2, streams_afpresource, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Got unexpected AFP_AfpResource stream"); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } static bool test_setinfo_eof_resource(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; union smb_setfileinfo sfinfo; union smb_fileinfo finfo; struct smb2_handle h1; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file" AFPRESOURCE_STREAM_NAME; const char *streams_basic[] = { "::$DATA" }; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new"); ret = enable_aapl(tctx, tree); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); torture_comment(tctx, "Set AFP_AfpResource EOF to 1 and 0\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir"); smb2_util_close(tree, h1); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); ret = write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 10, 10, "1234567890"); torture_assert_goto(tctx, ret == true, ret, done, "Writing to AFP_AfpResource failed"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; torture_comment(tctx, "Set AFP_AfpResource EOF to 1\n"); /* Test setinfo end-of-file info */ ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 1; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 1 failed"); smb2_util_close(tree, h1); /* Check size == 1 */ ZERO_STRUCT(create); create.in.fname = sname; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; finfo.generic.in.file.handle = h1; status = smb2_getinfo_file(tree, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed"); smb2_util_close(tree, h1); torture_assert_goto(tctx, finfo.all_info.out.size == 1, ret, done, "size != 1"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; /* * Delete stream via setinfo end-of-file info to 0, this * should delete the stream. */ ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 0; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed"); smb2_util_close(tree, h1); ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ZERO_STRUCT(create); create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.desired_access = SEC_FILE_ALL; create.in.fname = sname; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; status = smb2_create(tree, mem_ctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "smb2_create failed"); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } /* * This tests that right after creating the AFP_AfpInfo stream, * reading from the stream returns an empty, default metadata blob of * 60 bytes. * * NOTE: against OS X SMB server this only works if the read request * is compounded with the create that created the stream, is fails * otherwise. We don't care... */ static bool test_null_afpinfo(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = "test_null_afpinfo"; const char *sname = "test_null_afpinfo" AFPINFO_STREAM_NAME; NTSTATUS status; bool ret = true; struct smb2_request *req[3]; struct smb2_handle handle; struct smb2_create create; struct smb2_read read; AfpInfo *afpinfo = NULL; char *afpinfo_buf = NULL; const char *type_creator = "SMB,OLE!"; struct smb2_handle handle2; struct smb2_read r; torture_comment(tctx, "Checking create of AfpInfo stream\n"); smb2_util_unlink(tree, fname); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA; create.in.share_access = FILE_SHARE_READ | FILE_SHARE_DELETE; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; create.in.fname = sname; smb2_transport_compound_start(tree->session->transport, 2); req[0] = smb2_create_send(tree, &create); handle.data[0] = UINT64_MAX; handle.data[1] = UINT64_MAX; smb2_transport_compound_set_related(tree->session->transport, true); ZERO_STRUCT(read); read.in.file.handle = handle; read.in.length = AFP_INFO_SIZE; req[1] = smb2_read_send(tree, &read); status = smb2_create_recv(req[0], tree, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create_recv failed"); handle = create.out.file.handle; status = smb2_read_recv(req[1], tree, &read); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_read_recv failed"); status = torture_smb2_testfile_access(tree, sname, &handle2, SEC_FILE_READ_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); r = (struct smb2_read) { .in.file.handle = handle2, .in.length = AFP_INFO_SIZE, }; status = smb2_read(tree, tree, &r); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); smb2_util_close(tree, handle2); afpinfo = torture_afpinfo_new(mem_ctx); torture_assert_goto(tctx, afpinfo != NULL, ret, done, "torture_afpinfo_new failed"); memcpy(afpinfo->afpi_FinderInfo, type_creator, 8); afpinfo_buf = torture_afpinfo_pack(tctx, afpinfo); torture_assert_goto(tctx, afpinfo_buf != NULL, ret, done, "torture_afpinfo_new failed"); status = smb2_util_write(tree, handle, afpinfo_buf, 0, AFP_INFO_SIZE); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed"); smb2_util_close(tree, handle); ret = check_stream(tree, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed"); done: smb2_util_unlink(tree, fname); talloc_free(mem_ctx); return ret; } static bool test_delete_file_with_rfork(struct torture_context *tctx, struct smb2_tree *tree) { const char *fname = "torture_write_rfork_io"; const char *rfork_content = "1234567890"; NTSTATUS status; bool ret = true; smb2_util_unlink(tree, fname); torture_comment(tctx, "Test deleting file with resource fork\n"); ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = write_stream(tree, __location__, tctx, tctx, fname, AFPRESOURCE_STREAM_NAME, 10, 10, rfork_content); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed\n"); ret = check_stream(tree, __location__, tctx, tctx, fname, AFPRESOURCE_STREAM_NAME, 0, 20, 10, 10, rfork_content); torture_assert_goto(tctx, ret == true, ret, done, "check_stream failed\n"); status = smb2_util_unlink(tree, fname); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "check_stream failed\n"); done: return ret; } static bool test_rename_and_read_rsrc(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create, create2; struct smb2_handle h1, h2; const char *fname = "test_rename_openfile"; const char *sname = "test_rename_openfile" AFPRESOURCE_STREAM_NAME; const char *fname_renamed = "test_rename_openfile_renamed"; const char *data = "1234567890"; union smb_setfileinfo sinfo; bool server_is_macos = torture_setting_bool(tctx, "osx", false); NTSTATUS expected_status; ret = enable_aapl(tctx, tree); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); torture_comment(tctx, "Create file with resource fork\n"); ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); ret = write_stream(tree, __location__, tctx, tctx, fname, AFPRESOURCE_STREAM_NAME, 0, 10, data); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed"); torture_comment(tctx, "Open resource fork\n"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_ALL; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; create.in.fname = sname; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h1 = create.out.file.handle; torture_comment(tctx, "Rename base file\n"); ZERO_STRUCT(create2); create2.in.desired_access = SEC_FILE_ALL; create2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create2.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create2.in.create_disposition = NTCREATEX_DISP_OPEN; create2.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; create2.in.fname = fname; status = smb2_create(tree, tctx, &create2); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); h2 = create2.out.file.handle; ZERO_STRUCT(sinfo); sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; sinfo.rename_information.in.file.handle = h2; sinfo.rename_information.in.overwrite = 0; sinfo.rename_information.in.root_fid = 0; sinfo.rename_information.in.new_name = fname_renamed; if (server_is_macos) { expected_status = NT_STATUS_SHARING_VIOLATION; } else { expected_status = NT_STATUS_ACCESS_DENIED; } status = smb2_setinfo_file(tree, &sinfo); torture_assert_ntstatus_equal_goto( tctx, status, expected_status, ret, done, "smb2_setinfo_file failed"); smb2_util_close(tree, h2); status = smb2_util_write(tree, h1, "foo", 0, 3); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "write failed\n"); smb2_util_close(tree, h1); done: smb2_util_unlink(tree, fname); smb2_util_unlink(tree, fname_renamed); return ret; } static bool test_readdir_attr_illegal_ntfs(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *name = "test" "\xef\x80\xa2" "aapl"; /* "test:aapl" */ const char *fname = BASEDIR "\\test" "\xef\x80\xa2" "aapl"; /* "test:aapl" */ NTSTATUS status; struct smb2_handle testdirh; bool ret = true; struct smb2_create io; AfpInfo *info; const char *type_creator = "SMB,OLE!"; struct smb2_find f; unsigned int count; union smb_search_data *d; uint64_t rfork_len; int i; smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed"); smb2_util_close(tree, testdirh); torture_comment(tctx, "Enabling AAPL\n"); ret = enable_aapl(tctx, tree); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); /* * Now that Requested AAPL extensions are enabled, setup some * Mac files with metadata and resource fork */ torture_comment(tctx, "Preparing file\n"); ret = torture_setup_file(mem_ctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed"); info = torture_afpinfo_new(mem_ctx); torture_assert_not_null_goto(tctx, info, ret, done, "torture_afpinfo_new failed"); memcpy(info->afpi_FinderInfo, type_creator, 8); ret = torture_write_afpinfo(tree, tctx, mem_ctx, fname, info); torture_assert_goto(tctx, ret == true, ret, done, "torture_write_afpinfo failed"); ret = write_stream(tree, __location__, tctx, mem_ctx, fname, AFPRESOURCE_STREAM_NAME, 0, 3, "foo"); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed"); /* * Ok, file is prepared, now call smb2/find */ torture_comment(tctx, "Issue find\n"); ZERO_STRUCT(io); io.in.desired_access = SEC_RIGHTS_DIR_READ; io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; io.in.share_access = (NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE); io.in.create_disposition = NTCREATEX_DISP_OPEN; io.in.fname = BASEDIR; status = smb2_create(tree, tctx, &io); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed"); ZERO_STRUCT(f); f.in.file.handle = io.out.file.handle; f.in.pattern = "*"; f.in.max_response_size = 0x1000; f.in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO; status = smb2_find_level(tree, tree, &f, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed"); status = smb2_util_close(tree, io.out.file.handle); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed"); torture_comment(tctx, "Checking find response with enriched macOS metadata\n"); for (i = 0; i < count; i++) { const char *found = d[i].id_both_directory_info.name.s; if (!strcmp(found, ".") || !strcmp(found, "..")) continue; if (strncmp(found, "._", 2) == 0) { continue; } break; } torture_assert_str_equal_goto(tctx, d[i].id_both_directory_info.name.s, name, ret, done, "bad name"); rfork_len = BVAL(d[i].id_both_directory_info.short_name_buf, 0); torture_assert_int_equal_goto(tctx, rfork_len, 3, ret, done, "bad resource fork length"); torture_assert_mem_equal_goto(tctx, type_creator, d[i].id_both_directory_info.short_name_buf + 8, 8, ret, done, "Bad FinderInfo"); done: smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_invalid_afpinfo(struct torture_context *tctx, struct smb2_tree *tree1, struct smb2_tree *tree2) { const char *fname = "filtest_invalid_afpinfo"; const char *sname = "filtest_invalid_afpinfo" AFPINFO_STREAM_NAME; struct smb2_create create; const char *streams_basic[] = { "::$DATA" }; const char *streams_afpinfo[] = { "::$DATA", AFPINFO_STREAM }; NTSTATUS status; bool ret = true; if (tree2 == NULL) { torture_skip_goto(tctx, done, "need second share without fruit\n"); } torture_comment(tctx, "Testing invalid AFP_AfpInfo stream\n"); ret = torture_setup_file(tctx, tree2, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); ret = write_stream(tree2, __location__, tctx, tctx, fname, AFPINFO_STREAM_NAME, 0, 3, "foo"); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed"); ret = check_stream_list(tree2, tctx, fname, 2, streams_afpinfo, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); torture_comment(tctx, "Listing streams, bad AFPINFO stream must not be present\n"); ret = check_stream_list(tree1, tctx, fname, 1, streams_basic, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); torture_comment(tctx, "Try to open AFPINFO stream, must fail\n"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_ALL; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; create.in.fname = sname; status = smb2_create(tree1, tctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Stream still around?"); done: smb2_util_unlink(tree1, fname); return ret; } static bool test_writing_afpinfo(struct torture_context *tctx, struct smb2_tree *tree) { const char *fname = "filtest_invalid_afpinfo"; const char *sname = "filtest_invalid_afpinfo" AFPINFO_STREAM; const char *streams_afpinfo[] = { "::$DATA", AFPINFO_STREAM }; bool ret = true; static AfpInfo *afpi = NULL; char *buf = NULL; char *afpi_buf = NULL; char *zero_buf = NULL; bool broken_osx = torture_setting_bool(tctx, "broken_osx_45759458", false); off_t min_offset_for_2streams = 16; int i; NTSTATUS status; struct test_sizes { off_t offset; size_t size; bool expected_result; } test_sizes[] = { { 0, 1, false}, { 0, 2, false}, { 0, 3, true}, { 0, 4, true}, { 0, 14, true}, { 0, 15, true}, { 0, 16, true}, { 0, 24, true}, { 0, 34, true}, { 0, 44, true}, { 0, 54, true}, { 0, 55, true}, { 0, 56, true}, { 0, 57, true}, { 0, 58, true}, { 0, 59, true}, { 0, 60, true}, { 0, 61, true}, { 0, 64, true}, { 0, 1024, true}, { 0, 10064, true}, { 1, 1, false}, { 1, 2, false}, { 1, 3, false}, { 1, 4, false}, { 1, 14, false}, { 1, 15, false}, { 1, 16, false}, { 1, 24, false}, { 1, 34, false}, { 1, 44, false}, { 1, 54, false}, { 1, 55, false}, { 1, 56, false}, { 1, 57, false}, { 1, 58, false}, { 1, 59, false}, { 1, 60, true}, { 1, 61, true}, { 1, 1024, true}, { 1, 10064, true}, { 30, 1, false}, { 30, 2, false}, { 30, 3, false}, { 30, 4, false}, { 30, 14, false}, { 30, 15, false}, { 30, 16, false}, { 30, 24, false}, { 30, 34, false}, { 30, 44, false}, { 30, 54, false}, { 30, 55, false}, { 30, 56, false}, { 30, 57, false}, { 30, 58, false}, { 30, 59, false}, { 30, 60, true}, { 30, 61, true}, { 30, 1024, true}, { 30, 10064, true}, { 58, 1, false}, { 58, 2, false}, { 58, 3, false}, { 58, 4, false}, { 58, 14, false}, { 58, 15, false}, { 58, 16, false}, { 58, 24, false}, { 58, 34, false}, { 58, 44, false}, { 58, 54, false}, { 58, 55, false}, { 58, 56, false}, { 58, 57, false}, { 58, 58, false}, { 58, 59, false}, { 58, 60, true}, { 58, 61, true}, { 58, 1024, true}, { 58, 10064, true}, { 59, 1, false}, { 59, 2, false}, { 59, 3, false}, { 59, 4, false}, { 59, 14, false}, { 59, 15, false}, { 59, 16, false}, { 59, 24, false}, { 59, 34, false}, { 59, 44, false}, { 59, 54, false}, { 59, 55, false}, { 59, 56, false}, { 59, 57, false}, { 59, 58, false}, { 59, 59, false}, { 59, 60, true}, { 59, 61, true}, { 59, 1024, true}, { 59, 10064, true}, { 60, 1, false}, { 60, 2, false}, { 60, 3, false}, { 60, 4, false}, { 60, 14, false}, { 60, 15, false}, { 60, 16, false}, { 60, 24, false}, { 60, 34, false}, { 60, 44, false}, { 60, 54, false}, { 60, 55, false}, { 60, 56, false}, { 60, 57, false}, { 60, 58, false}, { 60, 59, false}, { 60, 60, true}, { 60, 61, true}, { 60, 1024, true}, { 60, 10064, true}, { 61, 1, false}, { 61, 2, false}, { 61, 3, false}, { 61, 4, false}, { 61, 14, false}, { 61, 15, false}, { 61, 16, false}, { 61, 24, false}, { 61, 34, false}, { 61, 44, false}, { 61, 54, false}, { 61, 55, false}, { 61, 56, false}, { 61, 57, false}, { 61, 58, false}, { 61, 59, false}, { 61, 60, true}, { 61, 61, true}, { 61, 1024, true}, { 61, 10064, true}, { 10000, 1, false}, { 10000, 2, false}, { 10000, 3, false}, { 10000, 4, false}, { 10000, 14, false}, { 10000, 15, false}, { 10000, 16, false}, { 10000, 24, false}, { 10000, 34, false}, { 10000, 44, false}, { 10000, 54, false}, { 10000, 55, false}, { 10000, 56, false}, { 10000, 57, false}, { 10000, 58, false}, { 10000, 59, false}, { 10000, 60, true}, { 10000, 61, true}, { 10000, 1024, true}, { 10000, 10064, true}, { -1, 0, false}, }; afpi = torture_afpinfo_new(tctx); torture_assert_not_null_goto(tctx, afpi, ret, done, "torture_afpinfo_new failed\n"); memcpy(afpi->afpi_FinderInfo, "FOO BAR ", 8); buf = torture_afpinfo_pack(afpi, afpi); torture_assert_not_null_goto(tctx, buf, ret, done, "torture_afpinfo_pack failed\n"); afpi_buf = talloc_zero_array(tctx, char, 10064); torture_assert_not_null_goto(tctx, afpi_buf, ret, done, "talloc_zero_array failed\n"); memcpy(afpi_buf, buf, 60); zero_buf = talloc_zero_array(tctx, char, 10064); torture_assert_not_null_goto(tctx, zero_buf, ret, done, "talloc_zero_array failed\n"); ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file\n"); for (i = 0; test_sizes[i].offset != -1; i++) { struct smb2_handle h; struct smb2_create c; int expected_num_streams; size_t fi_check_size; torture_comment(tctx, "Test %d: offset=%jd size=%zu result=%s\n", i, (intmax_t)test_sizes[i].offset, test_sizes[i].size, test_sizes[i].expected_result ? "true":"false"); c = (struct smb2_create) { .in.desired_access = SEC_FILE_WRITE_DATA, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OPEN_IF, .in.fname = sname, }; status = smb2_create(tree, tree, &c); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create\n"); h = c.out.file.handle; status = smb2_util_write(tree, h, zero_buf, test_sizes[i].offset, test_sizes[i].size); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_INVALID_PARAMETER, ret, done, "smb2_util_write\n"); status = smb2_util_write(tree, h, afpi_buf, test_sizes[i].offset, test_sizes[i].size); smb2_util_close(tree, h); if (test_sizes[i].expected_result == true) { torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write\n"); } else { torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_INVALID_PARAMETER, ret, done, "smb2_util_write\n"); } if (broken_osx) { /* * Currently macOS has a bug (Radar #45759458) where it * writes more bytes then requested from uninitialized * memory to the filesystem. That means it will likely * write data to FinderInfo so the stream is not empty * and thus listed when the number of streams is * queried. */ min_offset_for_2streams = 2; } if ((test_sizes[i].expected_result == true) && (test_sizes[i].size > min_offset_for_2streams)) { expected_num_streams = 2; } else { expected_num_streams = 1; } ret = check_stream_list(tree, tctx, fname, expected_num_streams, streams_afpinfo, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams\n"); if (test_sizes[i].expected_result == false) { continue; } if (test_sizes[i].size <= 16) { /* * FinderInfo with the "FOO BAR " string we wrote above * would start at offset 16. Check whether this test * wrote 1 byte or more. */ goto next; } fi_check_size = test_sizes[i].size - 16; fi_check_size = MIN(fi_check_size, 8); ret = check_stream(tree, __location__, tctx, tctx, fname, AFPINFO_STREAM, 0, 60, 16, fi_check_size, "FOO BAR "); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams\n"); next: status = smb2_util_unlink(tree, sname); if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { bool missing_ok; missing_ok = test_sizes[i].expected_result == false; missing_ok |= test_sizes[i].size <= 16; torture_assert_goto(tctx, missing_ok, ret, done, "smb2_util_unlink\n"); } } done: smb2_util_unlink(tree, fname); return ret; } static bool test_zero_file_id(struct torture_context *tctx, struct smb2_tree *tree) { const char *fname = "filtest_file_id"; struct smb2_create create = {0}; NTSTATUS status; bool ret = true; uint8_t zero_file_id[8] = {0}; torture_comment(tctx, "Testing zero file id\n"); ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = fname; create.in.query_on_disk_id = true; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OK, ret, done, "test file could not be opened"); torture_assert_mem_not_equal_goto(tctx, create.out.on_disk_id, zero_file_id, 8, ret, done, "unexpected zero file id"); smb2_util_close(tree, create.out.file.handle); ret = enable_aapl(tctx, tree); torture_assert(tctx, ret == true, "enable_aapl failed"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = fname; create.in.query_on_disk_id = true; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_OK, ret, done, "test file could not be opened with AAPL"); torture_assert_mem_equal_goto(tctx, create.out.on_disk_id, zero_file_id, 8, ret, done, "non-zero file id"); smb2_util_close(tree, create.out.file.handle); done: smb2_util_unlink(tree, fname); return ret; } static bool copy_one_stream(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *tmp_ctx, const char *src_sname, const char *dst_sname) { struct smb2_handle src_h = {{0}}; struct smb2_handle dest_h = {{0}}; NTSTATUS status; union smb_ioctl io; struct srv_copychunk_copy cc_copy; struct srv_copychunk_rsp cc_rsp; enum ndr_err_code ndr_ret; bool ok = false; ok = test_setup_copy_chunk(torture, tree, tmp_ctx, 1, /* 1 chunk */ src_sname, &src_h, 256, /* fill 256 byte src file */ SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, dst_sname, &dest_h, 0, /* 0 byte dest file */ SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, &cc_copy, &io); torture_assert_goto(torture, ok == true, ok, done, "setup copy chunk error\n"); /* copy all src file data (via a single chunk desc) */ cc_copy.chunks[0].source_off = 0; cc_copy.chunks[0].target_off = 0; cc_copy.chunks[0].length = 256; ndr_ret = ndr_push_struct_blob( &io.smb2.in.out, tmp_ctx, &cc_copy, (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, "ndr_push_srv_copychunk_copy\n"); status = smb2_ioctl(tree, tmp_ctx, &io.smb2); torture_assert_ntstatus_ok_goto(torture, status, ok, done, "FSCTL_SRV_COPYCHUNK\n"); ndr_ret = ndr_pull_struct_blob( &io.smb2.out.out, tmp_ctx, &cc_rsp, (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, "ndr_pull_srv_copychunk_rsp\n"); ok = check_copy_chunk_rsp(torture, &cc_rsp, 1, /* chunks written */ 0, /* chunk bytes unsuccessfully written */ 256); /* total bytes written */ torture_assert_goto(torture, ok == true, ok, done, "bad copy chunk response data\n"); ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 256, 0); if (!ok) { torture_fail(torture, "inconsistent file data\n"); } done: if (!smb2_util_handle_empty(src_h)) { smb2_util_close(tree, src_h); } if (!smb2_util_handle_empty(dest_h)) { smb2_util_close(tree, dest_h); } return ok; } static bool copy_finderinfo_stream(struct torture_context *torture, struct smb2_tree *tree, TALLOC_CTX *tmp_ctx, const char *src_name, const char *dst_name) { struct smb2_handle src_h = {{0}}; struct smb2_handle dest_h = {{0}}; NTSTATUS status; union smb_ioctl io; struct srv_copychunk_copy cc_copy; struct srv_copychunk_rsp cc_rsp; enum ndr_err_code ndr_ret; const char *type_creator = "SMB,OLE!"; AfpInfo *info = NULL; const char *src_name_afpinfo = NULL; const char *dst_name_afpinfo = NULL; bool ok = false; src_name_afpinfo = talloc_asprintf(tmp_ctx, "%s%s", src_name, AFPINFO_STREAM); torture_assert_not_null_goto(torture, src_name_afpinfo, ok, done, "talloc_asprintf failed\n"); dst_name_afpinfo = talloc_asprintf(tmp_ctx, "%s%s", dst_name, AFPINFO_STREAM); torture_assert_not_null_goto(torture, dst_name_afpinfo, ok, done, "talloc_asprintf failed\n"); info = torture_afpinfo_new(tmp_ctx); torture_assert_not_null_goto(torture, info, ok, done, "torture_afpinfo_new failed\n"); memcpy(info->afpi_FinderInfo, type_creator, 8); ok = torture_write_afpinfo(tree, torture, tmp_ctx, src_name, info); torture_assert_goto(torture, ok == true, ok, done, "torture_write_afpinfo failed\n"); ok = test_setup_copy_chunk(torture, tree, tmp_ctx, 1, /* 1 chunk */ src_name_afpinfo, &src_h, 0, SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, dst_name_afpinfo, &dest_h, 0, SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, &cc_copy, &io); torture_assert_goto(torture, ok == true, ok, done, "setup copy chunk error\n"); /* copy all src file data (via a single chunk desc) */ cc_copy.chunks[0].source_off = 0; cc_copy.chunks[0].target_off = 0; cc_copy.chunks[0].length = 60; ndr_ret = ndr_push_struct_blob( &io.smb2.in.out, tmp_ctx, &cc_copy, (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, "ndr_push_srv_copychunk_copy\n"); status = smb2_ioctl(tree, tmp_ctx, &io.smb2); torture_assert_ntstatus_ok_goto(torture, status, ok, done, "FSCTL_SRV_COPYCHUNK\n"); ndr_ret = ndr_pull_struct_blob( &io.smb2.out.out, tmp_ctx, &cc_rsp, (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, "ndr_pull_srv_copychunk_rsp\n"); smb2_util_close(tree, src_h); ZERO_STRUCT(src_h); smb2_util_close(tree, dest_h); ZERO_STRUCT(dest_h); ok = check_copy_chunk_rsp(torture, &cc_rsp, 1, /* chunks written */ 0, /* chunk bytes unsuccessfully written */ 60); /* total bytes written */ torture_assert_goto(torture, ok == true, ok, done, "bad copy chunk response data\n"); ok = check_stream(tree, __location__, torture, tmp_ctx, dst_name, AFPINFO_STREAM, 0, 60, 16, 8, type_creator); torture_assert_goto(torture, ok == true, ok, done, "check_stream failed\n"); done: if (!smb2_util_handle_empty(src_h)) { smb2_util_close(tree, src_h); } if (!smb2_util_handle_empty(dest_h)) { smb2_util_close(tree, dest_h); } return ok; } static bool test_copy_chunk_streams(struct torture_context *torture, struct smb2_tree *tree) { const char *src_name = "src"; const char *dst_name = "dst"; struct names { const char *src_sname; const char *dst_sname; } names[] = { { "src:foo", "dst:foo" }, { "src" AFPRESOURCE_STREAM, "dst" AFPRESOURCE_STREAM } }; int i; TALLOC_CTX *tmp_ctx = NULL; bool ok = false; tmp_ctx = talloc_new(tree); torture_assert_not_null_goto(torture, tmp_ctx, ok, done, "torture_setup_file\n"); smb2_util_unlink(tree, src_name); smb2_util_unlink(tree, dst_name); ok = torture_setup_file(torture, tree, src_name, false); torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n"); ok = torture_setup_file(torture, tree, dst_name, false); torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n"); for (i = 0; i < ARRAY_SIZE(names); i++) { ok = copy_one_stream(torture, tree, tmp_ctx, names[i].src_sname, names[i].dst_sname); torture_assert_goto(torture, ok == true, ok, done, "copy_one_stream failed\n"); } ok = copy_finderinfo_stream(torture, tree, tmp_ctx, src_name, dst_name); torture_assert_goto(torture, ok == true, ok, done, "copy_finderinfo_stream failed\n"); done: smb2_util_unlink(tree, src_name); smb2_util_unlink(tree, dst_name); talloc_free(tmp_ctx); return ok; } /* * Ensure this security descriptor has exactly one mode, uid * and gid. */ static NTSTATUS check_nfs_sd(const struct security_descriptor *psd) { uint32_t i; bool got_one_mode = false; bool got_one_uid = false; bool got_one_gid = false; if (psd->dacl == NULL) { return NT_STATUS_INVALID_SECURITY_DESCR; } for (i = 0; i < psd->dacl->num_aces; i++) { if (dom_sid_compare_domain(&global_sid_Unix_NFS_Mode, &psd->dacl->aces[i].trustee) == 0) { if (got_one_mode == true) { /* Can't have more than one. */ return NT_STATUS_INVALID_SECURITY_DESCR; } got_one_mode = true; } } for (i = 0; i < psd->dacl->num_aces; i++) { if (dom_sid_compare_domain(&global_sid_Unix_NFS_Users, &psd->dacl->aces[i].trustee) == 0) { if (got_one_uid == true) { /* Can't have more than one. */ return NT_STATUS_INVALID_SECURITY_DESCR; } got_one_uid = true; } } for (i = 0; i < psd->dacl->num_aces; i++) { if (dom_sid_compare_domain(&global_sid_Unix_NFS_Groups, &psd->dacl->aces[i].trustee) == 0) { if (got_one_gid == true) { /* Can't have more than one. */ return NT_STATUS_INVALID_SECURITY_DESCR; } got_one_gid = true; } } /* Must have at least one of each. */ if (got_one_mode == false || got_one_uid == false || got_one_gid == false) { return NT_STATUS_INVALID_SECURITY_DESCR; } return NT_STATUS_OK; } static bool test_nfs_aces(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); struct security_ace ace; struct dom_sid sid; const char *fname = BASEDIR "\\nfs_aces.txt"; struct smb2_handle h = {{0}}; union smb_fileinfo finfo2; union smb_setfileinfo set; struct security_descriptor *psd = NULL; NTSTATUS status; bool ret = true; bool is_osx = torture_setting_bool(tctx, "osx", false); if (is_osx) { torture_skip(tctx, "Test only works with Samba\n"); } ret = enable_aapl(tctx, tree); torture_assert(tctx, ret == true, "enable_aapl failed"); /* clean slate ...*/ smb2_util_unlink(tree, fname); smb2_deltree(tree, fname); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); /* Create a test file. */ status = torture_smb2_testfile_access(tree, fname, &h, SEC_STD_READ_CONTROL | SEC_STD_WRITE_DAC | SEC_RIGHTS_FILE_ALL); CHECK_STATUS(status, NT_STATUS_OK); /* Get the ACL. */ finfo2.query_secdesc.in.secinfo_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; finfo2.generic.level = RAW_FILEINFO_SEC_DESC; finfo2.generic.in.file.handle = h; status = smb2_getinfo_file(tree, tctx, &finfo2); CHECK_STATUS(status, NT_STATUS_OK); psd = finfo2.query_secdesc.out.sd; /* Ensure we have only single mode/uid/gid NFS entries. */ status = check_nfs_sd(psd); if (!NT_STATUS_IS_OK(status)) { NDR_PRINT_DEBUG( security_descriptor, discard_const_p(struct security_descriptor, psd)); } CHECK_STATUS(status, NT_STATUS_OK); /* Add a couple of extra NFS uids and gids. */ sid_compose(&sid, &global_sid_Unix_NFS_Users, 27); init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); status = security_descriptor_dacl_add(psd, &ace); CHECK_STATUS(status, NT_STATUS_OK); status = security_descriptor_dacl_add(psd, &ace); CHECK_STATUS(status, NT_STATUS_OK); sid_compose(&sid, &global_sid_Unix_NFS_Groups, 300); init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); status = security_descriptor_dacl_add(psd, &ace); CHECK_STATUS(status, NT_STATUS_OK); status = security_descriptor_dacl_add(psd, &ace); CHECK_STATUS(status, NT_STATUS_OK); /* Now set on the file handle. */ set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; set.set_secdesc.in.file.handle = h; set.set_secdesc.in.secinfo_flags = SECINFO_DACL; set.set_secdesc.in.sd = psd; status = smb2_setinfo_file(tree, &set); CHECK_STATUS(status, NT_STATUS_OK); /* Get the ACL again. */ finfo2.query_secdesc.in.secinfo_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; finfo2.generic.level = RAW_FILEINFO_SEC_DESC; finfo2.generic.in.file.handle = h; status = smb2_getinfo_file(tree, tctx, &finfo2); CHECK_STATUS(status, NT_STATUS_OK); psd = finfo2.query_secdesc.out.sd; /* Ensure we have only single mode/uid/gid NFS entries. */ status = check_nfs_sd(psd); if (!NT_STATUS_IS_OK(status)) { NDR_PRINT_DEBUG( security_descriptor, discard_const_p(struct security_descriptor, psd)); } CHECK_STATUS(status, NT_STATUS_OK); done: if (!smb2_util_handle_empty(h)) { smb2_util_close(tree, h); } smb2_util_unlink(tree, fname); smb2_deltree(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_setinfo_stream_eof(struct torture_context *tctx, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_create create; union smb_setfileinfo sfinfo; union smb_fileinfo finfo; struct smb2_handle h1; TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\file"; const char *sname = BASEDIR "\\file:foo"; torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new failed\n"); ret = enable_aapl(tctx, tree); torture_assert(tctx, ret == true, "enable_aapl failed"); torture_comment(tctx, "Test setting EOF on a stream\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); status = torture_smb2_testfile(tree, fname, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); smb2_util_close(tree, h1); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); status = smb2_util_write(tree, h1, "1234567890", 0, 10); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); smb2_util_close(tree, h1); /* * Test setting EOF to 21 */ torture_comment(tctx, "Setting stream EOF to 21\n"); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 21; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set EOF 21 failed\n"); smb2_util_close(tree, h1); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_STANDARD_INFORMATION; finfo.generic.in.file.handle = h1; status = smb2_getinfo_file(tree, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed"); smb2_util_close(tree, h1); torture_assert_goto(tctx, finfo.standard_info.out.size == 21, ret, done, "size != 21\n"); /* * Test setting EOF to 0 */ torture_comment(tctx, "Setting stream EOF to 0\n"); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 0; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed\n"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = sname; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Unexpected status\n"); smb2_util_close(tree, h1); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = sname; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Unexpected status\n"); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_STANDARD_INFORMATION; finfo.generic.in.file.handle = h1; status = smb2_getinfo_file(tree, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_goto(tctx, finfo.standard_info.out.size == 0, ret, done, "size != 0\n"); /* * Test setinfo end-of-file info to 1 */ torture_comment(tctx, "Setting stream EOF to 1\n"); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 1; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set EOF 1 failed\n"); smb2_util_close(tree, h1); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(finfo); finfo.generic.level = RAW_FILEINFO_STANDARD_INFORMATION; finfo.generic.in.file.handle = h1; status = smb2_getinfo_file(tree, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); smb2_util_close(tree, h1); torture_assert_goto(tctx, finfo.standard_info.out.size == 1, ret, done, "size != 1\n"); /* * Test setting EOF to 0 with AAPL enabled, should delete stream */ torture_comment(tctx, "Enabling AAPL extensions\n"); ret = enable_aapl(tctx, tree); torture_assert(tctx, ret == true, "enable_aapl failed\n"); torture_comment(tctx, "Setting stream EOF to 0\n"); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 0; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed\n"); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = sname; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Unexpected status\n"); smb2_util_close(tree, h1); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = sname; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Unexpected status\n"); torture_comment( tctx, "Setting main file EOF to 1 to force 0-truncate\n"); status = torture_smb2_testfile_access( tree, fname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 1; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto( tctx, status, ret, done, "set eof 1 failed\n"); sfinfo.position_information.in.position = 0; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto( tctx, status, ret, done, "set eof 0 failed\n"); smb2_util_close(tree, h1); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_READ_ATTRIBUTE; create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.create_disposition = NTCREATEX_DISP_OPEN; create.in.fname = fname; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); smb2_util_close(tree, h1); torture_comment(tctx, "Writing to stream after setting EOF to 0\n"); status = torture_smb2_testfile_access(tree, sname, &h1, SEC_FILE_WRITE_DATA); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); status = smb2_util_write(tree, h1, "1234567890", 0, 10); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); ZERO_STRUCT(sfinfo); sfinfo.generic.in.file.handle = h1; sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; sfinfo.position_information.in.position = 0; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed\n"); status = smb2_util_write(tree, h1, "1234567890", 0, 10); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); smb2_util_close(tree, h1); done: smb2_util_unlink(tree, fname); smb2_util_rmdir(tree, BASEDIR); return ret; } #define MAX_STREAMS 16 struct tcase { const char *name; uint32_t access; const char *write_data; size_t write_size; struct tcase_results { size_t size; NTSTATUS initial_status; NTSTATUS final_status; int num_streams_open_handle; const char *streams_open_handle[MAX_STREAMS]; int num_streams_closed_handle; const char *streams_closed_handle[MAX_STREAMS]; } create, write, overwrite, eof, doc; }; typedef enum {T_CREATE, T_WRITE, T_OVERWRITE, T_EOF, T_DOC} subtcase_t; static bool test_empty_stream_do_checks( struct torture_context *tctx, struct smb2_tree *tree, struct smb2_tree *tree2, struct tcase *tcase, TALLOC_CTX *mem_ctx, struct smb2_handle baseh, struct smb2_handle streamh, subtcase_t subcase) { bool ret = false; NTSTATUS status; struct smb2_handle h1; union smb_fileinfo finfo; struct tcase_results *tcase_results = NULL; switch (subcase) { case T_CREATE: tcase_results = &tcase->create; break; case T_OVERWRITE: tcase_results = &tcase->overwrite; break; case T_WRITE: tcase_results = &tcase->write; break; case T_EOF: tcase_results = &tcase->eof; break; case T_DOC: tcase_results = &tcase->doc; break; } finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_STANDARD_INFORMATION, .generic.in.file.handle = streamh, }; /* * Test: check size, same client */ status = smb2_getinfo_file(tree, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); torture_assert_int_equal_goto(tctx, finfo.standard_info.out.size, tcase_results->size, ret, done, "Wrong size\n"); /* * Test: open, same client */ status = torture_smb2_open(tree, tcase->name, SEC_FILE_READ_ATTRIBUTE, &h1); torture_assert_ntstatus_equal_goto(tctx, status, tcase_results->initial_status, ret, done, "smb2_create failed\n"); if (NT_STATUS_IS_OK(status)) { status = smb2_util_close(tree, h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); } /* * Test: check streams, same client */ ret = check_stream_list_handle(tree, tctx, baseh, tcase_results->num_streams_open_handle, tcase_results->streams_open_handle, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); /* * Test: open, different client */ status = torture_smb2_open(tree2, tcase->name, SEC_FILE_READ_ATTRIBUTE, &h1); torture_assert_ntstatus_equal_goto(tctx, status, tcase_results->initial_status, ret, done, "smb2_create failed\n"); if (NT_STATUS_IS_OK(status)) { finfo = (union smb_fileinfo) { .generic.level = RAW_FILEINFO_STANDARD_INFORMATION, .generic.in.file.handle = h1, }; /* * Test: check size, different client */ status = smb2_getinfo_file(tree2, mem_ctx, &finfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_getinfo_file failed\n"); torture_assert_int_equal_goto(tctx, finfo.standard_info.out.size, tcase_results->size, ret, done, "Wrong size\n"); /* * Test: check streams, different client */ ret = check_stream_list(tree2, tctx, BASEDIR "\\file", tcase_results->num_streams_open_handle, tcase_results->streams_open_handle, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); status = smb2_util_close(tree2, h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); } status = smb2_util_close(tree, streamh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); /* * Test: open after close, same client */ status = torture_smb2_open(tree, tcase->name, SEC_FILE_READ_DATA, &h1); torture_assert_ntstatus_equal_goto(tctx, status, tcase_results->final_status, ret, done, "smb2_create failed\n"); if (NT_STATUS_IS_OK(status)) { status = smb2_util_close(tree, h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); } /* * Test: open after close, different client */ status = torture_smb2_open(tree2, tcase->name, SEC_FILE_READ_DATA, &h1); torture_assert_ntstatus_equal_goto(tctx, status, tcase_results->final_status, ret, done, "smb2_create failed\n"); if (NT_STATUS_IS_OK(status)) { status = smb2_util_close(tree2, h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); } /* * Test: check streams after close, same client */ ret = check_stream_list_handle(tree, tctx, baseh, tcase_results->num_streams_closed_handle, tcase_results->streams_closed_handle, false); torture_assert_goto(tctx, ret == true, ret, done, "Bad streams"); ret = true; done: smb2_util_close(tree, streamh); smb2_util_close(tree, baseh); return ret; } static bool test_empty_stream_do_one( struct torture_context *tctx, struct smb2_tree *tree, struct smb2_tree *tree2, struct tcase *tcase) { bool ret = false; NTSTATUS status; struct smb2_handle baseh = {{0}}; struct smb2_handle streamh; struct smb2_create create; union smb_setfileinfo sfinfo; TALLOC_CTX *mem_ctx = talloc_new(tctx); torture_comment(tctx, "Testing stream [%s]\n", tcase->name); torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new\n"); /* * Subtest: create */ torture_comment(tctx, "Subtest: T_CREATE\n"); status = smb2_util_unlink(tree, BASEDIR "\\file"); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_unlink failed\n"); status = torture_smb2_testfile_access(tree, BASEDIR "\\file", &baseh, SEC_FILE_ALL); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = torture_smb2_testfile_access(tree, tcase->name, &streamh, tcase->access); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase, mem_ctx, baseh, streamh, T_CREATE); torture_assert_goto(tctx, ret, ret, done, "test failed\n"); if (!(tcase->access & SEC_FILE_WRITE_DATA)) { /* * All subsequent tests require write access */ ret = true; goto done; } /* * Subtest: create and write */ torture_comment(tctx, "Subtest: T_WRITE\n"); status = smb2_util_unlink(tree, BASEDIR "\\file"); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_unlink failed\n"); status = torture_smb2_testfile_access(tree, BASEDIR "\\file", &baseh, SEC_FILE_ALL); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = torture_smb2_testfile_access(tree, tcase->name, &streamh, tcase->access); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = smb2_util_write(tree, streamh, tcase->write_data, 0, tcase->write_size); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_open failed\n"); ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase, mem_ctx, baseh, streamh, T_WRITE); torture_assert_goto(tctx, ret, ret, done, "test failed\n"); /* * Subtest: overwrite */ torture_comment(tctx, "Subtest: T_OVERWRITE\n"); status = smb2_util_unlink(tree, BASEDIR "\\file"); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_unlink failed\n"); status = torture_smb2_testfile_access(tree, BASEDIR "\\file", &baseh, SEC_FILE_ALL); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); create = (struct smb2_create) { .in.desired_access = SEC_FILE_ALL, .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF, .in.fname = tcase->name, }; status = smb2_create(tree, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile failed\n"); streamh = create.out.file.handle; ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase, mem_ctx, baseh, streamh, T_OVERWRITE); torture_assert_goto(tctx, ret, ret, done, "test failed\n"); /* * Subtest: setinfo EOF 0 */ torture_comment(tctx, "Subtest: T_EOF\n"); status = smb2_util_unlink(tree, BASEDIR "\\file"); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_unlink failed\n"); status = torture_smb2_testfile_access(tree, BASEDIR "\\file", &baseh, SEC_FILE_ALL); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = torture_smb2_testfile_access(tree, tcase->name, &streamh, tcase->access); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = smb2_util_write(tree, streamh, tcase->write_data, 0, tcase->write_size); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_open failed\n"); sfinfo = (union smb_setfileinfo) { .end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION, .end_of_file_info.in.file.handle = streamh, .end_of_file_info.in.size = 0, }; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed\n"); ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase, mem_ctx, baseh, streamh, T_EOF); torture_assert_goto(tctx, ret, ret, done, "test failed\n"); /* * Subtest: delete-on-close */ torture_comment(tctx, "Subtest: T_DOC\n"); status = smb2_util_unlink(tree, BASEDIR "\\file"); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_unlink failed\n"); status = torture_smb2_testfile_access(tree, BASEDIR "\\file", &baseh, SEC_FILE_ALL); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = torture_smb2_testfile_access(tree, tcase->name, &streamh, tcase->access); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile_access failed\n"); status = smb2_util_write(tree, streamh, tcase->write_data, 0, tcase->write_size); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_open failed\n"); sfinfo = (union smb_setfileinfo) { .disposition_info.level = RAW_SFILEINFO_DISPOSITION_INFORMATION, .disposition_info.in.file.handle = streamh, .disposition_info.in.delete_on_close = true, }; status = smb2_setinfo_file(tree, &sfinfo); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set eof 0 failed\n"); ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase, mem_ctx, baseh, streamh, T_DOC); torture_assert_goto(tctx, ret, ret, done, "test failed\n"); ret = true; done: smb2_util_close(tree, baseh); TALLOC_FREE(mem_ctx); return ret; } static bool test_empty_stream(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_tree *tree2 = NULL; struct tcase *tcase = NULL; const char *fname = BASEDIR "\\file"; struct smb2_handle h1; bool ret = true; NTSTATUS status; AfpInfo ai = (AfpInfo) { .afpi_Signature = AFP_Signature, .afpi_Version = AFP_Version, .afpi_BackupTime = AFP_BackupTime, .afpi_FinderInfo = "FOO BAR ", }; char *ai_blob = torture_afpinfo_pack(tctx, &ai); struct tcase tcase_afpinfo_ro = (struct tcase) { .name = BASEDIR "\\file" AFPINFO_STREAM, .access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, .create.size = 60, .create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.num_streams_open_handle = 1, .create.num_streams_closed_handle = 1, .create.streams_open_handle = {"::$DATA"}, .create.streams_closed_handle = {"::$DATA"}, }; struct tcase tcase_afpinfo_rw = (struct tcase) { .name = BASEDIR "\\file" AFPINFO_STREAM, .access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_DATA|SEC_STD_DELETE, .write_data = ai_blob, .write_size = AFP_INFO_SIZE, .create.size = 60, .create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.num_streams_open_handle = 1, .create.num_streams_closed_handle = 1, .create.streams_open_handle = {"::$DATA"}, .create.streams_closed_handle = {"::$DATA"}, .write.size = 60, .write.initial_status = NT_STATUS_OK, .write.final_status = NT_STATUS_OK, .write.num_streams_open_handle = 2, .write.num_streams_closed_handle = 2, .write.streams_open_handle = {"::$DATA", AFPINFO_STREAM}, .write.streams_closed_handle = {"::$DATA", AFPINFO_STREAM}, .overwrite.size = 60, .overwrite.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .overwrite.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .overwrite.num_streams_open_handle = 1, .overwrite.num_streams_closed_handle = 1, .overwrite.streams_open_handle = {"::$DATA"}, .overwrite.streams_closed_handle = {"::$DATA"}, .eof.size = 60, .eof.initial_status = NT_STATUS_OK, .eof.final_status = NT_STATUS_OK, .eof.num_streams_open_handle = 2, .eof.num_streams_closed_handle = 2, .eof.streams_open_handle = {"::$DATA", AFPINFO_STREAM}, .eof.streams_closed_handle = {"::$DATA", AFPINFO_STREAM}, .doc.size = 60, .doc.initial_status = NT_STATUS_DELETE_PENDING, .doc.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .doc.num_streams_open_handle = 2, .doc.num_streams_closed_handle = 1, .doc.streams_open_handle = {"::$DATA", AFPINFO_STREAM}, .doc.streams_closed_handle = {"::$DATA"}, }; struct tcase tcase_afpresource_ro = (struct tcase) { .name = BASEDIR "\\file" AFPRESOURCE_STREAM, .access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, .create.size = 0, .create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.num_streams_open_handle = 1, .create.num_streams_closed_handle = 1, .create.streams_open_handle = {"::$DATA"}, .create.streams_closed_handle = {"::$DATA"}, }; struct tcase tcase_afpresource_rw = (struct tcase) { .name = BASEDIR "\\file" AFPRESOURCE_STREAM, .access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_DATA|SEC_STD_DELETE, .write_data = "foo", .write_size = 3, .create.size = 0, .create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.num_streams_open_handle = 1, .create.num_streams_closed_handle = 1, .create.streams_open_handle = {"::$DATA"}, .create.streams_closed_handle = {"::$DATA"}, .write.size = 3, .write.initial_status = NT_STATUS_OK, .write.final_status = NT_STATUS_OK, .write.num_streams_open_handle = 2, .write.num_streams_closed_handle = 2, .write.streams_open_handle = {"::$DATA", AFPRESOURCE_STREAM}, .write.streams_closed_handle = {"::$DATA", AFPRESOURCE_STREAM}, .overwrite.size = 0, .overwrite.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .overwrite.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .overwrite.num_streams_open_handle = 1, .overwrite.num_streams_closed_handle = 1, .overwrite.streams_open_handle = {"::$DATA"}, .overwrite.streams_closed_handle = {"::$DATA"}, .eof.size = 0, .eof.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .eof.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .eof.num_streams_open_handle = 1, .eof.num_streams_closed_handle = 1, .eof.streams_open_handle = {"::$DATA"}, .eof.streams_closed_handle = {"::$DATA"}, .doc.size = 3, .doc.initial_status = NT_STATUS_DELETE_PENDING, .doc.final_status = NT_STATUS_OK, .doc.num_streams_open_handle = 2, .doc.num_streams_closed_handle = 2, .doc.streams_open_handle = {"::$DATA", AFPRESOURCE_STREAM}, .doc.streams_closed_handle = {"::$DATA", AFPRESOURCE_STREAM}, }; struct tcase tcase_foo_ro = (struct tcase) { .name = BASEDIR "\\file:foo", .access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, .write_data = "foo", .write_size = 3, .create.size = 0, .create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.num_streams_open_handle = 1, .create.num_streams_closed_handle = 1, .create.streams_open_handle = {"::$DATA"}, .create.streams_closed_handle = {"::$DATA"}, }; struct tcase tcase_foo_rw = (struct tcase) { .name = BASEDIR "\\file:foo", .access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_DATA|SEC_STD_DELETE, .write_data = "foo", .write_size = 3, .create.size = 0, .create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .create.num_streams_open_handle = 1, .create.num_streams_closed_handle = 1, .create.streams_open_handle = {"::$DATA"}, .create.streams_closed_handle = {"::$DATA"}, .write.size = 3, .write.initial_status = NT_STATUS_OK, .write.final_status = NT_STATUS_OK, .write.num_streams_open_handle = 2, .write.num_streams_closed_handle = 2, .write.streams_open_handle = {"::$DATA", ":foo:$DATA"}, .write.streams_closed_handle = {"::$DATA", ":foo:$DATA"}, .overwrite.size = 0, .overwrite.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .overwrite.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .overwrite.num_streams_open_handle = 1, .overwrite.num_streams_closed_handle = 1, .overwrite.streams_open_handle = {"::$DATA"}, .overwrite.streams_closed_handle = {"::$DATA"}, .eof.size = 0, .eof.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .eof.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .eof.num_streams_open_handle = 1, .eof.num_streams_closed_handle = 1, .eof.streams_open_handle = {"::$DATA"}, .eof.streams_closed_handle = {"::$DATA"}, .doc.size = 3, .doc.initial_status = NT_STATUS_DELETE_PENDING, .doc.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND, .doc.num_streams_open_handle = 2, .doc.num_streams_closed_handle = 1, .doc.streams_open_handle = {"::$DATA", ":foo:$DATA"}, .doc.streams_closed_handle = {"::$DATA"}, }; struct tcase tcases[] = { tcase_afpinfo_ro, tcase_afpinfo_rw, tcase_afpresource_ro, tcase_afpresource_rw, tcase_foo_ro, tcase_foo_rw, {NULL} }; ret = torture_smb2_connection(tctx, &tree2); torture_assert_goto(tctx, ret == true, ret, done, "torture_smb2_connection failed\n"); ret = enable_aapl(tctx, tree); torture_assert(tctx, ret == true, "enable_aapl failed\n"); ret = enable_aapl(tctx, tree2); torture_assert(tctx, ret == true, "enable_aapl failed\n"); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h1); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); smb2_util_close(tree, h1); for (tcase = &tcases[0]; tcase->name != NULL; tcase++) { ret = torture_setup_file(tctx, tree, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = test_empty_stream_do_one(tctx, tree, tree2, tcase); torture_assert_goto(tctx, ret == true, ret, done, "subtest failed\n"); status = smb2_util_unlink(tree, fname); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_unlink failed\n"); } done: smb2_deltree(tree, BASEDIR); TALLOC_FREE(tree2); return ret; } /* * Note: This test depends on "vfs objects = catia fruit streams_xattr". For * some tests torture must be run on the host it tests and takes an additional * argument with the local path to the share: * "--option=torture:localdir=". * * When running against an OS X SMB server add "--option=torture:osx=true" */ struct torture_suite *torture_vfs_fruit(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create( ctx, "fruit"); suite->description = talloc_strdup(suite, "vfs_fruit tests"); torture_suite_add_1smb2_test(suite, "copyfile", test_copyfile); torture_suite_add_1smb2_test(suite, "read metadata", test_read_afpinfo); torture_suite_add_1smb2_test(suite, "write metadata", test_write_atalk_metadata); torture_suite_add_1smb2_test(suite, "resource fork IO", test_write_atalk_rfork_io); torture_suite_add_1smb2_test(suite, "SMB2/CREATE context AAPL", test_aapl); torture_suite_add_1smb2_test(suite, "stream names", test_stream_names); torture_suite_add_1smb2_test(suite, "truncate resource fork to 0 bytes", test_rfork_truncate); torture_suite_add_1smb2_test(suite, "opening and creating resource fork", test_rfork_create); torture_suite_add_1smb2_test(suite, "rename_dir_openfile", test_rename_dir_openfile); torture_suite_add_1smb2_test(suite, "File without AFP_AfpInfo", test_afpinfo_enoent); torture_suite_add_1smb2_test(suite, "create delete-on-close AFP_AfpInfo", test_create_delete_on_close); torture_suite_add_1smb2_test(suite, "setinfo delete-on-close AFP_AfpInfo", test_setinfo_delete_on_close); torture_suite_add_1smb2_test(suite, "setinfo eof AFP_AfpInfo", test_setinfo_eof); torture_suite_add_1smb2_test(suite, "delete AFP_AfpInfo by writing all 0", test_afpinfo_all0); torture_suite_add_1smb2_test(suite, "create delete-on-close AFP_AfpResource", test_create_delete_on_close_resource); torture_suite_add_1smb2_test(suite, "setinfo delete-on-close AFP_AfpResource", test_setinfo_delete_on_close_resource); torture_suite_add_1smb2_test(suite, "setinfo eof AFP_AfpResource", test_setinfo_eof_resource); torture_suite_add_1smb2_test(suite, "setinfo eof stream", test_setinfo_stream_eof); torture_suite_add_1smb2_test(suite, "null afpinfo", test_null_afpinfo); torture_suite_add_1smb2_test(suite, "delete", test_delete_file_with_rfork); torture_suite_add_1smb2_test(suite, "read open rsrc after rename", test_rename_and_read_rsrc); torture_suite_add_1smb2_test(suite, "readdir_attr with names with illegal ntfs characters", test_readdir_attr_illegal_ntfs); torture_suite_add_2ns_smb2_test(suite, "invalid AFP_AfpInfo", test_invalid_afpinfo); torture_suite_add_1smb2_test(suite, "creating rsrc with read-only access", test_rfork_create_ro); torture_suite_add_1smb2_test(suite, "copy-chunk streams", test_copy_chunk_streams); torture_suite_add_1smb2_test(suite, "OS X AppleDouble file conversion", test_adouble_conversion); torture_suite_add_1smb2_test(suite, "NFS ACE entries", test_nfs_aces); torture_suite_add_1smb2_test(suite, "OS X AppleDouble file conversion without embedded xattr", test_adouble_conversion_wo_xattr); torture_suite_add_1smb2_test(suite, "empty_stream", test_empty_stream); torture_suite_add_1smb2_test(suite, "writing_afpinfo", test_writing_afpinfo); return suite; } static bool test_stream_names_local(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); NTSTATUS status; struct smb2_create create; struct smb2_handle h; const char *fname = BASEDIR "\\stream_names.txt"; const char *sname1; bool ret; /* UTF8 private use are starts at 0xef 0x80 0x80 (0xf000) */ const char *streams[] = { ":foo" "\xef\x80\xa2" "bar:$DATA", /* "foo:bar:$DATA" */ ":bar" "\xef\x80\xa2" "baz:$DATA", /* "bar:baz:$DATA" */ "::$DATA" }; const char *localdir = NULL; localdir = torture_setting_string(tctx, "localdir", NULL); if (localdir == NULL) { torture_skip(tctx, "Need localdir for test"); } sname1 = talloc_asprintf(mem_ctx, "%s%s", fname, streams[0]); /* clean slate ...*/ smb2_util_unlink(tree, fname); smb2_deltree(tree, fname); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); torture_comment(tctx, "(%s) testing stream names\n", __location__); ZERO_STRUCT(create); create.in.desired_access = SEC_FILE_WRITE_DATA; create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; create.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE| NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE; create.in.create_disposition = NTCREATEX_DISP_CREATE; create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; create.in.fname = sname1; status = smb2_create(tree, mem_ctx, &create); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_write(tree, create.out.file.handle, "foo", 0, 3); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_write failed\n"); smb2_util_close(tree, create.out.file.handle); ret = torture_setup_local_xattr(tctx, "localdir", BASEDIR "/stream_names.txt", "user.DosStream.bar:baz:$DATA", "data", strlen("data")); CHECK_VALUE(ret, true); ret = check_stream_list(tree, tctx, fname, 3, streams, false); CHECK_VALUE(ret, true); done: status = smb2_util_unlink(tree, fname); smb2_deltree(tree, BASEDIR); talloc_free(mem_ctx); return ret; } static bool test_fruit_locking_conflict(struct torture_context *tctx, struct smb2_tree *tree, struct smb2_tree *tree2) { TALLOC_CTX *mem_ctx; struct smb2_create create; struct smb2_handle h; struct smb2_lock lck; struct smb2_lock_element el; const char *fname = BASEDIR "\\locking_conflict.txt"; NTSTATUS status; bool ret = false; mem_ctx = talloc_new(tctx); torture_assert_not_null(tctx, mem_ctx, "talloc_new failed"); /* clean slate ...*/ smb2_util_unlink(tree, fname); smb2_deltree(tree, fname); smb2_deltree(tree, BASEDIR); status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_FILE_READ, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.share_access = NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE, .in.create_disposition = NTCREATEX_DISP_CREATE, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = fname, }; status = smb2_create(tree, mem_ctx, &create); CHECK_STATUS(status, NT_STATUS_OK); h = create.out.file.handle; /* Add AD_FILELOCK_RSRC_DENY_WR lock. */ el = (struct smb2_lock_element) { .offset = 0xfffffffffffffffc, .length = 1, .flags = SMB2_LOCK_FLAG_EXCLUSIVE, }; lck = (struct smb2_lock) { .in.lock_count = 1, .in.file.handle = h, .in.locks = &el, }; /* * Lock up to and including: * AD_FILELOCK_OPEN_WR * AD_FILELOCK_OPEN_RD * This is designed to cause a NetAtalk * locking conflict on the next open, * even though the share modes are * compatible. */ status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el = (struct smb2_lock_element) { .offset = 0, .length = 0x7ffffffffffffff7, .flags = SMB2_LOCK_FLAG_EXCLUSIVE, }; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.share_access = NTCREATEX_SHARE_ACCESS_READ, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = fname, }; /* * Open on the second tree - ensure we are * emulating trying to access with a NetATalk * process with an existing open/deny mode. */ status = smb2_create(tree2, mem_ctx, &create); CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); { struct smb2_close cl = { .level = RAW_CLOSE_SMB2, .in.file.handle = h, }; smb2_close(tree, &cl); } ret = true; done: return ret; } struct torture_suite *torture_vfs_fruit_netatalk(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create( ctx, "fruit_netatalk"); suite->description = talloc_strdup(suite, "vfs_fruit tests for Netatalk interop that require fruit:metadata=netatalk"); torture_suite_add_1smb2_test(suite, "read netatalk metadata", test_read_netatalk_metadata); torture_suite_add_1smb2_test(suite, "stream names with locally created xattr", test_stream_names_local); torture_suite_add_2smb2_test( suite, "locking conflict", test_fruit_locking_conflict); return suite; } struct torture_suite *torture_vfs_fruit_file_id(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "fruit_file_id"); suite->description = talloc_strdup(suite, "vfs_fruit tests for on-disk file ID that " "require fruit:zero_file_id=yes"); torture_suite_add_1smb2_test(suite, "zero file id if AAPL negotiated", test_zero_file_id); return suite; } static bool test_timemachine_volsize(struct torture_context *tctx, struct smb2_tree *tree) { TALLOC_CTX *mem_ctx = talloc_new(tctx); struct smb2_handle h = {{0}}; union smb_fsinfo fsinfo; NTSTATUS status; bool ok = true; const char *info_plist = "\n" " band-size\n" " 8192\n" "\n"; smb2_deltree(tree, "test.sparsebundle"); ok = enable_aapl(tctx, tree); torture_assert_goto(tctx, ok, ok, done, "enable_aapl failed"); status = smb2_util_mkdir(tree, "test.sparsebundle"); torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "smb2_util_mkdir\n"); ok = write_stream(tree, __location__, tctx, mem_ctx, "test.sparsebundle/Info.plist", NULL, 0, strlen(info_plist), info_plist); torture_assert_goto(tctx, ok, ok, done, "write_stream failed\n"); status = smb2_util_mkdir(tree, "test.sparsebundle/bands"); torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "smb2_util_mkdir\n"); ok = torture_setup_file(tctx, tree, "test.sparsebundle/bands/1", false); torture_assert_goto(tctx, ok, ok, done, "torture_setup_file failed\n"); ok = torture_setup_file(tctx, tree, "test.sparsebundle/bands/2", false); torture_assert_goto(tctx, ok, ok, done, "torture_setup_file failed\n"); status = smb2_util_roothandle(tree, &h); torture_assert_ntstatus_ok(tctx, status, "Unable to create root handle"); ZERO_STRUCT(fsinfo); fsinfo.generic.level = RAW_QFS_SIZE_INFORMATION; fsinfo.generic.handle = h; status = smb2_getinfo_fs(tree, tree, &fsinfo); torture_assert_ntstatus_ok(tctx, status, "smb2_getinfo_fs failed"); torture_comment(tctx, "sectors_per_unit: %" PRIu32"\n" "bytes_per_sector: %" PRIu32"\n" "total_alloc_units: %" PRIu64"\n" "avail_alloc_units: %" PRIu64"\n", fsinfo.size_info.out.sectors_per_unit, fsinfo.size_info.out.bytes_per_sector, fsinfo.size_info.out.total_alloc_units, fsinfo.size_info.out.avail_alloc_units); /* * Let me explain the numbers: * * - the share is set to "fruit:time machine max size = 32K" * - we've faked a bandsize of 8 K in the Info.plist file * - we've created two bands files * - one allocation unit is made of two sectors with 512 B each * => we've consumed 16 allocation units, there should be 16 free */ torture_assert_goto(tctx, fsinfo.size_info.out.sectors_per_unit == 2, ok, done, "Bad sectors_per_unit"); torture_assert_goto(tctx, fsinfo.size_info.out.bytes_per_sector == 512, ok, done, "Bad bytes_per_sector"); torture_assert_goto(tctx, fsinfo.size_info.out.total_alloc_units == 32, ok, done, "Bad total_alloc_units"); torture_assert_goto(tctx, fsinfo.size_info.out.avail_alloc_units == 16, ok, done, "Bad avail_alloc_units"); done: if (!smb2_util_handle_empty(h)) { smb2_util_close(tree, h); } smb2_deltree(tree, "test.sparsebundle"); talloc_free(mem_ctx); return ok; } struct torture_suite *torture_vfs_fruit_timemachine(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create( ctx, "fruit_timemachine"); suite->description = talloc_strdup( suite, "vfs_fruit tests for TimeMachine"); torture_suite_add_1smb2_test(suite, "Timemachine-volsize", test_timemachine_volsize); return suite; } static bool test_convert_xattr_and_empty_rfork_then_delete( struct torture_context *tctx, struct smb2_tree *tree1, struct smb2_tree *tree2) { TALLOC_CTX *mem_ctx = talloc_new(tctx); const char *fname = BASEDIR "\\test_adouble_conversion"; const char *adname = BASEDIR "/._test_adouble_conversion"; const char *rfork = BASEDIR "\\test_adouble_conversion" AFPRESOURCE_STREAM_NAME; NTSTATUS status; struct smb2_handle testdirh; bool ret = true; const char *streams[] = { "::$DATA", AFPINFO_STREAM, ":com.apple.metadata" "\xef\x80\xa2" "_kMDItemUserTags:$DATA", ":foo" "\xef\x80\xa2" "bar:$DATA", /* "foo:bar:$DATA" */ }; struct smb2_create create; struct smb2_find find; unsigned int count; union smb_search_data *d; bool delete_empty_adfiles; int expected_num_files; delete_empty_adfiles = torture_setting_bool(tctx, "delete_empty_adfiles", false); smb2_deltree(tree1, BASEDIR); status = torture_smb2_testdir(tree1, BASEDIR, &testdirh); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed\n"); smb2_util_close(tree1, testdirh); ret = torture_setup_file(tctx, tree1, fname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = torture_setup_file(tctx, tree1, adname, false); torture_assert_goto(tctx, ret == true, ret, done, "torture_setup_file failed\n"); ret = write_stream(tree1, __location__, tctx, mem_ctx, adname, NULL, 0, sizeof(osx_adouble_w_xattr), osx_adouble_w_xattr); torture_assert_goto(tctx, ret == true, ret, done, "write_stream failed\n"); ret = enable_aapl(tctx, tree2); torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed"); /* * Issue a smb2_find(), this triggers the server-side conversion */ create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_DIR_READ, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.share_access = NTCREATEX_SHARE_ACCESS_READ, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = BASEDIR, }; status = smb2_create(tree2, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); find = (struct smb2_find) { .in.file.handle = create.out.file.handle, .in.pattern = "*", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, }; status = smb2_find_level(tree2, tree2, &find, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); status = smb2_util_close(tree2, create.out.file.handle); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed"); /* * Check number of streams */ ret = check_stream_list(tree2, tctx, fname, 4, streams, false); torture_assert_goto(tctx, ret == true, ret, done, "check_stream_list"); /* * Check Resource Fork is gone */ create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE, .in.file_attributes = FILE_ATTRIBUTE_NORMAL, .in.share_access = NTCREATEX_SHARE_ACCESS_READ, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = rfork, }; status = smb2_create(tree2, mem_ctx, &create); torture_assert_ntstatus_equal_goto( tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, "Bad smb2_create return\n"); /* * Check xattr data has been migrated from the AppleDouble file to * streams. */ ret = check_stream(tree2, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM, 0, 60, 16, 8, "TESTSLOW"); torture_assert_goto(tctx, ret == true, ret, done, "check AFPINFO_STREAM failed\n"); ret = check_stream(tree2, __location__, tctx, mem_ctx, fname, ":foo" "\xef\x80\xa2" "bar", /* foo:bar */ 0, 3, 0, 3, "baz"); torture_assert_goto(tctx, ret == true, ret, done, "check foo stream failed\n"); /* * Now check number of files. If delete_empty_adfiles is set, the * AppleDouble files should have been deleted. */ create = (struct smb2_create) { .in.desired_access = SEC_RIGHTS_DIR_READ, .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, .in.share_access = NTCREATEX_SHARE_ACCESS_READ, .in.create_disposition = NTCREATEX_DISP_OPEN, .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, .in.fname = BASEDIR, }; status = smb2_create(tree2, tctx, &create); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); find = (struct smb2_find) { .in.file.handle = create.out.file.handle, .in.pattern = "*", .in.max_response_size = 0x1000, .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, }; status = smb2_find_level(tree2, tree2, &find, &count, &d); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_level failed\n"); status = smb2_util_close(tree2, create.out.file.handle); torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed"); if (delete_empty_adfiles) { expected_num_files = 3; } else { expected_num_files = 4; } torture_assert_int_equal_goto(tctx, count, expected_num_files, ret, done, "Wrong number of files\n"); done: smb2_deltree(tree1, BASEDIR); talloc_free(mem_ctx); return ret; } struct torture_suite *torture_vfs_fruit_conversion(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create( ctx, "fruit_conversion"); suite->description = talloc_strdup( suite, "vfs_fruit conversion tests"); torture_suite_add_2ns_smb2_test( suite, "convert_xattr_and_empty_rfork_then_delete", test_convert_xattr_and_empty_rfork_then_delete); return suite; }