{"requires_hwauth", 15, KRB5_KDB_REQUIRES_HW_AUTH, 0},
{"needchange", 10, KRB5_KDB_REQUIRES_PWCHANGE, 0},
{"allow_svr", 9, KRB5_KDB_DISALLOW_SVR, 1},
-{"password_changing_service", 25, KRB5_KDB_PWCHANGE_SERVICE, 0 }
+{"password_changing_service", 25, KRB5_KDB_PWCHANGE_SERVICE, 0 },
+{"support_desmd5", 14, KRB5_KDB_SUPPORT_DESMD5, 0 }
};
static char *prflags[] = {
"UNKNOWN_0x00000400", /* 0x00000400 */
"UNKNOWN_0x00000800", /* 0x00000800 */
"DISALLOW_SVR", /* 0x00001000 */
- "PWCHANGE_SERVICE" /* 0x00002000 */
+ "PWCHANGE_SERVICE", /* 0x00002000 */
+ "SUPPORT_DESMD5", /* 0x00004000 */
+ "NEW_PRINC", /* 0x00008000 */
};
char *getenv();
+Mon Mar 31 17:42:03 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * kadm_rpc_svc.c, server_stubs.c: add support for setkey_principal
+
Tue Feb 4 20:59:31 1997 Tom Yu <tlyu@mit.edu>
* Makefile.in:
* $Source$
*
* $Log$
+ * Revision 1.13 1997/04/23 19:53:11 bjaspan
+ * add kadm5_setkey_principal
+ *
* Revision 1.12 1996/07/22 20:28:53 marc
* this commit includes all the changes on the OV_9510_INTEGRATION and
* OV_MERGE branches. This includes, but is not limited to, the new openvision
xdr_result = xdr_generic_ret;
local = (char *(*)()) chpass_principal_1;
break;
+
+ case SETKEY_PRINCIPAL:
+ xdr_argument = xdr_setkey_arg;
+ xdr_result = xdr_generic_ret;
+ local = (char *(*)()) setkey_principal_1;
+ break;
case CHRAND_PRINCIPAL:
xdr_argument = xdr_chrand_arg;
break;
default:
- krb5_klog_syslog(LOG_ERR, "Invalid OVSEC_KADM procedure number: %s, %d",
+ krb5_klog_syslog(LOG_ERR, "Invalid KADM5 procedure number: %s, %d",
inet_ntoa(rqstp->rq_xprt->xp_raddr.sin_addr),
rqstp->rq_proc);
svcerr_noproc(transp);
return &ret;
}
+generic_ret *
+setkey_principal_1(setkey_arg *arg, struct svc_req *rqstp)
+{
+ static generic_ret ret;
+ char *prime_arg;
+ gss_buffer_desc client_name,
+ service_name;
+ OM_uint32 minor_stat;
+ kadm5_server_handle_t handle;
+
+ xdr_free(xdr_generic_ret, &ret);
+
+ if (ret.code = new_server_handle(arg->api_version, rqstp, &handle))
+ return &ret;
+
+ if (ret.code = check_handle((void *)handle)) {
+ free_server_handle(handle);
+ return &ret;
+ }
+
+ ret.api_version = handle->api_version;
+
+ if (setup_gss_names(rqstp, &client_name, &service_name) < 0) {
+ ret.code = KADM5_FAILURE;
+ return &ret;
+ }
+ krb5_unparse_name(handle->context, arg->princ, &prime_arg);
+
+ if (!(CHANGEPW_SERVICE(rqstp)) &&
+ acl_check(handle->context, rqstp->rq_clntcred,
+ ACL_SETKEY, arg->princ)) {
+ ret.code = kadm5_setkey_principal((void *)handle, arg->princ,
+ arg->keyblocks, arg->n_keys);
+ } else {
+ krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, "kadm5_setkey_principal",
+ prime_arg, client_name.value, service_name.value,
+ inet_ntoa(rqstp->rq_xprt->xp_raddr.sin_addr));
+ ret.code = KADM5_AUTH_SETKEY;
+ }
+
+ if(ret.code != KADM5_AUTH_SETKEY) {
+ krb5_klog_syslog(LOG_NOTICE, LOG_DONE, "kadm5_setkey_principal",
+ prime_arg, ((ret.code == 0) ? "success" :
+ error_message(ret.code)),
+ client_name.value, service_name.value,
+ inet_ntoa(rqstp->rq_xprt->xp_raddr.sin_addr));
+ }
+
+ free_server_handle(handle);
+ free(prime_arg);
+ gss_release_buffer(&minor_stat, &client_name);
+ gss_release_buffer(&minor_stat, &service_name);
+ return &ret;
+}
+
chrand_ret *
chrand_principal_1(chrand_arg *arg, struct svc_req *rqstp)
{
+Mon Mar 31 17:42:33 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * krb5.conf.proto: add support for setkey_principal (support more enctypes)
+
Tue Oct 15 16:24:33 1996 Barry Jaspan <bjaspan@mit.edu>
* krb5.conf.proto: s/localhost/__LOCALHOST__/
dict_file = __K5ROOT__/ovsec_adm.dict
kadmind_port = 1751
master_key_type = des-cbc-crc
- supported_enctypes = des-cbc-crc:normal des-cbc-crc:v4
+ supported_enctypes = des-cbc-crc:normal des-cbc-crc:v4 des-cbc-md5:normal des-cbc-raw:normal
}
+Mon Mar 31 17:43:06 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * Makefile.in: be more verbose if Tcl is unavailable
+
Wed Feb 5 22:57:53 1997 Tom Yu <tlyu@mit.edu>
* Makefile.in:
all:: all-$(DO_ALL)
all-::
- @echo "Tcl is not installed on this system. The kadm5 test suite cannot be run."
+ @echo "+++"
+ @echo "+++ WARNING: Tcl not available. The kadm5 tests will not be run."
+ @echo "+++"
all-tcl:: $(CLNTPROG) $(SRVPROG)
+Mon Mar 31 17:41:11 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * kadm_err.et, kadm_rpc.h, kadm_rpc_xdr.c: add support for
+ setkey_principal
+
Thu Jan 16 19:01:00 1997 Tom Yu <tlyu@mit.edu>
* Makefile.in (all-prerecurse): Update to use double-colon rules.
+Mon Mar 31 17:40:48 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * client_principal.c, client_rpc.c: add support for
+ setkey_principal
+
Sat Feb 22 01:35:19 1997 Sam Hartman <hartmans@tertius.mit.edu>
* Makefile.in (SHLIB_EXPDEPS): s/.so/$(SHLIBEXT)
return r->code;
}
+kadm5_ret_t
+kadm5_setkey_principal(void *server_handle,
+ krb5_principal princ,
+ krb5_keyblock *keyblocks,
+ int n_keys)
+{
+ setkey_arg arg;
+ generic_ret *r;
+ kadm5_server_handle_t handle = server_handle;
+
+ CHECK_HANDLE(server_handle);
+
+ arg.princ = princ;
+ arg.keyblocks = keyblocks;
+ arg.n_keys = n_keys;
+ arg.api_version = handle->api_version;
+
+ if(princ == NULL || keyblocks == NULL)
+ return EINVAL;
+ r = setkey_principal_1(&arg, handle->clnt);
+ if(r == NULL)
+ return KADM5_RPC_ERROR;
+ return r->code;
+}
+
kadm5_ret_t
kadm5_randkey_principal(void *server_handle,
krb5_principal princ,
return (&res);
}
+generic_ret *
+setkey_principal_1(argp, clnt)
+ setkey_arg *argp;
+ CLIENT *clnt;
+{
+ static generic_ret res;
+
+ memset((char *)&res, 0, sizeof(res));
+ if (clnt_call(clnt, SETKEY_PRINCIPAL, xdr_setkey_arg, argp, xdr_generic_ret, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
chrand_ret *
chrand_principal_1(argp, clnt)
chrand_arg *argp;
error_code KADM5_BAD_TL_TYPE, "Programmer error! Illegal tagged data list type"
error_code KADM5_MISSING_CONF_PARAMS, "Required parameters in kdc.conf missing"
error_code KADM5_BAD_SERVER_NAME, "Bad krb5 admin server hostname"
+error_code KADM5_AUTH_SETKEY, "Operation requires ``set-key'' privilege"
+error_code KADM5_SETKEY_DUP_ENCTYPES, "Multiple values for single or folded enctype"
end
typedef struct chpass_arg chpass_arg;
bool_t xdr_chpass_arg();
+struct setkey_arg {
+ krb5_ui_4 api_version;
+ krb5_principal princ;
+ krb5_keyblock *keyblocks;
+ int n_keys;
+};
+typedef struct setkey_arg setkey_arg;
+bool_t xdr_setkey_arg();
+
struct chrand_arg {
krb5_ui_4 api_version;
krb5_principal princ;
extern gprincs_ret *get_princs_1();
#define GET_POLS ((krb5_ui_4) 15)
extern gpols_ret *get_pols_1();
+#define SETKEY_PRINCIPAL ((krb5_ui_4) 16)
+extern generic_ret *setkey_principal_1();
return (TRUE);
}
+bool_t
+xdr_setkey_arg(XDR *xdrs, setkey_arg *objp)
+{
+ if (!xdr_ui_4(xdrs, &objp->api_version)) {
+ return (FALSE);
+ }
+ if (!xdr_krb5_principal(xdrs, &objp->princ)) {
+ return (FALSE);
+ }
+ if (!xdr_array(xdrs, (caddr_t *) &objp->keyblocks,
+ (unsigned int *) &objp->n_keys, ~0,
+ sizeof(krb5_keyblock), xdr_krb5_keyblock)) {
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
bool_t
xdr_chrand_arg(XDR *xdrs, chrand_arg *objp)
{
+Mon Mar 31 17:40:24 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * server_acl.c, server_acl.h, svr_pricipal.c: add support for
+ setkey_principal
+
Sun Mar 9 13:40:33 1997 Tom Yu <tlyu@mit.edu>
* svr_principal.c (add_to_history): Don't call realloc() on a NULL
{ 'c', ACL_CHANGEPW },
{ 'i', ACL_INQUIRE },
{ 'l', ACL_LIST },
+ { 's', ACL_SETKEY },
{ 'x', ACL_ALL_MASK },
{ '*', ACL_ALL_MASK },
{ '\0', 0 }
#define ACL_INQUIRE 32
/* #define ACL_EXTRACT 64 */
#define ACL_LIST 128
+#define ACL_SETKEY 256
#define ACL_RENAME (ACL_ADD+ACL_DELETE)
#define ACL_ALL_MASK (ACL_ADD | \
return n;
}
+/* This is in lib/kdb/kdb_cpw.c, but is static */
+static void cleanup_key_data(context, count, data)
+ krb5_context context;
+ int count;
+ krb5_key_data * data;
+{
+ int i, j;
+
+ for (i = 0; i < count; i++)
+ for (j = 0; j < data[i].key_data_ver; j++)
+ if (data[i].key_data_length[j])
+ free(data[i].key_data_contents[j]);
+ free(data);
+}
+
kadm5_ret_t
kadm5_create_principal(void *server_handle,
kadm5_principal_ent_t entry, long mask,
return ret;
}
+kadm5_ret_t
+kadm5_setkey_principal(void *server_handle,
+ krb5_principal principal,
+ krb5_keyblock *keyblocks,
+ int n_keys)
+{
+ krb5_db_entry kdb;
+ osa_princ_ent_rec adb;
+ krb5_int32 now;
+ kadm5_policy_ent_rec pol;
+ krb5_key_data *key_data;
+ int i, kvno, ret, last_pwd, have_pol = 0;
+ int deskeys;
+ kadm5_server_handle_t handle = server_handle;
+
+ CHECK_HANDLE(server_handle);
+
+ if (principal == NULL || keyblocks == NULL)
+ return EINVAL;
+ if (hist_princ && /* this will be NULL when initializing the databse */
+ ((krb5_principal_compare(handle->context,
+ principal, hist_princ)) == TRUE))
+ return KADM5_PROTECT_PRINCIPAL;
+
+ for (i = 0, deskeys = 0; i < n_keys; i++) {
+ if (keyblocks[i].enctype == ENCTYPE_DES_CBC_MD4 ||
+ keyblocks[i].enctype == ENCTYPE_DES_CBC_MD5 ||
+ keyblocks[i].enctype == ENCTYPE_DES_CBC_RAW ||
+ keyblocks[i].enctype == ENCTYPE_DES_CBC_CRC)
+ deskeys++;
+ if (deskeys > 1)
+ return KADM5_SETKEY_DUP_ENCTYPES;
+ }
+
+ if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+ return(ret);
+
+ for (kvno = 0, i=0; i<kdb.n_key_data; i++)
+ if (kdb.key_data[i].key_data_kvno > kvno)
+ kvno = kdb.key_data[i].key_data_kvno;
+
+ if (kdb.key_data != NULL)
+ cleanup_key_data(handle->context, kdb.n_key_data, kdb.key_data);
+
+ kdb.key_data = (krb5_key_data*)malloc(n_keys*sizeof(krb5_key_data));
+ if (kdb.key_data == NULL)
+ return ENOMEM;
+ memset(kdb.key_data, 0, n_keys*sizeof(krb5_key_data));
+ kdb.n_key_data = n_keys;
+
+ for (i = 0; i < n_keys; i++) {
+ if (ret = krb5_dbekd_encrypt_key_data(handle->context,
+ &master_encblock,
+ &keyblocks[i], NULL,
+ kvno + 1,
+ &kdb.key_data[i]))
+ return ret;
+ }
+
+ kdb.attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
+
+ if (ret = krb5_timeofday(handle->context, &now))
+ goto done;
+
+ if ((adb.aux_attributes & KADM5_POLICY)) {
+ if ((ret = kadm5_get_policy(handle->lhandle, adb.policy,
+ &pol)) != KADM5_OK)
+ goto done;
+ have_pol = 1;
+
+#if 0
+ /*
+ * The spec says this check is overridden if the caller has
+ * modify privilege. The admin server therefore makes this
+ * check itself (in chpass_principal_wrapper, misc.c). A
+ * local caller implicitly has all authorization bits.
+ */
+ if (ret = krb5_dbe_lookup_last_pwd_change(handle->context,
+ &kdb, &last_pwd))
+ goto done;
+ if((now - last_pwd) < pol.pw_min_life &&
+ !(kdb.attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
+ ret = KADM5_PASS_TOOSOON;
+ goto done;
+ }
+#endif
+#if 0
+ /*
+ * Should we be checking/updating pw history here?
+ */
+ if(pol.pw_history_num > 1) {
+ if(adb.admin_history_kvno != hist_kvno) {
+ ret = KADM5_BAD_HIST_KEY;
+ goto done;
+ }
+
+ if (ret = check_pw_reuse(handle->context,
+ &hist_encblock,
+ kdb.n_key_data, kdb.key_data,
+ adb.old_key_len, adb.old_keys))
+ goto done;
+ }
+#endif
+
+ if (pol.pw_max_life)
+ kdb.pw_expiration = now + pol.pw_max_life;
+ else
+ kdb.pw_expiration = 0;
+ } else {
+ kdb.pw_expiration = 0;
+ }
+
+ if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now))
+ goto done;
+
+ if ((ret = kdb_put_entry(handle, &kdb, &adb)))
+ goto done;
+
+ ret = KADM5_OK;
+done:
+ kdb_free_entry(handle, &kdb, &adb);
+ if (have_pol)
+ kadm5_free_policy_ent(handle->lhandle, &pol);
+
+ return ret;
+}
+
/*
* Allocate an array of n_key_data krb5_keyblocks, fill in each
* element with the results of decrypting the nth key in key_data with
+Mon Mar 31 17:39:52 1997 Barry Jaspan <bjaspan@mit.edu>
+
+ * Makefile.in, setkey-test.c: add support for setkey
+
Wed Mar 12 15:49:46 1997 Barry Jaspan <bjaspan@mit.edu>
* Makefile.in (unit-test-server-body): depend on test-randkey, not
all:: init-test destroy-test client-handle-test client-iter-test
all:: randkey-test server-handle-test lock-test server-iter-test
+all:: server-setkey-test client-setkey-test
#
# The client-side test programs.
$(CC_LINK) -o client-iter-test iter-test.o \
$(KADMCLNT_LIBS) $(KRB5_BASE_LIBS)
+client-setkey-test: setkey-test.o $(KADMCLNT_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o client-setkey-test setkey-test.o \
+ $(KADMCLNT_LIBS) $(KRB5_BASE_LIBS)
+
#
# The server-side test programs.
#
$(CC_LINK) -o server-iter-test iter-test.o \
$(KADMSRV_LIBS) $(KRB5_BASE_LIBS)
+setkey-test.o: $(SRCTOP)/lib/kadm5/unit-test/setkey-test.c
+ $(CC) $(CFLAGS) -UUSE_KADM5_API_VERSION -DUSE_KADM5_API_VERSION=2 -c $(SRCTOP)/lib/kadm5/unit-test/setkey-test.c
+
+server-setkey-test: setkey-test.o $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o server-setkey-test setkey-test.o \
+ $(KADMSRV_LIBS) $(KRB5_BASE_LIBS)
+
#
# The unit-test targets
#
--- /dev/null
+#include <stdio.h>
+#include <krb5.h>
+#include <kadm5/admin.h>
+
+krb5_keyblock test1[] = {
+ 0, ENCTYPE_DES_CBC_CRC, 0, 0,
+ -1,
+};
+krb5_keyblock test2[] = {
+ 0, ENCTYPE_DES_CBC_RAW, 0, 0,
+ -1,
+};
+krb5_keyblock test3[] = {
+ 0, ENCTYPE_DES_CBC_MD5, 0, 0,
+ -1,
+};
+
+krb5_keyblock *tests[] = {
+ test1, test2, test3, NULL
+};
+
+int keyblocks_equal(krb5_keyblock *kb1, krb5_keyblock *kb2)
+{
+ return (kb1->enctype == kb2->enctype &&
+ kb1->length == kb2->length &&
+ memcmp(kb1->contents, kb2->contents, kb1->length) == 0);
+}
+
+krb5_data tgtname = {
+ 0,
+ KRB5_TGS_NAME_SIZE,
+ KRB5_TGS_NAME
+};
+
+unsigned int ktypes[] = { 0, 0 };
+
+extern krb5_kt_ops krb5_ktf_writable_ops;
+
+main(int argc, char **argv)
+{
+ krb5_context context;
+ krb5_keytab kt;
+ krb5_keytab_entry ktent;
+ krb5_encrypt_block eblock;
+ krb5_creds my_creds;
+ kadm5_principal_ent_rec princ_ent;
+ krb5_principal princ, server;
+ char pw[16];
+ char *whoami, *principal, *authprinc;
+ krb5_data pwdata;
+ void *handle;
+ int ret, i, test, encnum;
+
+ whoami = argv[0];
+
+ if (argc != 2 && argc != 3) {
+ fprintf(stderr, "Usage: %s principal [authuser]\n", whoami);
+ exit(1);
+ }
+ principal = argv[1];
+ authprinc = argv[2] ? argv[2] : argv[0];
+
+ /*
+ * Setup. Initialize data structures, open keytab, open connection
+ * to kadm5 server.
+ */
+
+ memset((char *) &context, 0, sizeof(context));
+ krb5_init_context(&context);
+
+ ret = krb5_parse_name(context, principal, &princ);
+ if (ret) {
+ com_err(whoami, ret, "while parsing principal name %s", principal);
+ exit(1);
+ }
+
+ if((ret = krb5_build_principal_ext(context, &server,
+ krb5_princ_realm(kcontext, princ)->length,
+ krb5_princ_realm(kcontext, princ)->data,
+ tgtname.length, tgtname.data,
+ krb5_princ_realm(kcontext, princ)->length,
+ krb5_princ_realm(kcontext, princ)->data,
+ 0))) {
+ com_err(whoami, ret, "while building server name");
+ exit(1);
+ }
+
+ /* register the WRFILE keytab type */
+ if (ret = krb5_kt_register(context, &krb5_ktf_writable_ops)) {
+ com_err(whoami, ret,
+ "while registering writable key table functions");
+ exit(1);
+ }
+
+ ret = krb5_kt_default(context, &kt);
+ if (ret) {
+ com_err(whoami, ret, "while opening keytab");
+ exit(1);
+ }
+
+ ret = kadm5_init(authprinc, NULL, KADM5_ADMIN_SERVICE, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_2,
+ &handle);
+ if (ret) {
+ com_err(whoami, ret, "while initializing connection");
+ exit(1);
+ }
+
+ /* these pw's don't need to be secure, just different every time */
+ srandom(getpid() ^ time(0));
+ pwdata.data = pw;
+ pwdata.length = sizeof(pw);
+
+ /*
+ * For each test:
+ *
+ * For each enctype in the test, construct a random password/key.
+ * Assign all keys to principal with kadm5_setkey_principal. Add
+ * each key to the keytab, and acquire an initial ticket with the
+ * keytab (XXX can I specify the enctype & kvno explicitly?). If
+ * krb5_get_in_tkt_with_keytab succeeds, then the keys were set
+ * successfully.
+ */
+ for (test = 0; tests[test] != NULL; test++) {
+ krb5_keyblock *testp = tests[test];
+ printf("+ Test %d:\n", test);
+
+ for (encnum = 0; testp[encnum].magic != -1; encnum++) {
+ for (i = 0; i < sizeof(pw); i++)
+ pw[i] = (random() % 26) + '0'; /* XXX */
+
+ krb5_use_enctype(context, &eblock, testp[encnum].enctype);
+ if (ret = krb5_string_to_key(context, &eblock, &testp[encnum],
+ &pwdata, NULL)) {
+ com_err(whoami, ret, "while converting string to key");
+ exit(1);
+ }
+ }
+
+ /* now, encnum == # of keyblocks in testp */
+ ret = kadm5_setkey_principal(handle, princ, testp, encnum);
+ if (ret) {
+ com_err(whoami, ret, "while setting keys");
+ exit(1);
+ }
+
+ ret = kadm5_get_principal(handle, princ, &princ_ent, KADM5_KVNO);
+ if (ret) {
+ com_err(whoami, ret, "while retrieving principal");
+ exit(1);
+ }
+
+ for (encnum = 0; testp[encnum].magic != -1; encnum++) {
+ printf("+ enctype %d\n", testp[encnum].enctype);
+
+ memset((char *) &ktent, 0, sizeof(ktent));
+ ktent.principal = princ;
+ ktent.key = testp[encnum];
+ ktent.vno = princ_ent.kvno;
+
+ ret = krb5_kt_add_entry(context, kt, &ktent);
+ if (ret) {
+ com_err(whoami, ret, "while adding keytab entry");
+ exit(1);
+ }
+
+ memset((char *)&my_creds, 0, sizeof(my_creds));
+ my_creds.client = princ;
+ my_creds.server = server;
+
+ ktypes[0] = testp[encnum].enctype;
+ ret = krb5_get_in_tkt_with_keytab(context,
+ 0 /* options */,
+ NULL /* addrs */,
+ ktypes,
+ NULL /* preauth */,
+ kt, 0,
+ &my_creds, 0);
+ if (ret) {
+ com_err(whoami, ret, "while acquiring initial ticket");
+ exit(1);
+ }
+
+ /* since I can't specify enctype explicitly ... */
+ ret = krb5_kt_remove_entry(context, kt, &ktent);
+ if (ret) {
+ com_err(whoami, ret, "while removing keytab entry");
+ exit(1);
+ }
+ }
+ }
+
+ ret = krb5_kt_close(context, kt);
+ if (ret) {
+ com_err(whoami, ret, "while closing keytab");
+ exit(1);
+ }
+
+ ret = kadm5_destroy(handle);
+ if (ret) {
+ com_err(whoami, ret, "while closing kadmin connection");
+ exit(1);
+ }
+
+ return 0;
+}
+
+
+
+
+
+