/* Unix SMB/CIFS implementation. generic testing tool - version with both SMB and SMB2 support Copyright (C) Andrew Tridgell 2003-2008 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 "lib/cmdline/popt_common.h" #include "lib/events/events.h" #include "system/time.h" #include "system/filesys.h" #include "libcli/raw/request.h" #include "libcli/libcli.h" #include "libcli/raw/libcliraw.h" #include "libcli/smb2/smb2.h" #include "libcli/smb2/smb2_calls.h" #include "librpc/gen_ndr/security.h" #include "librpc/gen_ndr/ndr_security.h" #include "auth/credentials/credentials.h" #include "libcli/resolve/resolve.h" #include "auth/gensec/gensec.h" #include "param/param.h" #include "dynconfig/dynconfig.h" #include "libcli/security/security.h" #include "libcli/raw/raw_proto.h" #include "../libcli/smb/smbXcli_base.h" #define NSERVERS 2 #define NINSTANCES 2 /* global options */ static struct gentest_options { int showall; int analyze; int analyze_always; int analyze_continuous; unsigned int max_open_handles; unsigned int seed; unsigned int numops; int use_oplocks; char **ignore_patterns; const char *seeds_file; int use_preset_seeds; int fast_reconnect; int mask_indexing; int no_eas; int no_acls; int skip_cleanup; int valid; int smb2; } options; /* mapping between open handles on the server and local handles */ static struct { bool active; unsigned int instance; struct smb2_handle smb2_handle[NSERVERS]; /* SMB2 */ uint16_t smb_handle[NSERVERS]; /* SMB */ const char *name; } *open_handles; static unsigned int num_open_handles; /* state information for the servers. We open NINSTANCES connections to each server */ static struct { struct smb2_tree *smb2_tree[NINSTANCES]; struct smbcli_tree *smb_tree[NINSTANCES]; char *server_name; char *share_name; struct cli_credentials *credentials; } servers[NSERVERS]; /* the seeds and flags for each operation */ static struct { unsigned int seed; bool disabled; } *op_parms; /* oplock break info */ static struct { bool got_break; struct smb2_handle smb2_handle; uint16_t smb_handle; uint16_t handle; uint8_t level; bool do_close; } oplocks[NSERVERS][NINSTANCES]; /* change notify reply info */ static struct { int notify_count; NTSTATUS status; union smb_notify notify; } notifies[NSERVERS][NINSTANCES]; /* info relevant to the current operation */ static struct { const char *name; unsigned int seed; NTSTATUS status; unsigned int opnum; TALLOC_CTX *mem_ctx; const char *mismatch; } current_op; static struct smb2_handle bad_smb2_handle; #define BAD_HANDLE 0xFFFE static bool oplock_handler_smb2(struct smb2_transport *transport, const struct smb2_handle *handle, uint8_t level, void *private_data); static void idle_func_smb2(struct smb2_transport *transport, void *private_data); static bool oplock_handler_smb(struct smbcli_transport *transport, uint16_t tid, uint16_t fnum, uint8_t level, void *private_data); static void idle_func_smb(struct smbcli_transport *transport, void *private_data); /* check if a string should be ignored. This is used as the basis for all error ignore settings */ static bool ignore_pattern(const char *str) { int i; if (!options.ignore_patterns) return false; for (i=0;options.ignore_patterns[i];i++) { if (strcmp(options.ignore_patterns[i], str) == 0 || gen_fnmatch(options.ignore_patterns[i], str) == 0) { DEBUG(2,("Ignoring '%s'\n", str)); return true; } } return false; } /***************************************************** connect to the servers *******************************************************/ static bool connect_servers_fast(void) { int h, i; /* close all open files */ for (h=0;hsession->transport->oplock.handler = oplock_handler_smb2; servers[i].smb2_tree[j]->session->transport->oplock.private_data = (void *)(uintptr_t)((i<<8)|j); smb2_transport_idle_handler(servers[i].smb2_tree[j]->session->transport, idle_func_smb2, 50000, NULL); } else { smbcli_oplock_handler(servers[i].smb_tree[j]->session->transport, oplock_handler_smb, (void *)(uintptr_t)((i<<8)|j)); smbcli_transport_idle_handler(servers[i].smb_tree[j]->session->transport, idle_func_smb, 50000, (void *)(uintptr_t)((i<<8)|j)); } } } return true; } /* work out the time skew between the servers - be conservative */ static unsigned int time_skew(void) { unsigned int ret; NTTIME nt0, nt1; if (options.smb2) { struct smbXcli_conn *c0, *c1; c0 = servers[0].smb2_tree[0]->session->transport->conn; c1 = servers[1].smb2_tree[0]->session->transport->conn; nt0 = smbXcli_conn_server_system_time(c0); nt1 = smbXcli_conn_server_system_time(c1); } else { nt0 = servers[0].smb_tree[0]->session->transport->negotiate.server_time; nt1 = servers[1].smb_tree[0]->session->transport->negotiate.server_time; } /* Samba's NTTIME is unsigned, abs() won't work! */ if (nt0 > nt1){ ret = nt0 - nt1; } else { ret = nt1 - nt0; } return ret + 300; } static bool smb2_handle_equal(const struct smb2_handle *h1, const struct smb2_handle *h2) { return memcmp(h1, h2, sizeof(struct smb2_handle)) == 0; } /* turn a server handle into a local handle */ static unsigned int fnum_to_handle_smb2(int server, int instance, struct smb2_handle server_handle) { unsigned int i; for (i=0;i 0 && count++ < 10*options.max_open_handles) { h = random() % options.max_open_handles; if (open_handles[h].active && open_handles[h].instance == instance) { return h; } } return BAD_HANDLE; } /* return a file handle, but skewed so we don't close the last couple of handles too readily */ static uint16_t gen_fnum_close(int instance) { if (num_open_handles < 5) { if (gen_chance(90)) return BAD_HANDLE; } return gen_fnum(instance); } /* generate an integer in a specified range */ static int gen_int_range(uint64_t min, uint64_t max) { unsigned int r = random(); return min + (r % (1+max-min)); } /* return a fnum for use as a root fid be careful to call GEN_SET_FNUM() when you use this! */ static uint16_t gen_root_fid(int instance) { if (gen_chance(5)) return gen_fnum(instance); return 0; } /* generate a file offset */ static int gen_offset(void) { if (gen_chance(20)) return 0; // if (gen_chance(5)) return gen_int_range(0, 0xFFFFFFFF); return gen_int_range(0, 1024*1024); } /* generate a io count */ static int gen_io_count(void) { if (gen_chance(20)) return 0; // if (gen_chance(5)) return gen_int_range(0, 0xFFFFFFFF); return gen_int_range(0, 4096); } /* generate a filename */ static const char *gen_fname(void) { const char *names[] = {"gentest\\gentest.dat", "gentest\\foo", "gentest\\foo2.sym", "gentest\\foo3.dll", "gentest\\foo4", "gentest\\foo4:teststream1", "gentest\\foo4:teststream2", "gentest\\foo5.exe", "gentest\\foo5.exe:teststream3", "gentest\\foo5.exe:teststream4", "gentest\\foo6.com", "gentest\\blah", "gentest\\blah\\blergh.txt", "gentest\\blah\\blergh2", "gentest\\blah\\blergh3.txt", "gentest\\blah\\blergh4", "gentest\\blah\\blergh5.txt", "gentest\\blah\\blergh5", "gentest\\blah\\.", "gentest\\blah\\..", "gentest\\a_very_long_name.bin", "gentest\\x.y", "gentest\\blah"}; int i; do { i = gen_int_range(0, ARRAY_SIZE(names)-1); } while (ignore_pattern(names[i])); return names[i]; } /* generate a filename with a higher chance of choosing an already open file */ static const char *gen_fname_open(int instance) { uint16_t h; h = gen_fnum(instance); if (h == BAD_HANDLE) { return gen_fname(); } return open_handles[h].name; } /* generate a wildcard pattern */ static const char *gen_pattern(void) { int i; const char *names[] = {"gentest\\*.dat", "gentest\\*", "gentest\\*.*", "gentest\\blah\\*.*", "gentest\\blah\\*", "gentest\\?"}; if (gen_chance(50)) return gen_fname(); do { i = gen_int_range(0, ARRAY_SIZE(names)-1); } while (ignore_pattern(names[i])); return names[i]; } static uint32_t gen_bits_levels(int nlevels, ...) { va_list ap; uint32_t pct; uint32_t mask; int i; va_start(ap, nlevels); for (i=0;isession->transport && tid == servers[i].smb_tree[j]->tid) { oplocks[i][j].got_break = true; oplocks[i][j].smb_handle = fnum; oplocks[i][j].handle = fnum_to_handle_smb(i, j, fnum); oplocks[i][j].level = level; oplocks[i][j].do_close = do_close; tree = servers[i].smb_tree[j]; } } } if (!tree) { printf("Oplock break not for one of our trees!?\n"); return false; } if (!do_close) { printf("oplock ack fnum=%d\n", fnum); return smbcli_oplock_ack(tree, fnum, level); } printf("oplock close fnum=%d\n", fnum); io.close.level = RAW_CLOSE_CLOSE; io.close.in.file.fnum = fnum; io.close.in.write_time = 0; req = smb_raw_close_send(tree, &io); if (req == NULL) { printf("WARNING: close failed in oplock_handler_close\n"); return false; } req->async.fn = oplock_handler_close_recv_smb; req->async.private_data = NULL; return true; } /* the idle function tries to cope with getting an oplock break on a connection, and an operation on another connection blocking until that break is acked we check for operations on all transports in the idle function */ static void idle_func_smb(struct smbcli_transport *transport, void *private_data) { int i, j; for (i=0;isession->transport) { smbcli_transport_process(servers[i].smb_tree[j]->session->transport); } } } } static void oplock_handler_close_recv_smb2(struct smb2_request *req) { NTSTATUS status; struct smb2_close io; status = smb2_close_recv(req, &io); if (!NT_STATUS_IS_OK(status)) { printf("close failed in oplock_handler\n"); smb_panic("close failed in oplock_handler"); } } static void oplock_handler_ack_callback_smb2(struct smb2_request *req) { NTSTATUS status; struct smb2_break br; status = smb2_break_recv(req, &br); if (!NT_STATUS_IS_OK(status)) { printf("oplock break ack failed in oplock_handler\n"); smb_panic("oplock break ack failed in oplock_handler"); } } static bool send_oplock_ack_smb2(struct smb2_tree *tree, struct smb2_handle handle, uint8_t level) { struct smb2_break br; struct smb2_request *req; ZERO_STRUCT(br); br.in.file.handle = handle; br.in.oplock_level = level; br.in.reserved = gen_reserved8(); br.in.reserved2 = gen_reserved32(); req = smb2_break_send(tree, &br); if (req == NULL) return false; req->async.fn = oplock_handler_ack_callback_smb2; req->async.private_data = NULL; return true; } /* the oplock handler will either ack the break or close the file */ static bool oplock_handler_smb2(struct smb2_transport *transport, const struct smb2_handle *handle, uint8_t level, void *private_data) { struct smb2_close io; unsigned i, j; bool do_close; struct smb2_tree *tree = NULL; struct smb2_request *req; srandom(current_op.seed); do_close = gen_chance(50); i = ((uintptr_t)private_data) >> 8; j = ((uintptr_t)private_data) & 0xFF; if (i >= NSERVERS || j >= NINSTANCES) { printf("Bad private_data in oplock_handler\n"); return false; } oplocks[i][j].got_break = true; oplocks[i][j].smb2_handle = *handle; oplocks[i][j].handle = fnum_to_handle_smb2(i, j, *handle); oplocks[i][j].level = level; oplocks[i][j].do_close = do_close; tree = talloc_get_type(servers[i].smb2_tree[j], struct smb2_tree); if (!tree) { printf("Oplock break not for one of our trees!?\n"); return false; } if (!do_close) { printf("oplock ack handle=%d\n", oplocks[i][j].handle); return send_oplock_ack_smb2(tree, *handle, level); } printf("oplock close fnum=%d\n", oplocks[i][j].handle); ZERO_STRUCT(io); io.in.file.handle = *handle; io.in.flags = 0; req = smb2_close_send(tree, &io); if (req == NULL) { printf("WARNING: close failed in oplock_handler_close\n"); return false; } req->async.fn = oplock_handler_close_recv_smb2; req->async.private_data = NULL; return true; } /* the idle function tries to cope with getting an oplock break on a connection, and an operation on another connection blocking until that break is acked we check for operations on all transports in the idle function */ static void idle_func_smb2(struct smb2_transport *transport, void *private_data) { int i, j; for (i=0;isession->transport) { // smb2_transport_process(servers[i].smb2_tree[j]->session->transport); } } } } /* compare NTSTATUS, using checking ignored patterns */ static bool compare_status(NTSTATUS status1, NTSTATUS status2) { char *s; if (NT_STATUS_EQUAL(status1, status2)) return true; /* one code being an error and the other OK is always an error */ if (NT_STATUS_IS_OK(status1) || NT_STATUS_IS_OK(status2)) { current_op.mismatch = nt_errstr(status1); return false; } /* if we are ignoring one of the status codes then consider this a match */ if (ignore_pattern(nt_errstr(status1)) || ignore_pattern(nt_errstr(status2))) { return true; } /* also support ignore patterns of the form NT_STATUS_XX:NT_STATUS_YY meaning that the first server returns NT_STATUS_XX and the 2nd returns NT_STATUS_YY */ s = talloc_asprintf(current_op.mem_ctx, "%s:%s", nt_errstr(status1), nt_errstr(status2)); if (ignore_pattern(s)) { return true; } current_op.mismatch = nt_errstr(status1); return false; } /* check for pending packets on all connections */ static void check_pending(void) { int i, j; smb_msleep(20); for (j=0;jsession->transport); } } } /* check that the same oplock breaks have been received by all instances */ static bool check_oplocks(const char *call) { int i, j; int tries = 0; if (!options.use_oplocks || options.smb2) { /* no smb2 oplocks in gentest yet */ return true; } again: check_pending(); for (j=0;jdacl, parm[1].field->dacl) && !ignore_pattern(#field)) { \ current_op.mismatch = #field; \ printf("Mismatch in %s\n", #field); \ return false; \ } \ } while(0) #define CHECK_ATTRIB(field) do { \ if (!options.mask_indexing) { \ CHECK_EQUAL(field); \ } else if ((~FILE_ATTRIBUTE_NONINDEXED & parm[0].field) != (~FILE_ATTRIBUTE_NONINDEXED & parm[1].field) && !ignore_pattern(#field)) { \ current_op.mismatch = #field; \ printf("Mismatch in %s - 0x%x 0x%x\n", #field, \ (int)parm[0].field, (int)parm[1].field); \ return false; \ } \ } while(0) #define CHECK_WSTR_EQUAL(field) do { \ if ((!parm[0].field.s && parm[1].field.s) || (parm[0].field.s && !parm[1].field.s)) { \ current_op.mismatch = #field; \ printf("%s is NULL!\n", #field); \ return false; \ } \ if (parm[0].field.s && strcmp(parm[0].field.s, parm[1].field.s) != 0 && !ignore_pattern(#field)) { \ current_op.mismatch = #field; \ printf("Mismatch in %s - %s %s\n", #field, \ parm[0].field.s, parm[1].field.s); \ return false; \ } \ CHECK_EQUAL(field.private_length); \ } while(0) #define CHECK_BLOB_EQUAL(field) do { \ if (((parm[0].field.data == NULL && parm[1].field.data != NULL) || \ (parm[1].field.data == NULL && parm[0].field.data != NULL) || \ (memcmp(parm[0].field.data, parm[1].field.data, parm[0].field.length) != 0)) && !ignore_pattern(#field)) { \ current_op.mismatch = #field; \ printf("Mismatch in %s\n", #field); \ return false; \ } \ CHECK_EQUAL(field.length); \ } while(0) #define CHECK_TIMES_EQUAL(field) do { \ if (labs(parm[0].field - parm[1].field) > time_skew() && \ !ignore_pattern(#field)) { \ current_op.mismatch = #field; \ printf("Mismatch in %s - 0x%x 0x%x\n", #field, \ (int)parm[0].field, (int)parm[1].field); \ return false; \ } \ } while(0) #define CHECK_NTTIMES_EQUAL(field) do { \ if (labs(nt_time_to_unix(parm[0].field) - \ nt_time_to_unix(parm[1].field)) > time_skew() && \ !ignore_pattern(#field)) { \ current_op.mismatch = #field; \ printf("Mismatch in %s - 0x%x 0x%x\n", #field, \ (int)nt_time_to_unix(parm[0].field), \ (int)nt_time_to_unix(parm[1].field)); \ return false; \ } \ } while(0) /* compare returned fileinfo structures */ static bool cmp_fileinfo(int instance, union smb_fileinfo parm[NSERVERS], NTSTATUS status[NSERVERS]) { int i; enum smb_fileinfo_level level = parm[0].generic.level; if (level == RAW_FILEINFO_ALL_INFORMATION && options.smb2) { level = RAW_FILEINFO_SMB2_ALL_INFORMATION; } switch (level) { case RAW_FILEINFO_GENERIC: return false; case RAW_FILEINFO_GETATTR: CHECK_ATTRIB(getattr.out.attrib); CHECK_EQUAL(getattr.out.size); CHECK_TIMES_EQUAL(getattr.out.write_time); break; case RAW_FILEINFO_GETATTRE: CHECK_TIMES_EQUAL(getattre.out.create_time); CHECK_TIMES_EQUAL(getattre.out.access_time); CHECK_TIMES_EQUAL(getattre.out.write_time); CHECK_EQUAL(getattre.out.size); CHECK_EQUAL(getattre.out.alloc_size); CHECK_ATTRIB(getattre.out.attrib); break; case RAW_FILEINFO_STANDARD: CHECK_TIMES_EQUAL(standard.out.create_time); CHECK_TIMES_EQUAL(standard.out.access_time); CHECK_TIMES_EQUAL(standard.out.write_time); CHECK_EQUAL(standard.out.size); CHECK_EQUAL(standard.out.alloc_size); CHECK_ATTRIB(standard.out.attrib); break; case RAW_FILEINFO_EA_SIZE: CHECK_TIMES_EQUAL(ea_size.out.create_time); CHECK_TIMES_EQUAL(ea_size.out.access_time); CHECK_TIMES_EQUAL(ea_size.out.write_time); CHECK_EQUAL(ea_size.out.size); CHECK_EQUAL(ea_size.out.alloc_size); CHECK_ATTRIB(ea_size.out.attrib); CHECK_EQUAL(ea_size.out.ea_size); break; case RAW_FILEINFO_ALL_EAS: CHECK_EQUAL(all_eas.out.num_eas); for (i=0;i 0) { parm[0].lockx.in.locks = talloc_array(current_op.mem_ctx, struct smb_lock_entry, nlocks); for (n=0;ngeneric.level = levels[i].level; switch (info->generic.level) { case RAW_SFILEINFO_SETATTR: info->setattr.in.attrib = gen_attrib(); info->setattr.in.write_time = gen_timet(); break; case RAW_SFILEINFO_SETATTRE: info->setattre.in.create_time = gen_timet(); info->setattre.in.access_time = gen_timet(); info->setattre.in.write_time = gen_timet(); break; case RAW_SFILEINFO_STANDARD: info->standard.in.create_time = gen_timet(); info->standard.in.access_time = gen_timet(); info->standard.in.write_time = gen_timet(); break; case RAW_SFILEINFO_EA_SET: { static struct ea_struct ea; info->ea_set.in.num_eas = 1; info->ea_set.in.eas = &ea; info->ea_set.in.eas[0] = gen_ea_struct(); } break; case RAW_SFILEINFO_BASIC_INFO: case RAW_SFILEINFO_BASIC_INFORMATION: info->basic_info.in.create_time = gen_nttime(); info->basic_info.in.access_time = gen_nttime(); info->basic_info.in.write_time = gen_nttime(); info->basic_info.in.change_time = gen_nttime(); info->basic_info.in.attrib = gen_attrib(); break; case RAW_SFILEINFO_DISPOSITION_INFO: case RAW_SFILEINFO_DISPOSITION_INFORMATION: info->disposition_info.in.delete_on_close = gen_bool(); break; case RAW_SFILEINFO_ALLOCATION_INFO: case RAW_SFILEINFO_ALLOCATION_INFORMATION: info->allocation_info.in.alloc_size = gen_alloc_size(); break; case RAW_SFILEINFO_END_OF_FILE_INFO: case RAW_SFILEINFO_END_OF_FILE_INFORMATION: info->end_of_file_info.in.size = gen_offset(); break; case RAW_SFILEINFO_RENAME_INFORMATION: case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: info->rename_information.in.overwrite = gen_bool(); info->rename_information.in.root_fid = gen_root_fid(instance); info->rename_information.in.new_name = gen_fname_open(instance); break; case RAW_SFILEINFO_POSITION_INFORMATION: info->position_information.in.position = gen_offset(); break; case RAW_SFILEINFO_MODE_INFORMATION: info->mode_information.in.mode = gen_bits_mask(0xFFFFFFFF); break; case RAW_SFILEINFO_FULL_EA_INFORMATION: info->full_ea_information.in.eas = gen_ea_list(); break; case RAW_SFILEINFO_GENERIC: case RAW_SFILEINFO_SEC_DESC: case RAW_SFILEINFO_UNIX_BASIC: case RAW_SFILEINFO_UNIX_LINK: case RAW_SFILEINFO_UNIX_HLINK: case RAW_SFILEINFO_1023: case RAW_SFILEINFO_1025: case RAW_SFILEINFO_1029: case RAW_SFILEINFO_1032: case RAW_SFILEINFO_1039: case RAW_SFILEINFO_1040: case RAW_SFILEINFO_UNIX_INFO2: /* Untested */ break; } } #endif /* generate a fileinfo query structure */ static void gen_setfileinfo(int instance, union smb_setfileinfo *info) { int i; #undef LVL #define LVL(v) {RAW_SFILEINFO_ ## v, "RAW_SFILEINFO_" #v} struct levels { enum smb_setfileinfo_level level; const char *name; }; struct levels smb_levels[] = { LVL(EA_SET), LVL(BASIC_INFO), LVL(DISPOSITION_INFO), LVL(STANDARD), LVL(ALLOCATION_INFO), LVL(END_OF_FILE_INFO), LVL(SETATTR), LVL(SETATTRE), LVL(BASIC_INFORMATION), LVL(RENAME_INFORMATION), LVL(DISPOSITION_INFORMATION), LVL(POSITION_INFORMATION), LVL(FULL_EA_INFORMATION), LVL(MODE_INFORMATION), LVL(ALLOCATION_INFORMATION), LVL(END_OF_FILE_INFORMATION), LVL(PIPE_INFORMATION), LVL(VALID_DATA_INFORMATION), LVL(SHORT_NAME_INFORMATION), LVL(1025), LVL(1027), LVL(1029), LVL(1030), LVL(1031), LVL(1032), LVL(1036), LVL(1041), LVL(1042), LVL(1043), LVL(1044), }; struct levels smb2_levels[] = { LVL(BASIC_INFORMATION), LVL(RENAME_INFORMATION), LVL(DISPOSITION_INFORMATION), LVL(POSITION_INFORMATION), LVL(FULL_EA_INFORMATION), LVL(MODE_INFORMATION), LVL(ALLOCATION_INFORMATION), LVL(END_OF_FILE_INFORMATION), LVL(PIPE_INFORMATION), LVL(VALID_DATA_INFORMATION), LVL(SHORT_NAME_INFORMATION), LVL(1025), LVL(1027), LVL(1029), LVL(1030), LVL(1031), LVL(1032), LVL(1036), LVL(1041), LVL(1042), LVL(1043), LVL(1044), }; struct levels *levels = options.smb2?smb2_levels:smb_levels; uint32_t num_levels = options.smb2?ARRAY_SIZE(smb2_levels):ARRAY_SIZE(smb_levels); do { i = gen_int_range(0, num_levels-1); } while (ignore_pattern(levels[i].name)); ZERO_STRUCTP(info); info->generic.level = levels[i].level; switch (info->generic.level) { case RAW_SFILEINFO_SETATTR: info->setattr.in.attrib = gen_attrib(); info->setattr.in.write_time = gen_timet(); break; case RAW_SFILEINFO_SETATTRE: info->setattre.in.create_time = gen_timet(); info->setattre.in.access_time = gen_timet(); info->setattre.in.write_time = gen_timet(); break; case RAW_SFILEINFO_STANDARD: info->standard.in.create_time = gen_timet(); info->standard.in.access_time = gen_timet(); info->standard.in.write_time = gen_timet(); break; case RAW_SFILEINFO_EA_SET: { static struct ea_struct ea; info->ea_set.in.num_eas = 1; info->ea_set.in.eas = &ea; info->ea_set.in.eas[0] = gen_ea_struct(); break; } case RAW_SFILEINFO_BASIC_INFO: case RAW_SFILEINFO_BASIC_INFORMATION: info->basic_info.in.create_time = gen_nttime(); info->basic_info.in.access_time = gen_nttime(); info->basic_info.in.write_time = gen_nttime(); info->basic_info.in.change_time = gen_nttime(); info->basic_info.in.attrib = gen_attrib(); break; case RAW_SFILEINFO_DISPOSITION_INFO: case RAW_SFILEINFO_DISPOSITION_INFORMATION: info->disposition_info.in.delete_on_close = gen_bool(); break; case RAW_SFILEINFO_ALLOCATION_INFO: case RAW_SFILEINFO_ALLOCATION_INFORMATION: info->allocation_info.in.alloc_size = gen_alloc_size(); break; case RAW_SFILEINFO_END_OF_FILE_INFO: case RAW_SFILEINFO_END_OF_FILE_INFORMATION: info->end_of_file_info.in.size = gen_offset(); break; case RAW_SFILEINFO_RENAME_INFORMATION: case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: info->rename_information.in.overwrite = gen_bool(); info->rename_information.in.root_fid = gen_root_fid(instance); info->rename_information.in.new_name = gen_fname_open(instance); break; case RAW_SFILEINFO_POSITION_INFORMATION: info->position_information.in.position = gen_offset(); break; case RAW_SFILEINFO_MODE_INFORMATION: info->mode_information.in.mode = gen_bits_mask(0xFFFFFFFF); break; case RAW_SFILEINFO_FULL_EA_INFORMATION: info->full_ea_information.in.eas = gen_ea_list(); break; case RAW_SFILEINFO_GENERIC: case RAW_SFILEINFO_SEC_DESC: case RAW_SFILEINFO_1025: case RAW_SFILEINFO_1029: case RAW_SFILEINFO_1032: case RAW_SFILEINFO_UNIX_BASIC: case RAW_SFILEINFO_UNIX_INFO2: case RAW_SFILEINFO_UNIX_LINK: case RAW_SFILEINFO_UNIX_HLINK: case RAW_SFILEINFO_LINK_INFORMATION: case RAW_SFILEINFO_PIPE_INFORMATION: case RAW_SFILEINFO_VALID_DATA_INFORMATION: case RAW_SFILEINFO_SHORT_NAME_INFORMATION: case RAW_SFILEINFO_1027: case RAW_SFILEINFO_1030: case RAW_SFILEINFO_1031: case RAW_SFILEINFO_1036: case RAW_SFILEINFO_1041: case RAW_SFILEINFO_1042: case RAW_SFILEINFO_1043: case RAW_SFILEINFO_1044: /* Untested */ break; } } /* generate a fileinfo query structure */ static void gen_fileinfo_smb(int instance, union smb_fileinfo *info) { int i; #undef LVL #define LVL(v) {RAW_FILEINFO_ ## v, "RAW_FILEINFO_" #v} struct { enum smb_fileinfo_level level; const char *name; } levels[] = { LVL(GETATTR), LVL(GETATTRE), LVL(STANDARD), LVL(EA_SIZE), LVL(ALL_EAS), LVL(IS_NAME_VALID), LVL(BASIC_INFO), LVL(STANDARD_INFO), LVL(EA_INFO), LVL(NAME_INFO), LVL(ALL_INFO), LVL(ALT_NAME_INFO), LVL(STREAM_INFO), LVL(COMPRESSION_INFO), LVL(BASIC_INFORMATION), LVL(STANDARD_INFORMATION), LVL(INTERNAL_INFORMATION), LVL(EA_INFORMATION), LVL(ACCESS_INFORMATION), LVL(NAME_INFORMATION), LVL(POSITION_INFORMATION), LVL(MODE_INFORMATION), LVL(ALIGNMENT_INFORMATION), LVL(ALL_INFORMATION), LVL(ALT_NAME_INFORMATION), LVL(STREAM_INFORMATION), LVL(COMPRESSION_INFORMATION), LVL(NETWORK_OPEN_INFORMATION), LVL(ATTRIBUTE_TAG_INFORMATION) }; do { i = gen_int_range(0, ARRAY_SIZE(levels)-1); } while (ignore_pattern(levels[i].name)); info->generic.level = levels[i].level; } /* generate qpathinfo operations */ static bool handler_smb_qpathinfo(int instance) { union smb_fileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.in.file.path = gen_fname_open(instance); gen_fileinfo_smb(instance, &parm[0]); GEN_COPY_PARM; GEN_CALL_SMB(smb_raw_pathinfo(tree, current_op.mem_ctx, &parm[i])); return cmp_fileinfo(instance, parm, status); } /* generate qfileinfo operations */ static bool handler_smb_qfileinfo(int instance) { union smb_fileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.in.file.fnum = gen_fnum(instance); gen_fileinfo_smb(instance, &parm[0]); GEN_COPY_PARM; GEN_SET_FNUM_SMB(generic.in.file.fnum); GEN_CALL_SMB(smb_raw_fileinfo(tree, current_op.mem_ctx, &parm[i])); return cmp_fileinfo(instance, parm, status); } /* generate setpathinfo operations */ static bool handler_smb_spathinfo(int instance) { union smb_setfileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; gen_setfileinfo(instance, &parm[0]); parm[0].generic.in.file.path = gen_fname_open(instance); GEN_COPY_PARM; /* a special case for the fid in a RENAME */ if (parm[0].generic.level == RAW_SFILEINFO_RENAME_INFORMATION && parm[0].rename_information.in.root_fid != 0) { GEN_SET_FNUM_SMB(rename_information.in.root_fid); } GEN_CALL_SMB(smb_raw_setpathinfo(tree, &parm[i])); return true; } /* generate setfileinfo operations */ static bool handler_smb_sfileinfo(int instance) { union smb_setfileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.in.file.fnum = gen_fnum(instance); gen_setfileinfo(instance, &parm[0]); GEN_COPY_PARM; GEN_SET_FNUM_SMB(generic.in.file.fnum); GEN_CALL_SMB(smb_raw_setfileinfo(tree, &parm[i])); return true; } /* this is called when a change notify reply comes in */ static void async_notify_smb(struct smbcli_request *req) { union smb_notify notify; NTSTATUS status; int i, j; uint16_t tid = 0; struct smbcli_transport *transport = req->transport; if (req->tree) { tid = req->tree->tid; } notify.nttrans.level = RAW_NOTIFY_NTTRANS; status = smb_raw_changenotify_recv(req, current_op.mem_ctx, ¬ify); if (NT_STATUS_IS_OK(status) && notify.nttrans.out.num_changes > 0) { printf("notify tid=%d num_changes=%d action=%d name=%s\n", tid, notify.nttrans.out.num_changes, notify.nttrans.out.changes[0].action, notify.nttrans.out.changes[0].name.s); } for (i=0;isession->transport && tid == servers[i].smb_tree[j]->tid) { notifies[i][j].notify_count++; notifies[i][j].status = status; notifies[i][j].notify = notify; } } } } /* generate change notify operations */ static bool handler_smb_notify(int instance) { union smb_notify parm[NSERVERS]; int n; ZERO_STRUCT(parm[0]); parm[0].nttrans.level = RAW_NOTIFY_NTTRANS; parm[0].nttrans.in.buffer_size = gen_io_count(); parm[0].nttrans.in.completion_filter = gen_bits_mask(0xFF); parm[0].nttrans.in.file.fnum = gen_fnum(instance); parm[0].nttrans.in.recursive = gen_bool(); GEN_COPY_PARM; GEN_SET_FNUM_SMB(nttrans.in.file.fnum); for (n=0;nasync.fn = async_notify_smb; } return true; } /* generate ntcreatex operations */ static bool handler_smb2_create(int instance) { struct smb2_create parm[NSERVERS]; NTSTATUS status[NSERVERS]; ZERO_STRUCT(parm[0]); parm[0].in.security_flags = gen_bits_levels(3, 90, 0x0, 70, 0x3, 100, 0xFF); parm[0].in.oplock_level = gen_bits_levels(3, 90, 0x0, 70, 0x9, 100, 0xFF); parm[0].in.impersonation_level = gen_bits_levels(3, 90, 0x0, 70, 0x3, 100, 0xFFFFFFFF); parm[0].in.create_flags = gen_reserved64(); parm[0].in.reserved = gen_reserved64(); parm[0].in.desired_access = gen_access_mask(); parm[0].in.file_attributes = gen_attrib(); parm[0].in.share_access = gen_bits_mask2(0x7, 0xFFFFFFFF); parm[0].in.create_disposition = gen_open_disp(); parm[0].in.create_options = gen_create_options(); parm[0].in.fname = gen_fname_open(instance); parm[0].in.eas = gen_ea_list(); parm[0].in.alloc_size = gen_alloc_size(); parm[0].in.durable_open = gen_bool(); parm[0].in.query_maximal_access = gen_bool(); parm[0].in.timewarp = gen_timewarp(); parm[0].in.query_on_disk_id = gen_bool(); parm[0].in.sec_desc = gen_sec_desc(); if (!options.use_oplocks) { /* mask out oplocks */ parm[0].in.oplock_level = 0; } if (options.valid) { parm[0].in.security_flags &= 3; parm[0].in.oplock_level &= 9; parm[0].in.impersonation_level &= 3; } GEN_COPY_PARM; GEN_CALL_SMB2(smb2_create(tree, current_op.mem_ctx, &parm[i])); CHECK_EQUAL(out.oplock_level); CHECK_EQUAL(out.reserved); CHECK_EQUAL(out.create_action); CHECK_NTTIMES_EQUAL(out.create_time); CHECK_NTTIMES_EQUAL(out.access_time); CHECK_NTTIMES_EQUAL(out.write_time); CHECK_NTTIMES_EQUAL(out.change_time); CHECK_EQUAL(out.alloc_size); CHECK_EQUAL(out.size); CHECK_ATTRIB(out.file_attr); CHECK_EQUAL(out.reserved2); CHECK_EQUAL(out.maximal_access); /* ntcreatex creates a new file handle */ ADD_HANDLE_SMB2(parm[0].in.fname, out.file.handle); return true; } /* generate close operations */ static bool handler_smb2_close(int instance) { struct smb2_close parm[NSERVERS]; NTSTATUS status[NSERVERS]; ZERO_STRUCT(parm[0]); parm[0].in.file.handle.data[0] = gen_fnum_close(instance); parm[0].in.flags = gen_bits_mask2(0x1, 0xFFFF); GEN_COPY_PARM; GEN_SET_FNUM_SMB2(in.file.handle); GEN_CALL_SMB2(smb2_close(tree, &parm[i])); CHECK_EQUAL(out.flags); CHECK_EQUAL(out._pad); CHECK_NTTIMES_EQUAL(out.create_time); CHECK_NTTIMES_EQUAL(out.access_time); CHECK_NTTIMES_EQUAL(out.write_time); CHECK_NTTIMES_EQUAL(out.change_time); CHECK_EQUAL(out.alloc_size); CHECK_EQUAL(out.size); CHECK_ATTRIB(out.file_attr); REMOVE_HANDLE_SMB2(in.file.handle); return true; } /* generate read operations */ static bool handler_smb2_read(int instance) { struct smb2_read parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].in.file.handle.data[0] = gen_fnum(instance); parm[0].in.reserved = gen_reserved8(); parm[0].in.length = gen_io_count(); parm[0].in.offset = gen_offset(); parm[0].in.min_count = gen_io_count(); parm[0].in.channel = gen_bits_mask2(0x0, 0xFFFFFFFF); parm[0].in.remaining = gen_bits_mask2(0x0, 0xFFFFFFFF); parm[0].in.channel_offset = gen_bits_mask2(0x0, 0xFFFF); parm[0].in.channel_length = gen_bits_mask2(0x0, 0xFFFF); GEN_COPY_PARM; GEN_SET_FNUM_SMB2(in.file.handle); GEN_CALL_SMB2(smb2_read(tree, current_op.mem_ctx, &parm[i])); CHECK_EQUAL(out.remaining); CHECK_EQUAL(out.reserved); CHECK_EQUAL(out.data.length); return true; } /* generate write operations */ static bool handler_smb2_write(int instance) { struct smb2_write parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].in.file.handle.data[0] = gen_fnum(instance); parm[0].in.offset = gen_offset(); parm[0].in.unknown1 = gen_bits_mask2(0, 0xFFFFFFFF); parm[0].in.unknown2 = gen_bits_mask2(0, 0xFFFFFFFF); parm[0].in.data = data_blob_talloc(current_op.mem_ctx, NULL, gen_io_count()); GEN_COPY_PARM; GEN_SET_FNUM_SMB2(in.file.handle); GEN_CALL_SMB2(smb2_write(tree, &parm[i])); CHECK_EQUAL(out._pad); CHECK_EQUAL(out.nwritten); CHECK_EQUAL(out.unknown1); return true; } /* generate lockingx operations */ static bool handler_smb2_lock(int instance) { struct smb2_lock parm[NSERVERS]; NTSTATUS status[NSERVERS]; int n; parm[0].level = RAW_LOCK_LOCKX; parm[0].in.file.handle.data[0] = gen_fnum(instance); parm[0].in.lock_count = gen_lock_count(); parm[0].in.lock_sequence = gen_reserved32(); parm[0].in.locks = talloc_array(current_op.mem_ctx, struct smb2_lock_element, parm[0].in.lock_count); for (n=0;nsession->transport)); return true; } /* generate a fileinfo query structure */ static void gen_fileinfo_smb2(int instance, union smb_fileinfo *info) { int i; #define LVL(v) {RAW_FILEINFO_ ## v, "RAW_FILEINFO_" #v} struct { enum smb_fileinfo_level level; const char *name; } levels[] = { LVL(BASIC_INFORMATION), LVL(STANDARD_INFORMATION), LVL(INTERNAL_INFORMATION), LVL(EA_INFORMATION), LVL(ACCESS_INFORMATION), LVL(NAME_INFORMATION), LVL(POSITION_INFORMATION), LVL(MODE_INFORMATION), LVL(ALIGNMENT_INFORMATION), LVL(SMB2_ALL_INFORMATION), LVL(ALT_NAME_INFORMATION), LVL(STREAM_INFORMATION), LVL(COMPRESSION_INFORMATION), LVL(NETWORK_OPEN_INFORMATION), LVL(ATTRIBUTE_TAG_INFORMATION), LVL(SMB2_ALL_EAS), LVL(SMB2_ALL_INFORMATION), LVL(SEC_DESC), }; do { i = gen_int_range(0, ARRAY_SIZE(levels)-1); } while (ignore_pattern(levels[i].name)); info->generic.level = levels[i].level; } /* generate qfileinfo operations */ static bool handler_smb2_qfileinfo(int instance) { union smb_fileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.in.file.handle.data[0] = gen_fnum(instance); gen_fileinfo_smb2(instance, &parm[0]); GEN_COPY_PARM; GEN_SET_FNUM_SMB2(generic.in.file.handle); GEN_CALL_SMB2(smb2_getinfo_file(tree, current_op.mem_ctx, &parm[i])); return cmp_fileinfo(instance, parm, status); } /* generate setfileinfo operations */ static bool handler_smb2_sfileinfo(int instance) { union smb_setfileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; gen_setfileinfo(instance, &parm[0]); parm[0].generic.in.file.fnum = gen_fnum(instance); GEN_COPY_PARM; GEN_SET_FNUM_SMB2(generic.in.file.handle); GEN_CALL_SMB2(smb2_setinfo_file(tree, &parm[i])); return true; } /* wipe any relevant files */ static void wipe_files(void) { int i; NTSTATUS status; if (options.skip_cleanup) { return; } for (i=0;i 0) { printf("Deleted %d files on server %d\n", n, i); } } } /* dump the current seeds - useful for continuing a backtrack */ static void dump_seeds(void) { int i; FILE *f; if (!options.seeds_file) { return; } f = fopen("seeds.tmp", "w"); if (!f) return; for (i=0;i 0 && base+chunk < options.numops && options.numops > 1; ) { int i, max; chunk = MIN(chunk, options.numops / 2); /* mark this range as disabled */ max = MIN(options.numops, base+chunk); for (i=base;i 0); printf("Reduced to %d ops\n", options.numops); ret = run_test(ev, lp_ctx); if (ret != options.numops - 1) { printf("Inconsistent result? ret=%d numops=%d\n", ret, options.numops); } } /* start the main gentest process */ static bool start_gentest(struct tevent_context *ev, struct loadparm_context *lp_ctx) { int op; int ret; /* allocate the open_handles array */ open_handles = calloc(options.max_open_handles, sizeof(open_handles[0])); if (open_handles == NULL) { printf("Unable to allocate memory for open_handles array.\n"); exit(1); } srandom(options.seed); op_parms = calloc(options.numops, sizeof(op_parms[0])); if (op_parms == NULL) { printf("Unable to allocate memory for op_parms.\n"); exit(1); } /* generate the seeds - after this everything is deterministic */ if (options.use_preset_seeds) { int numops; char **preset = file_lines_load(options.seeds_file, &numops, 0, NULL); if (!preset) { printf("Failed to load %s - %s\n", options.seeds_file, strerror(errno)); exit(1); } if (numops < options.numops) { options.numops = numops; } for (op=0;op "); lp_ctx = cmdline_lp_ctx; servers[0].credentials = cli_credentials_init(mem_ctx); servers[1].credentials = cli_credentials_init(mem_ctx); cli_credentials_guess(servers[0].credentials, lp_ctx); cli_credentials_guess(servers[1].credentials, lp_ctx); while((opt = poptGetNextOpt(pc)) != -1) { switch (opt) { case OPT_UNCLIST: lpcfg_set_cmdline(cmdline_lp_ctx, "torture:unclist", poptGetOptArg(pc)); break; case 'U': if (username_count == 2) { usage(pc); talloc_free(mem_ctx); exit(1); } cli_credentials_parse_string(servers[username_count].credentials, poptGetOptArg(pc), CRED_SPECIFIED); username_count++; break; } } if (ignore_file) { options.ignore_patterns = file_lines_load(ignore_file, NULL, 0, NULL); } argv_new = discard_const_p(char *, poptGetArgs(pc)); argc_new = argc; for (i=0; i= 3)) { usage(pc); talloc_free(mem_ctx); exit(1); } setlinebuf(stdout); setup_logging("gentest", DEBUG_STDOUT); if (argc < 3 || argv[1][0] == '-') { usage(pc); talloc_free(mem_ctx); exit(1); } setup_logging(argv[0], DEBUG_STDOUT); for (i=0;i