summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcvs2svn Import User <samba-bugs@samba.org>2001-07-04 07:36:10 +0000
committercvs2svn Import User <samba-bugs@samba.org>2001-07-04 07:36:10 +0000
commite3bae2fdff9c579a701736c529f19f3a5d4fd0a1 (patch)
tree4a4b100422e8dc815b999cd1ef3fe1cc49b125d2
parent733762fcd7a67061a1e4473d2f5f6d0ecb1611c0 (diff)
parentc41fc06376d1a2b83690612304e85010b5e5f3cf (diff)
downloadsamba-e3bae2fdff9c579a701736c529f19f3a5d4fd0a1.tar.gz
samba-e3bae2fdff9c579a701736c529f19f3a5d4fd0a1.tar.xz
samba-e3bae2fdff9c579a701736c529f19f3a5d4fd0a1.zip
This commit was manufactured by cvs2svn to create branch 'SAMBA_2_2'.
-rw-r--r--source/nsswitch/winbindd_misc.c155
-rw-r--r--source/passdb/machine_sid.c260
-rw-r--r--testsuite/build_farm/torture-ATTR.test2
-rw-r--r--testsuite/build_farm/torture-BROWSE.test2
-rw-r--r--testsuite/build_farm/torture-DELETE.test2
-rw-r--r--testsuite/build_farm/torture-DENY1.test2
-rw-r--r--testsuite/build_farm/torture-DENY2.test2
-rw-r--r--testsuite/build_farm/torture-DIR.test2
-rw-r--r--testsuite/build_farm/torture-FDPASS.test2
-rw-r--r--testsuite/build_farm/torture-LOCK1.test2
-rw-r--r--testsuite/build_farm/torture-LOCK2.test2
-rw-r--r--testsuite/build_farm/torture-LOCK3.test2
-rw-r--r--testsuite/build_farm/torture-LOCK4.test2
-rw-r--r--testsuite/build_farm/torture-LOCK5.test2
-rw-r--r--testsuite/build_farm/torture-OPEN.test2
-rw-r--r--testsuite/build_farm/torture-OPLOCK1.test2
-rw-r--r--testsuite/build_farm/torture-OPLOCK3.test2
-rw-r--r--testsuite/build_farm/torture-RANDOMIPC.test2
-rw-r--r--testsuite/build_farm/torture-RW1.test2
-rw-r--r--testsuite/build_farm/torture-RW2.test2
-rw-r--r--testsuite/build_farm/torture-TCON.test2
-rw-r--r--testsuite/build_farm/torture-TORTURE.test2
-rw-r--r--testsuite/build_farm/torture-TRANS2.test2
-rw-r--r--testsuite/build_farm/torture-UNLINK.test2
-rw-r--r--testsuite/build_farm/torture_setup.fns19
25 files changed, 478 insertions, 0 deletions
diff --git a/source/nsswitch/winbindd_misc.c b/source/nsswitch/winbindd_misc.c
new file mode 100644
index 00000000000..163837d1c3a
--- /dev/null
+++ b/source/nsswitch/winbindd_misc.c
@@ -0,0 +1,155 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 2.0
+
+ Winbind daemon - miscellaneous other functions
+
+ Copyright (C) Tim Potter 2000
+
+ 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 "winbindd.h"
+
+extern pstring global_myname;
+
+/* Some routines to fetch the trust account password from a HEAD
+ version of Samba. Yuck. )-: */
+
+/************************************************************************
+form a key for fetching a domain trust password from
+************************************************************************/
+static char *trust_keystr(char *domain)
+{
+ static fstring keystr;
+
+ snprintf(keystr,sizeof(keystr),"%s/%s", SECRETS_MACHINE_ACCT_PASS,
+ domain);
+
+ return keystr;
+}
+
+/************************************************************************
+ Routine to get the trust account password for a domain
+************************************************************************/
+BOOL _get_trust_account_password(char *domain, unsigned char *ret_pwd,
+ time_t *pass_last_set_time)
+{
+ struct machine_acct_pass *pass;
+ size_t size;
+
+ if (!(pass = secrets_fetch(trust_keystr(domain), &size)) ||
+ size != sizeof(*pass)) return False;
+
+ if (pass_last_set_time) *pass_last_set_time = pass->mod_time;
+ memcpy(ret_pwd, pass->hash, 16);
+ free(pass);
+ return True;
+}
+
+/* Check the machine account password is valid */
+
+enum winbindd_result winbindd_check_machine_acct(
+ struct winbindd_cli_state *state)
+{
+ int result = WINBINDD_ERROR;
+ uchar trust_passwd[16];
+ struct in_addr *ip_list = NULL;
+ int count;
+ uint16 validation_level;
+ fstring controller, trust_account;
+
+ DEBUG(3, ("[%5d]: check machine account\n", state->pid));
+
+ /* Get trust account password */
+
+ if (!_get_trust_account_password(lp_workgroup(), trust_passwd, NULL)) {
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* Get domain controller */
+
+ if (!get_dc_list(True, lp_workgroup(), &ip_list, &count) ||
+ !lookup_pdc_name(global_myname, lp_workgroup(), &ip_list[0],
+ controller)) {
+ DEBUG(0, ("could not find domain controller for "
+ "domain %s\n", lp_workgroup()));
+ result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ goto done;
+ }
+
+ DEBUG(3, ("contacting controller %s to check secret\n", controller));
+
+ /* Contact domain controller to check secret */
+
+ slprintf(trust_account, sizeof(trust_account) - 1, "%s$",
+ global_myname);
+
+#if 0 /* XXX */
+ result = cli_nt_setup_creds(controller, lp_workgroup(), global_myname,
+ trust_account, trust_passwd,
+ SEC_CHAN_WKSTA, &validation_level);
+#endif
+
+ /* Pass back result code - zero for success, other values for
+ specific failures. */
+
+ DEBUG(3, ("secret is %s\n", (result == NT_STATUS_NOPROBLEMO) ?
+ "good" : "bad"));
+
+ done:
+ state->response.data.num_entries = result;
+ return WINBINDD_OK;
+}
+
+enum winbindd_result winbindd_list_trusted_domains(struct winbindd_cli_state
+ *state)
+{
+ struct winbindd_domain *domain;
+ int total_entries = 0, extra_data_len = 0;
+ char *extra_data = NULL;
+
+ DEBUG(3, ("[%5d]: list trusted domains\n", state->pid));
+
+ for(domain = domain_list; domain; domain = domain->next) {
+
+ /* Skip own domain */
+
+ if (strequal(domain->name, lp_workgroup())) continue;
+
+ /* Add domain to list */
+
+ total_entries++;
+ extra_data = Realloc(extra_data, sizeof(fstring) *
+ total_entries);
+
+ if (!extra_data) return WINBINDD_ERROR;
+
+ memcpy(&extra_data[extra_data_len], domain->name,
+ strlen(domain->name));
+
+ extra_data_len += strlen(domain->name);
+ extra_data[extra_data_len++] = ',';
+ }
+
+ if (extra_data) {
+ if (extra_data_len > 1) extra_data[extra_data_len - 1] = '\0';
+ state->response.extra_data = extra_data;
+ state->response.length += extra_data_len;
+ }
+
+ return WINBINDD_OK;
+}
diff --git a/source/passdb/machine_sid.c b/source/passdb/machine_sid.c
new file mode 100644
index 00000000000..34b0c742087
--- /dev/null
+++ b/source/passdb/machine_sid.c
@@ -0,0 +1,260 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ Password and authentication handling
+ Copyright (C) Jeremy Allison 1996-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Gerald (Jerry) Carter 2000
+
+ 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"
+
+/****************************************************************************
+ Read the machine SID from a file.
+****************************************************************************/
+
+static BOOL read_sid_from_file(int fd, char *sid_file)
+{
+ fstring fline;
+
+ memset(fline, '\0', sizeof(fline));
+
+ if(read(fd, fline, sizeof(fline) -1 ) < 0) {
+ DEBUG(0,("unable to read file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ return False;
+ }
+
+ /*
+ * Convert to the machine SID.
+ */
+
+ fline[sizeof(fline)-1] = '\0';
+ if(!string_to_sid( &global_sam_sid, fline)) {
+ DEBUG(0,("unable to generate machine SID.\n"));
+ return False;
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Generate the global machine sid. Look for the MACHINE.SID file first, if
+ not found then look in smb.conf and use it to create the MACHINE.SID file.
+ Note this function will be replaced soon. JRA.
+****************************************************************************/
+
+BOOL pdb_generate_sam_sid(void)
+{
+ int fd;
+ char *p;
+ pstring sid_file;
+ fstring sid_string;
+ SMB_STRUCT_STAT st;
+ BOOL overwrite_bad_sid = False;
+
+ generate_wellknown_sids();
+
+ pstrcpy(sid_file, lp_smb_passwd_file());
+ p = strrchr_m(sid_file, '/');
+ if(p != NULL) {
+ *++p = '\0';
+ }
+
+ if (!directory_exist(sid_file, NULL)) {
+ if (mkdir(sid_file, 0700) != 0) {
+ DEBUG(0,("can't create private directory %s : %s\n",
+ sid_file, strerror(errno)));
+ return False;
+ }
+ }
+
+ pstrcat(sid_file, "MACHINE.SID");
+
+ if((fd = sys_open(sid_file, O_RDWR | O_CREAT, 0644)) == -1) {
+ DEBUG(0,("unable to open or create file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ return False;
+ }
+
+ /*
+ * Check if the file contains data.
+ */
+
+ if(sys_fstat( fd, &st) < 0) {
+ DEBUG(0,("unable to stat file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ close(fd);
+ return False;
+ }
+
+ if(st.st_size > 0) {
+ /*
+ * We have a valid SID - read it.
+ */
+ if(!read_sid_from_file( fd, sid_file)) {
+ DEBUG(0,("unable to read file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ close(fd);
+ return False;
+ }
+
+ /*
+ * JRA. Reversed the sense of this test now that I have
+ * actually done this test *personally*. One more reason
+ * to never trust third party information you have not
+ * independently verified.... sigh. JRA.
+ */
+
+ if(global_sam_sid.num_auths > 0 && global_sam_sid.sub_auths[0] == 0x21) {
+ /*
+ * Fix and re-write...
+ */
+ overwrite_bad_sid = True;
+ global_sam_sid.sub_auths[0] = 21;
+ DEBUG(5,("pdb_generate_sam_sid: Old (incorrect) sid id_auth of hex 21 \
+detected - re-writing to be decimal 21 instead.\n" ));
+ sid_to_string(sid_string, &global_sam_sid);
+ if(sys_lseek(fd, (SMB_OFF_T)0, SEEK_SET) != 0) {
+ DEBUG(0,("unable to seek file file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ close(fd);
+ return False;
+ }
+ } else {
+ close(fd);
+ return True;
+ }
+ } else {
+ /*
+ * The file contains no data - we need to generate our
+ * own sid.
+ * Generate the new sid data & turn it into a string.
+ */
+ int i;
+ uchar raw_sid_data[12];
+ DOM_SID mysid;
+
+ memset((char *)&mysid, '\0', sizeof(DOM_SID));
+ mysid.sid_rev_num = 1;
+ mysid.id_auth[5] = 5;
+ mysid.num_auths = 0;
+ mysid.sub_auths[mysid.num_auths++] = 21;
+
+ generate_random_buffer( raw_sid_data, 12, True);
+ for( i = 0; i < 3; i++)
+ mysid.sub_auths[mysid.num_auths++] = IVAL(raw_sid_data, i*4);
+
+ sid_to_string(sid_string, &mysid);
+ }
+
+ fstrcat(sid_string, "\n");
+
+ /*
+ * Ensure our new SID is valid.
+ */
+
+ if(!string_to_sid( &global_sam_sid, sid_string)) {
+ DEBUG(0,("unable to generate machine SID.\n"));
+ return False;
+ }
+
+ /*
+ * Do an exclusive blocking lock on the file.
+ */
+
+ if(!do_file_lock( fd, 60, F_WRLCK)) {
+ DEBUG(0,("unable to lock file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ close(fd);
+ return False;
+ }
+
+ if(!overwrite_bad_sid) {
+ /*
+ * At this point we have a blocking lock on the SID
+ * file - check if in the meantime someone else wrote
+ * SID data into the file. If so - they were here first,
+ * use their data.
+ */
+
+ if(sys_fstat( fd, &st) < 0) {
+ DEBUG(0,("unable to stat file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ close(fd);
+ return False;
+ }
+
+ if(st.st_size > 0) {
+ /*
+ * Unlock as soon as possible to reduce
+ * contention on the exclusive lock.
+ */
+ do_file_lock( fd, 60, F_UNLCK);
+
+ /*
+ * We have a valid SID - read it.
+ */
+
+ if(!read_sid_from_file( fd, sid_file)) {
+ DEBUG(0,("unable to read file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ close(fd);
+ return False;
+ }
+ close(fd);
+ return True;
+ }
+ }
+
+ /*
+ * The file is still empty and we have an exlusive lock on it,
+ * or we're fixing an earlier mistake.
+ * Write out out SID data into the file.
+ */
+
+ /*
+ * Use chmod here as some (strange) UNIX's don't
+ * have fchmod. JRA.
+ */
+
+ if(chmod(sid_file, 0644) < 0) {
+ DEBUG(0,("unable to set correct permissions on file %s. \
+Error was %s\n", sid_file, strerror(errno) ));
+ do_file_lock( fd, 60, F_UNLCK);
+ close(fd);
+ return False;
+ }
+
+ if(write( fd, sid_string, strlen(sid_string)) != strlen(sid_string)) {
+ DEBUG(0,("unable to write file %s. Error was %s\n",
+ sid_file, strerror(errno) ));
+ do_file_lock( fd, 60, F_UNLCK);
+ close(fd);
+ return False;
+ }
+
+ /*
+ * Unlock & exit.
+ */
+
+ do_file_lock( fd, 60, F_UNLCK);
+ close(fd);
+ return True;
+}
+
+
diff --git a/testsuite/build_farm/torture-ATTR.test b/testsuite/build_farm/torture-ATTR.test
new file mode 100644
index 00000000000..db6d5e87824
--- /dev/null
+++ b/testsuite/build_farm/torture-ATTR.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "ATTR"
diff --git a/testsuite/build_farm/torture-BROWSE.test b/testsuite/build_farm/torture-BROWSE.test
new file mode 100644
index 00000000000..da758977da5
--- /dev/null
+++ b/testsuite/build_farm/torture-BROWSE.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "BROWSE"
diff --git a/testsuite/build_farm/torture-DELETE.test b/testsuite/build_farm/torture-DELETE.test
new file mode 100644
index 00000000000..395f449d1e1
--- /dev/null
+++ b/testsuite/build_farm/torture-DELETE.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "DELETE"
diff --git a/testsuite/build_farm/torture-DENY1.test b/testsuite/build_farm/torture-DENY1.test
new file mode 100644
index 00000000000..99ce7ea8869
--- /dev/null
+++ b/testsuite/build_farm/torture-DENY1.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "DENY1"
diff --git a/testsuite/build_farm/torture-DENY2.test b/testsuite/build_farm/torture-DENY2.test
new file mode 100644
index 00000000000..17c8f707d87
--- /dev/null
+++ b/testsuite/build_farm/torture-DENY2.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "DENY2"
diff --git a/testsuite/build_farm/torture-DIR.test b/testsuite/build_farm/torture-DIR.test
new file mode 100644
index 00000000000..085ce59c3b0
--- /dev/null
+++ b/testsuite/build_farm/torture-DIR.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "DIR"
diff --git a/testsuite/build_farm/torture-FDPASS.test b/testsuite/build_farm/torture-FDPASS.test
new file mode 100644
index 00000000000..e8af277d430
--- /dev/null
+++ b/testsuite/build_farm/torture-FDPASS.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "FDPASS"
diff --git a/testsuite/build_farm/torture-LOCK1.test b/testsuite/build_farm/torture-LOCK1.test
new file mode 100644
index 00000000000..fd01c492f16
--- /dev/null
+++ b/testsuite/build_farm/torture-LOCK1.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "LOCK1"
diff --git a/testsuite/build_farm/torture-LOCK2.test b/testsuite/build_farm/torture-LOCK2.test
new file mode 100644
index 00000000000..66b671d8010
--- /dev/null
+++ b/testsuite/build_farm/torture-LOCK2.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "LOCK2"
diff --git a/testsuite/build_farm/torture-LOCK3.test b/testsuite/build_farm/torture-LOCK3.test
new file mode 100644
index 00000000000..dcf14019d8e
--- /dev/null
+++ b/testsuite/build_farm/torture-LOCK3.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "LOCK3"
diff --git a/testsuite/build_farm/torture-LOCK4.test b/testsuite/build_farm/torture-LOCK4.test
new file mode 100644
index 00000000000..8fdc9b66615
--- /dev/null
+++ b/testsuite/build_farm/torture-LOCK4.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "LOCK4"
diff --git a/testsuite/build_farm/torture-LOCK5.test b/testsuite/build_farm/torture-LOCK5.test
new file mode 100644
index 00000000000..a04f83c8491
--- /dev/null
+++ b/testsuite/build_farm/torture-LOCK5.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "LOCK5"
diff --git a/testsuite/build_farm/torture-OPEN.test b/testsuite/build_farm/torture-OPEN.test
new file mode 100644
index 00000000000..ee3e55f0890
--- /dev/null
+++ b/testsuite/build_farm/torture-OPEN.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "OPEN"
diff --git a/testsuite/build_farm/torture-OPLOCK1.test b/testsuite/build_farm/torture-OPLOCK1.test
new file mode 100644
index 00000000000..bb606ad3bc9
--- /dev/null
+++ b/testsuite/build_farm/torture-OPLOCK1.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "OPLOCK1"
diff --git a/testsuite/build_farm/torture-OPLOCK3.test b/testsuite/build_farm/torture-OPLOCK3.test
new file mode 100644
index 00000000000..f8dfb3f8e9b
--- /dev/null
+++ b/testsuite/build_farm/torture-OPLOCK3.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "OPLOCK3"
diff --git a/testsuite/build_farm/torture-RANDOMIPC.test b/testsuite/build_farm/torture-RANDOMIPC.test
new file mode 100644
index 00000000000..e510b6b6672
--- /dev/null
+++ b/testsuite/build_farm/torture-RANDOMIPC.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "RANDOMIPC"
diff --git a/testsuite/build_farm/torture-RW1.test b/testsuite/build_farm/torture-RW1.test
new file mode 100644
index 00000000000..6be4a897d91
--- /dev/null
+++ b/testsuite/build_farm/torture-RW1.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "RW1"
diff --git a/testsuite/build_farm/torture-RW2.test b/testsuite/build_farm/torture-RW2.test
new file mode 100644
index 00000000000..a647d9de2e9
--- /dev/null
+++ b/testsuite/build_farm/torture-RW2.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "RW22"
diff --git a/testsuite/build_farm/torture-TCON.test b/testsuite/build_farm/torture-TCON.test
new file mode 100644
index 00000000000..7d1aba0f29f
--- /dev/null
+++ b/testsuite/build_farm/torture-TCON.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "TCON"
diff --git a/testsuite/build_farm/torture-TORTURE.test b/testsuite/build_farm/torture-TORTURE.test
new file mode 100644
index 00000000000..bc97e94e850
--- /dev/null
+++ b/testsuite/build_farm/torture-TORTURE.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "TORTURE"
diff --git a/testsuite/build_farm/torture-TRANS2.test b/testsuite/build_farm/torture-TRANS2.test
new file mode 100644
index 00000000000..d2a387f1afc
--- /dev/null
+++ b/testsuite/build_farm/torture-TRANS2.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "TRANS2"
diff --git a/testsuite/build_farm/torture-UNLINK.test b/testsuite/build_farm/torture-UNLINK.test
new file mode 100644
index 00000000000..b7086bbc838
--- /dev/null
+++ b/testsuite/build_farm/torture-UNLINK.test
@@ -0,0 +1,2 @@
+. torture_setup.fns
+test_torture "UNLINK"
diff --git a/testsuite/build_farm/torture_setup.fns b/testsuite/build_farm/torture_setup.fns
new file mode 100644
index 00000000000..3d9abbcd79b
--- /dev/null
+++ b/testsuite/build_farm/torture_setup.fns
@@ -0,0 +1,19 @@
+. basicsmb.fns
+
+test_torture() {
+ torture_test=$1
+ password=samba
+ security=USER
+ (test_smb_conf_setup && test_smbpasswd $password ) || return 1
+
+ echo $pwd/$tree/source/bin/smbtorture //localhost/test -U$whoami%$password $torture_test
+ $pwd/$tree/source/bin/smbtorture //localhost/test -U$whoami%$password $torture_test
+ status=$?
+ if [ $status = 0 ]; then
+ echo "smbtorture test $torture_test worked"
+ else
+ echo "smbtorture test $torture_test FAILED!"
+ return 1
+ fi
+ return 0
+}