diff options
author | Andrew Tridgell <tridge@samba.org> | 2004-06-06 07:14:10 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 12:56:33 -0500 |
commit | a1318baa5503648ffcff2e9cd625b6848ad285b8 (patch) | |
tree | 0d3a24bfd6958b4ee0006add8c4334383472f71a /source4/torture | |
parent | 7ea6a0b1fc3e5f35e5096ad820053d54c4496a09 (diff) | |
download | samba-a1318baa5503648ffcff2e9cd625b6848ad285b8.tar.gz samba-a1318baa5503648ffcff2e9cd625b6848ad285b8.tar.bz2 samba-a1318baa5503648ffcff2e9cd625b6848ad285b8.zip |
r1041: - pulled the domain join code out of the netlogon test and made it a separate utility function, to allow
multiple torture tests to temporarily join a domain
- fixed a session key size problem
- added a schannel test suite
- allow schannel to work with ncacn_ip_tcp
(This used to be commit 36f05e4d575099fcb957b8a55781c38dcd2e1177)
Diffstat (limited to 'source4/torture')
-rw-r--r-- | source4/torture/config.mk | 2 | ||||
-rw-r--r-- | source4/torture/rpc/netlogon.c | 185 | ||||
-rw-r--r-- | source4/torture/rpc/schannel.c | 130 | ||||
-rw-r--r-- | source4/torture/rpc/spoolss.c | 8 | ||||
-rw-r--r-- | source4/torture/rpc/testjoin.c | 277 | ||||
-rw-r--r-- | source4/torture/torture.c | 3 |
6 files changed, 426 insertions, 179 deletions
diff --git a/source4/torture/config.mk b/source4/torture/config.mk index 4bbcf21b58..a4ec4b4076 100644 --- a/source4/torture/config.mk +++ b/source4/torture/config.mk @@ -61,6 +61,8 @@ ADD_OBJ_FILES = \ torture/rpc/mgmt.o \ torture/rpc/scanner.o \ torture/rpc/autoidl.o \ + torture/rpc/testjoin.o \ + torture/rpc/schannel.o \ torture/rpc/netlogon.o REQUIRED_SUBSYSTEMS = \ LIBSMB diff --git a/source4/torture/rpc/netlogon.c b/source4/torture/rpc/netlogon.c index 04741d8b90..68571a2f6c 100644 --- a/source4/torture/rpc/netlogon.c +++ b/source4/torture/rpc/netlogon.c @@ -25,172 +25,9 @@ #include "includes.h" -#define TEST_MACHINE_NAME "torturetest" - -static struct { - struct dcerpc_pipe *p; - const char *machine_password; - struct policy_handle acct_handle; -} join; - -/* - join the domain as a BDC -*/ -static BOOL join_domain_bdc(TALLOC_CTX *mem_ctx) -{ - NTSTATUS status; - struct samr_Connect c; - struct samr_CreateUser2 r; - struct samr_OpenDomain o; - struct samr_LookupDomain l; - struct samr_GetUserPwInfo pwp; - struct samr_SetUserInfo s; - union samr_UserInfo u; - struct policy_handle handle; - struct policy_handle domain_handle; - uint32_t access_granted; - uint32_t rid; - BOOL ret = True; - DATA_BLOB session_key; - struct samr_Name name; - int policy_min_pw_len = 0; - - printf("Connecting to SAMR\n"); - - status = torture_rpc_connection(&join.p, - DCERPC_SAMR_NAME, - DCERPC_SAMR_UUID, - DCERPC_SAMR_VERSION); - if (!NT_STATUS_IS_OK(status)) { - return False; - } - - c.in.system_name = NULL; - c.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; - c.out.handle = &handle; - - status = dcerpc_samr_Connect(join.p, mem_ctx, &c); - if (!NT_STATUS_IS_OK(status)) { - printf("samr_Connect failed - %s\n", nt_errstr(status)); - return False; - } - - printf("Opening domain %s\n", lp_workgroup()); - - name.name = lp_workgroup(); - l.in.handle = &handle; - l.in.domain = &name; - - status = dcerpc_samr_LookupDomain(join.p, mem_ctx, &l); - if (!NT_STATUS_IS_OK(status)) { - printf("LookupDomain failed - %s\n", nt_errstr(status)); - return False; - } - - o.in.handle = &handle; - o.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; - o.in.sid = l.out.sid; - o.out.domain_handle = &domain_handle; - - status = dcerpc_samr_OpenDomain(join.p, mem_ctx, &o); - if (!NT_STATUS_IS_OK(status)) { - printf("OpenDomain failed - %s\n", nt_errstr(status)); - return False; - } - - printf("Creating machine account %s\n", TEST_MACHINE_NAME); - -again: - name.name = talloc_asprintf(mem_ctx, "%s$", TEST_MACHINE_NAME); - r.in.handle = &domain_handle; - r.in.account_name = &name; - r.in.acct_flags = ACB_SVRTRUST; - r.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; - r.out.acct_handle = &join.acct_handle; - r.out.access_granted = &access_granted; - r.out.rid = &rid; - - status = dcerpc_samr_CreateUser2(join.p, mem_ctx, &r); - - if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS) && - test_DeleteUser_byname(join.p, mem_ctx, &domain_handle, name.name)) { - goto again; - } - - if (!NT_STATUS_IS_OK(status)) { - printf("CreateUser2 failed - %s\n", nt_errstr(status)); - return False; - } - - pwp.in.handle = &join.acct_handle; +static const char *machine_password; - status = dcerpc_samr_GetUserPwInfo(join.p, mem_ctx, &pwp); - if (NT_STATUS_IS_OK(status)) { - policy_min_pw_len = pwp.out.info.min_password_len; - } - - join.machine_password = generate_random_str(mem_ctx, MAX(8, policy_min_pw_len)); - - printf("Setting machine account password '%s'\n", join.machine_password); - - s.in.handle = &join.acct_handle; - s.in.info = &u; - s.in.level = 24; - - encode_pw_buffer(u.info24.password.data, join.machine_password, STR_UNICODE); - u.info24.pw_len = strlen(join.machine_password); - - status = dcerpc_fetch_session_key(join.p, &session_key); - if (!NT_STATUS_IS_OK(status)) { - printf("SetUserInfo level %u - no session key - %s\n", - s.in.level, nt_errstr(status)); - return False; - } - - arcfour_crypt_blob(u.info24.password.data, 516, &session_key); - - status = dcerpc_samr_SetUserInfo(join.p, mem_ctx, &s); - if (!NT_STATUS_IS_OK(status)) { - printf("SetUserInfo failed - %s\n", nt_errstr(status)); - return False; - } - - s.in.handle = &join.acct_handle; - s.in.info = &u; - s.in.level = 16; - - u.info16.acct_flags = ACB_SVRTRUST; - - printf("Resetting ACB flags\n"); - - status = dcerpc_samr_SetUserInfo(join.p, mem_ctx, &s); - if (!NT_STATUS_IS_OK(status)) { - printf("SetUserInfo failed - %s\n", nt_errstr(status)); - return False; - } - - return ret; -} - -/* - leave the domain as a BDC -*/ -static BOOL leave_domain_bdc(TALLOC_CTX *mem_ctx) -{ - struct samr_DeleteUser d; - NTSTATUS status; - - d.in.handle = &join.acct_handle; - d.out.handle = &join.acct_handle; - - status = dcerpc_samr_DeleteUser(join.p, mem_ctx, &d); - if (!NT_STATUS_IS_OK(status)) { - printf("Delete of machine account failed\n"); - return False; - } - - return True; -} +#define TEST_MACHINE_NAME "torturetest" static BOOL test_LogonUasLogon(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx) { @@ -259,7 +96,7 @@ static BOOL test_SetupCredentials(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, return False; } - plain_pass = join.machine_password; + plain_pass = machine_password; if (!plain_pass) { printf("Unable to fetch machine password!\n"); return False; @@ -319,7 +156,7 @@ static BOOL test_SetupCredentials2(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, return False; } - plain_pass = join.machine_password; + plain_pass = machine_password; if (!plain_pass) { printf("Unable to fetch machine password!\n"); return False; @@ -385,7 +222,7 @@ static BOOL test_SetupCredentials3(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, return False; } - plain_pass = join.machine_password; + plain_pass = machine_password; if (!plain_pass) { printf("Unable to fetch machine password!\n"); return False; @@ -1157,7 +994,7 @@ static BOOL test_SetPassword(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx) printf("Credential chaining failed\n"); } - join.machine_password = password; + machine_password = password; if (!test_SetupCredentials(p, mem_ctx, &creds)) { printf("ServerPasswordSet failed to actually change the password\n"); @@ -1679,10 +1516,13 @@ BOOL torture_rpc_netlogon(int dummy) struct dcerpc_pipe *p; TALLOC_CTX *mem_ctx; BOOL ret = True; + void *join_ctx; mem_ctx = talloc_init("torture_rpc_netlogon"); - if (!join_domain_bdc(mem_ctx)) { + join_ctx = torture_join_domain(TEST_MACHINE_NAME, lp_workgroup(), ACB_SVRTRUST, + &machine_password); + if (!join_ctx) { printf("Failed to join as BDC\n"); return False; } @@ -1757,10 +1597,7 @@ BOOL torture_rpc_netlogon(int dummy) torture_rpc_close(p); - if (!leave_domain_bdc(mem_ctx)) { - printf("Failed to delete BDC machine account\n"); - return False; - } + torture_leave_domain(join_ctx); return ret; } diff --git a/source4/torture/rpc/schannel.c b/source4/torture/rpc/schannel.c new file mode 100644 index 0000000000..8d30f16b06 --- /dev/null +++ b/source4/torture/rpc/schannel.c @@ -0,0 +1,130 @@ +/* + Unix SMB/CIFS implementation. + + test suite for schannel operations + + Copyright (C) Andrew Tridgell 2004 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +#define TEST_MACHINE_NAME "schanneltest" + +static BOOL test_samr_ops(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + struct samr_GetDomPwInfo r; + int i; + struct samr_Name name; + + name.name = lp_workgroup(); + r.in.name = &name; + + printf("Testing GetDomPwInfo with name %s\n", r.in.name->name); + + /* do several ops to test credential chaining */ + for (i=0;i<5;i++) { + status = dcerpc_samr_GetDomPwInfo(p, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + printf("GetDomPwInfo op %d failed - %s\n", i, nt_errstr(status)); + return False; + } + } + + return True; +} + + +static BOOL test_schannel(TALLOC_CTX *mem_ctx, + uint16 acct_flags, uint32 dcerpc_flags, + uint32 schannel_type) +{ + void *join_ctx; + const char *machine_password; + NTSTATUS status; + char *binding = lp_parm_string(-1, "torture", "binding"); + struct dcerpc_binding b; + struct dcerpc_pipe *p; + + join_ctx = torture_join_domain(TEST_MACHINE_NAME, lp_workgroup(), acct_flags, + &machine_password); + if (!join_ctx) { + printf("Failed to join domain with acct_flags=0x%x\n", acct_flags); + return False; + } + + status = dcerpc_parse_binding(mem_ctx, binding, &b); + if (!NT_STATUS_IS_OK(status)) { + printf("Bad binding string %s\n", binding); + goto failed; + } + + b.flags &= ~DCERPC_AUTH_OPTIONS; + b.flags |= dcerpc_flags; + + status = dcerpc_pipe_connect_b(&p, &b, + DCERPC_SAMR_UUID, + DCERPC_SAMR_VERSION, + lp_workgroup(), + TEST_MACHINE_NAME, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to connect with schannel\n"); + goto failed; + } + + test_samr_ops(p, mem_ctx); + + torture_leave_domain(join_ctx); + return True; + +failed: + torture_leave_domain(join_ctx); + return False; +} + + +BOOL torture_rpc_schannel(int dummy) +{ + TALLOC_CTX *mem_ctx; + BOOL ret = True; + struct { + uint16 acct_flags; + uint32 dcerpc_flags; + uint32 schannel_type; + } tests[] = { + { ACB_WSTRUST, DCERPC_SCHANNEL_WORKSTATION | DCERPC_SIGN, 3 }, + { ACB_WSTRUST, DCERPC_SCHANNEL_WORKSTATION | DCERPC_SEAL, 3 }, + { ACB_SVRTRUST, DCERPC_SCHANNEL_BDC | DCERPC_SIGN, 3 }, + { ACB_SVRTRUST, DCERPC_SCHANNEL_BDC | DCERPC_SEAL, 3 } + }; + int i; + + mem_ctx = talloc_init("torture_rpc_schannel"); + + for (i=0;i<ARRAY_SIZE(tests);i++) { + if (!test_schannel(mem_ctx, + tests[i].acct_flags, tests[i].dcerpc_flags, tests[i].schannel_type)) { + printf("Failed with acct_flags=0x%x dcerpc_flags=0x%x schannel_type=%d\n", + tests[i].acct_flags, tests[i].dcerpc_flags, tests[i].schannel_type); + ret = False; + break; + } + } + + return ret; +} diff --git a/source4/torture/rpc/spoolss.c b/source4/torture/rpc/spoolss.c index 74860cd369..cbeaac8d9e 100644 --- a/source4/torture/rpc/spoolss.c +++ b/source4/torture/rpc/spoolss.c @@ -587,10 +587,10 @@ static BOOL test_SecondaryClosePrinter(struct dcerpc_pipe *p, TALLOC_CTX *mem_ct printf("testing close on secondary pipe\n"); - status = dcerpc_secondary_smb(p, &p2, - DCERPC_SPOOLSS_NAME, - DCERPC_SPOOLSS_UUID, - DCERPC_SPOOLSS_VERSION); + status = dcerpc_secondary_connection(p, &p2, + DCERPC_SPOOLSS_NAME, + DCERPC_SPOOLSS_UUID, + DCERPC_SPOOLSS_VERSION); if (!NT_STATUS_IS_OK(status)) { printf("Failed to create secondary connection\n"); return False; diff --git a/source4/torture/rpc/testjoin.c b/source4/torture/rpc/testjoin.c new file mode 100644 index 0000000000..aab08c3a56 --- /dev/null +++ b/source4/torture/rpc/testjoin.c @@ -0,0 +1,277 @@ +/* + Unix SMB/CIFS implementation. + + utility code to join/leave a domain + + Copyright (C) Andrew Tridgell 2004 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + this code is used by other torture modules to join/leave a domain + as either a member, bdc or thru a trust relationship +*/ + +#include "includes.h" + +struct test_join { + TALLOC_CTX *mem_ctx; + struct dcerpc_pipe *p; + const char *machine_password; + struct policy_handle acct_handle; +}; + + +static NTSTATUS DeleteUser_byname(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, + struct policy_handle *handle, const char *name) +{ + NTSTATUS status; + struct samr_DeleteUser d; + struct policy_handle acct_handle; + uint32_t rid; + struct samr_LookupNames n; + struct samr_Name sname; + struct samr_OpenUser r; + + sname.name = name; + + n.in.handle = handle; + n.in.num_names = 1; + n.in.names = &sname; + + status = dcerpc_samr_LookupNames(p, mem_ctx, &n); + if (NT_STATUS_IS_OK(status)) { + rid = n.out.rids.ids[0]; + } else { + return status; + } + + r.in.handle = handle; + r.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.acct_handle = &acct_handle; + + status = dcerpc_samr_OpenUser(p, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + printf("OpenUser(%s) failed - %s\n", name, nt_errstr(status)); + return status; + } + + d.in.handle = &acct_handle; + d.out.handle = &acct_handle; + status = dcerpc_samr_DeleteUser(p, mem_ctx, &d); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/* + join the domain as a test machine + an opaque pointer is returned. Pass it to torture_leave_domain() + when finished +*/ +void *torture_join_domain(const char *machine_name, + const char *domain, + uint16 acct_flags, + const char **machine_password) +{ + NTSTATUS status; + struct samr_Connect c; + struct samr_CreateUser2 r; + struct samr_OpenDomain o; + struct samr_LookupDomain l; + struct samr_GetUserPwInfo pwp; + struct samr_SetUserInfo s; + union samr_UserInfo u; + struct policy_handle handle; + struct policy_handle domain_handle; + uint32_t access_granted; + uint32_t rid; + DATA_BLOB session_key; + struct samr_Name name; + int policy_min_pw_len = 0; + struct test_join *join; + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_init("torture_join_domain"); + if (!mem_ctx) { + return NULL; + } + + join = talloc_p(mem_ctx, struct test_join); + if (join == NULL) { + talloc_destroy(mem_ctx); + return NULL; + } + + ZERO_STRUCTP(join); + + join->mem_ctx = mem_ctx; + + printf("Connecting to SAMR\n"); + + status = torture_rpc_connection(&join->p, + DCERPC_SAMR_NAME, + DCERPC_SAMR_UUID, + DCERPC_SAMR_VERSION); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + c.in.system_name = NULL; + c.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; + c.out.handle = &handle; + + status = dcerpc_samr_Connect(join->p, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + printf("samr_Connect failed - %s\n", nt_errstr(status)); + goto failed; + } + + printf("Opening domain %s\n", domain); + + name.name = domain; + l.in.handle = &handle; + l.in.domain = &name; + + status = dcerpc_samr_LookupDomain(join->p, mem_ctx, &l); + if (!NT_STATUS_IS_OK(status)) { + printf("LookupDomain failed - %s\n", nt_errstr(status)); + goto failed; + } + + o.in.handle = &handle; + o.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; + o.in.sid = l.out.sid; + o.out.domain_handle = &domain_handle; + + status = dcerpc_samr_OpenDomain(join->p, mem_ctx, &o); + if (!NT_STATUS_IS_OK(status)) { + printf("OpenDomain failed - %s\n", nt_errstr(status)); + goto failed; + } + + printf("Creating machine account %s\n", machine_name); + +again: + name.name = talloc_asprintf(mem_ctx, "%s$", machine_name); + r.in.handle = &domain_handle; + r.in.account_name = &name; + r.in.acct_flags = acct_flags; + r.in.access_mask = SEC_RIGHTS_MAXIMUM_ALLOWED; + r.out.acct_handle = &join->acct_handle; + r.out.access_granted = &access_granted; + r.out.rid = &rid; + + status = dcerpc_samr_CreateUser2(join->p, mem_ctx, &r); + + if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) { + status = DeleteUser_byname(join->p, mem_ctx, &domain_handle, name.name); + if (NT_STATUS_IS_OK(status)) { + goto again; + } + } + + if (!NT_STATUS_IS_OK(status)) { + printf("CreateUser2 failed - %s\n", nt_errstr(status)); + goto failed; + } + + pwp.in.handle = &join->acct_handle; + + status = dcerpc_samr_GetUserPwInfo(join->p, mem_ctx, &pwp); + if (NT_STATUS_IS_OK(status)) { + policy_min_pw_len = pwp.out.info.min_password_len; + } + + join->machine_password = generate_random_str(mem_ctx, MAX(8, policy_min_pw_len)); + + printf("Setting machine account password '%s'\n", join->machine_password); + + s.in.handle = &join->acct_handle; + s.in.info = &u; + s.in.level = 24; + + encode_pw_buffer(u.info24.password.data, join->machine_password, STR_UNICODE); + u.info24.pw_len = strlen(join->machine_password); + + status = dcerpc_fetch_session_key(join->p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + printf("SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + torture_leave_domain(&join); + goto failed; + } + + arcfour_crypt_blob(u.info24.password.data, 516, &session_key); + + status = dcerpc_samr_SetUserInfo(join->p, mem_ctx, &s); + if (!NT_STATUS_IS_OK(status)) { + printf("SetUserInfo failed - %s\n", nt_errstr(status)); + goto failed; + } + + s.in.handle = &join->acct_handle; + s.in.info = &u; + s.in.level = 16; + + u.info16.acct_flags = acct_flags; + + printf("Resetting ACB flags\n"); + + status = dcerpc_samr_SetUserInfo(join->p, mem_ctx, &s); + if (!NT_STATUS_IS_OK(status)) { + printf("SetUserInfo failed - %s\n", nt_errstr(status)); + goto failed; + } + + *machine_password = join->machine_password; + + return join; + +failed: + torture_leave_domain(join); + return NULL; +} + + +/* + leave the domain, deleting the machine acct +*/ +void torture_leave_domain(void *join_ctx) +{ + struct test_join *join = join_ctx; + struct samr_DeleteUser d; + NTSTATUS status; + + if (!uuid_all_zero(&join->acct_handle.uuid)) { + d.in.handle = &join->acct_handle; + d.out.handle = &join->acct_handle; + + status = dcerpc_samr_DeleteUser(join->p, join->mem_ctx, &d); + if (!NT_STATUS_IS_OK(status)) { + printf("Delete of machine account failed\n"); + } + } + + if (join->p) { + torture_rpc_close(join->p); + } + + talloc_destroy(join->mem_ctx); +} diff --git a/source4/torture/torture.c b/source4/torture/torture.c index 2dcacf2388..4ff340bdd0 100644 --- a/source4/torture/torture.c +++ b/source4/torture/torture.c @@ -2795,7 +2795,7 @@ static BOOL run_vuidtest(int dummy) const char *fname = "\\vuid.tst"; int fnum; size_t size; - time_t c_time, a_time, m_time, w_time, m_time2; + time_t c_time, a_time, m_time; BOOL correct = True; uint16_t orig_vuid; @@ -4131,6 +4131,7 @@ static struct { {"RPC-SPOOLSS", torture_rpc_spoolss, 0}, {"RPC-SAMR", torture_rpc_samr, 0}, {"RPC-NETLOGON", torture_rpc_netlogon, 0}, + {"RPC-SCHANNEL", torture_rpc_schannel, 0}, {"RPC-WKSSVC", torture_rpc_wkssvc, 0}, {"RPC-SRVSVC", torture_rpc_srvsvc, 0}, {"RPC-ATSVC", torture_rpc_atsvc, 0}, |