1/*
2 * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "kadm5_locl.h"
35#include <sys/types.h>
36#ifdef HAVE_SYS_SOCKET_H
37#include <sys/socket.h>
38#endif
39#ifdef HAVE_NETINET_IN_H
40#include <netinet/in.h>
41#endif
42#ifdef HAVE_NETDB_H
43#include <netdb.h>
44#endif
45
46RCSID("$Id$");
47
48static kadm5_ret_t
49kadm5_c_lock(void *server_handle)
50{
51    return ENOTSUP;
52}
53
54static kadm5_ret_t
55kadm5_c_unlock(void *server_handle)
56{
57    return ENOTSUP;
58}
59
60static void
61set_funcs(kadm5_client_context *c)
62{
63#define SET(C, F) (C)->funcs.F = kadm5 ## _c_ ## F
64    SET(c, chpass_principal);
65    SET(c, chpass_principal_with_key);
66    SET(c, create_principal);
67    SET(c, delete_principal);
68    SET(c, destroy);
69    SET(c, flush);
70    SET(c, get_principal);
71    SET(c, get_principals);
72    SET(c, get_privs);
73    SET(c, modify_principal);
74    SET(c, randkey_principal);
75    SET(c, rename_principal);
76    SET(c, lock);
77    SET(c, unlock);
78}
79
80kadm5_ret_t
81_kadm5_c_init_context(kadm5_client_context **ctx,
82		      kadm5_config_params *params,
83		      krb5_context context)
84{
85    krb5_error_code ret;
86    char *colon;
87
88    *ctx = malloc(sizeof(**ctx));
89    if(*ctx == NULL)
90	return ENOMEM;
91    memset(*ctx, 0, sizeof(**ctx));
92    krb5_add_et_list (context, initialize_kadm5_error_table_r);
93    set_funcs(*ctx);
94    (*ctx)->context = context;
95    if(params->mask & KADM5_CONFIG_REALM) {
96	ret = 0;
97	(*ctx)->realm = strdup(params->realm);
98	if ((*ctx)->realm == NULL)
99	    ret = ENOMEM;
100    } else
101	ret = krb5_get_default_realm((*ctx)->context, &(*ctx)->realm);
102    if (ret) {
103	free(*ctx);
104	return ret;
105    }
106    if(params->mask & KADM5_CONFIG_ADMIN_SERVER)
107	(*ctx)->admin_server = strdup(params->admin_server);
108    else {
109	krb5_krbhst_handle handle = NULL;
110	char host[MAXHOSTNAMELEN];
111
112	ret = krb5_krbhst_init(context, (*ctx)->realm, KRB5_KRBHST_ADMIN, &handle);
113	if (ret == 0)
114	    ret = krb5_krbhst_next_as_string(context, handle, host, sizeof(host));
115	krb5_krbhst_free(context, handle);
116	if (ret) {
117	    free((*ctx)->realm);
118	    free(*ctx);
119	    return ret;
120	}
121
122	(*ctx)->admin_server = strdup(host);
123    }
124
125    if ((*ctx)->admin_server == NULL) {
126	free((*ctx)->realm);
127	free(*ctx);
128	return ENOMEM;
129    }
130    colon = strchr ((*ctx)->admin_server, ':');
131    if (colon != NULL)
132	*colon++ = '\0';
133
134    (*ctx)->kadmind_port = 0;
135
136    if(params->mask & KADM5_CONFIG_KADMIND_PORT)
137	(*ctx)->kadmind_port = params->kadmind_port;
138    else if (colon != NULL) {
139	char *end;
140
141	(*ctx)->kadmind_port = htons(strtol (colon, &end, 0));
142    }
143    if ((*ctx)->kadmind_port == 0)
144	(*ctx)->kadmind_port = krb5_getportbyname (context, "kerberos-adm",
145						   "tcp", 749);
146    return 0;
147}
148
149static krb5_error_code
150get_kadm_ticket(krb5_context context,
151		krb5_ccache id,
152		krb5_principal client,
153		const char *server_name)
154{
155    krb5_error_code ret;
156    krb5_creds in, *out;
157
158    memset(&in, 0, sizeof(in));
159    in.client = client;
160    ret = krb5_parse_name(context, server_name, &in.server);
161    if(ret)
162	return ret;
163    ret = krb5_get_credentials(context, 0, id, &in, &out);
164    if(ret == 0)
165	krb5_free_creds(context, out);
166    krb5_free_principal(context, in.server);
167    return ret;
168}
169
170static krb5_error_code
171get_new_cache(krb5_context context,
172	      krb5_principal client,
173	      const char *password,
174	      krb5_prompter_fct prompter,
175	      const char *keytab,
176	      const char *server_name,
177	      krb5_ccache *ret_cache)
178{
179    krb5_error_code ret;
180    krb5_creds cred;
181    krb5_get_init_creds_opt *opt;
182    krb5_ccache id;
183
184    ret = krb5_get_init_creds_opt_alloc (context, &opt);
185    if (ret)
186	return ret;
187
188    krb5_get_init_creds_opt_set_default_flags(context, "kadmin",
189					      krb5_principal_get_realm(context,
190								       client),
191					      opt);
192
193
194    krb5_get_init_creds_opt_set_forwardable (opt, FALSE);
195    krb5_get_init_creds_opt_set_proxiable (opt, FALSE);
196
197    if(password == NULL && prompter == NULL) {
198	krb5_keytab kt;
199	if(keytab == NULL)
200	    ret = krb5_kt_default(context, &kt);
201	else
202	    ret = krb5_kt_resolve(context, keytab, &kt);
203	if(ret) {
204	    krb5_get_init_creds_opt_free(context, opt);
205	    return ret;
206	}
207	ret = krb5_get_init_creds_keytab (context,
208					  &cred,
209					  client,
210					  kt,
211					  0,
212					  server_name,
213					  opt);
214	krb5_kt_close(context, kt);
215    } else {
216	ret = krb5_get_init_creds_password (context,
217					    &cred,
218					    client,
219					    password,
220					    prompter,
221					    NULL,
222					    0,
223					    server_name,
224					    opt);
225    }
226    krb5_get_init_creds_opt_free(context, opt);
227    switch(ret){
228    case 0:
229	break;
230    case KRB5_LIBOS_PWDINTR:	/* don't print anything if it was just C-c:ed */
231    case KRB5KRB_AP_ERR_BAD_INTEGRITY:
232    case KRB5KRB_AP_ERR_MODIFIED:
233	return KADM5_BAD_PASSWORD;
234    default:
235	return ret;
236    }
237    ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &id);
238    if(ret)
239	return ret;
240    ret = krb5_cc_initialize (context, id, cred.client);
241    if (ret)
242	return ret;
243    ret = krb5_cc_store_cred (context, id, &cred);
244    if (ret)
245	return ret;
246    krb5_free_cred_contents (context, &cred);
247    *ret_cache = id;
248    return 0;
249}
250
251/*
252 * Check the credential cache `id´ to figure out what principal to use
253 * when talking to the kadmind. If there is a initial kadmin/admin@
254 * credential in the cache, use that client principal. Otherwise, use
255 * the client principals first component and add /admin to the
256 * principal.
257 */
258
259krb5_error_code
260_kadm5_c_get_cache_principal(krb5_context context,
261			     krb5_ccache *id,
262			     krb5_principal *client)
263{
264    krb5_error_code ret;
265    const char *name, *inst;
266    krb5_principal p1, p2;
267
268    ret = krb5_cc_default(context, id);
269    if(ret) {
270	*id = NULL;
271	return ret;
272    }
273
274    ret = krb5_cc_get_principal(context, *id, &p1);
275    if(ret) {
276	krb5_cc_close(context, *id);
277	*id = NULL;
278	return ret;
279    }
280
281    ret = krb5_make_principal(context, &p2, NULL,
282			      "kadmin", "admin", NULL);
283    if (ret) {
284	krb5_cc_close(context, *id);
285	*id = NULL;
286	krb5_free_principal(context, p1);
287	return ret;
288    }
289
290    {
291	krb5_creds in, *out;
292	krb5_kdc_flags flags;
293
294	flags.i = 0;
295	memset(&in, 0, sizeof(in));
296
297	in.client = p1;
298	in.server = p2;
299
300	/* check for initial ticket kadmin/admin */
301	ret = krb5_get_credentials_with_flags(context, KRB5_GC_CACHED, flags,
302					      *id, &in, &out);
303	krb5_free_principal(context, p2);
304	if (ret == 0) {
305	    if (out->flags.b.initial) {
306		*client = p1;
307		krb5_free_creds(context, out);
308		return 0;
309	    }
310	    krb5_free_creds(context, out);
311	}
312    }
313    krb5_cc_close(context, *id);
314    *id = NULL;
315
316    name = krb5_principal_get_comp_string(context, p1, 0);
317    inst = krb5_principal_get_comp_string(context, p1, 1);
318    if(inst == NULL || strcmp(inst, "admin") != 0) {
319	ret = krb5_make_principal(context, &p2, NULL, name, "admin", NULL);
320	krb5_free_principal(context, p1);
321	if(ret != 0)
322	    return ret;
323
324	*client = p2;
325	return 0;
326    }
327
328    *client = p1;
329
330    return 0;
331}
332
333krb5_error_code
334_kadm5_c_get_cred_cache(krb5_context context,
335			const char *client_name,
336			const char *server_name,
337			const char *password,
338			krb5_prompter_fct prompter,
339			const char *keytab,
340			krb5_ccache ccache,
341			krb5_ccache *ret_cache)
342{
343    krb5_error_code ret;
344    krb5_ccache id = NULL;
345    krb5_principal default_client = NULL, client = NULL;
346
347    /* treat empty password as NULL */
348    if(password && *password == '\0')
349	password = NULL;
350    if(server_name == NULL)
351	server_name = KADM5_ADMIN_SERVICE;
352
353    if(client_name != NULL) {
354	ret = krb5_parse_name(context, client_name, &client);
355	if(ret)
356	    return ret;
357    }
358
359    if(ccache != NULL) {
360	id = ccache;
361	ret = krb5_cc_get_principal(context, id, &client);
362	if(ret)
363	    return ret;
364    } else {
365	/* get principal from default cache, ok if this doesn't work */
366
367	ret = _kadm5_c_get_cache_principal(context, &id, &default_client);
368	if (ret) {
369	    /*
370	     * No client was specified by the caller and we cannot
371	     * determine the client from a credentials cache.
372	     */
373	    const char *user;
374
375	    user = get_default_username ();
376
377	    if(user == NULL) {
378		krb5_set_error_message(context, KADM5_FAILURE, "Unable to find local user name");
379		return KADM5_FAILURE;
380	    }
381	    ret = krb5_make_principal(context, &default_client,
382				      NULL, user, "admin", NULL);
383	    if(ret)
384		return ret;
385	}
386    }
387
388
389    /*
390     * No client was specified by the caller, but we have a client
391     * from the default credentials cache.
392     */
393    if (client == NULL && default_client != NULL)
394	client = default_client;
395
396
397    if(id && client && (default_client == NULL ||
398	      krb5_principal_compare(context, client, default_client) != 0)) {
399	ret = get_kadm_ticket(context, id, client, server_name);
400	if(ret == 0) {
401	    *ret_cache = id;
402	    krb5_free_principal(context, default_client);
403	    if (default_client != client)
404		krb5_free_principal(context, client);
405	    return 0;
406	}
407	if(ccache != NULL)
408	    /* couldn't get ticket from cache */
409	    return -1;
410    }
411    /* get creds via AS request */
412    if(id && (id != ccache))
413	krb5_cc_close(context, id);
414    if (client != default_client)
415	krb5_free_principal(context, default_client);
416
417    ret = get_new_cache(context, client, password, prompter, keytab,
418			server_name, ret_cache);
419    krb5_free_principal(context, client);
420    return ret;
421}
422
423static kadm5_ret_t
424kadm_connect(kadm5_client_context *ctx)
425{
426    kadm5_ret_t ret;
427    krb5_principal server;
428    krb5_ccache cc;
429    rk_socket_t s = rk_INVALID_SOCKET;
430    struct addrinfo *ai, *a;
431    struct addrinfo hints;
432    int error;
433    char portstr[NI_MAXSERV];
434    char *hostname, *slash;
435    char *service_name;
436    krb5_context context = ctx->context;
437
438    memset (&hints, 0, sizeof(hints));
439    hints.ai_socktype = SOCK_STREAM;
440    hints.ai_protocol = IPPROTO_TCP;
441
442    snprintf (portstr, sizeof(portstr), "%u", ntohs(ctx->kadmind_port));
443
444    hostname = ctx->admin_server;
445    slash = strchr (hostname, '/');
446    if (slash != NULL)
447	hostname = slash + 1;
448
449    error = getaddrinfo (hostname, portstr, &hints, &ai);
450    if (error) {
451	krb5_clear_error_message(context);
452	return KADM5_BAD_SERVER_NAME;
453    }
454
455    for (a = ai; a != NULL; a = a->ai_next) {
456	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
457	if (s < 0)
458	    continue;
459	socket_set_nopipe(s, 1);
460	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
461	    krb5_clear_error_message(context);
462	    krb5_warn (context, errno, "connect(%s)", hostname);
463	    rk_closesocket (s);
464	    continue;
465	}
466	break;
467    }
468    if (a == NULL) {
469	freeaddrinfo (ai);
470	krb5_clear_error_message(context);
471	krb5_warnx (context, "failed to contact %s", hostname);
472	return KADM5_FAILURE;
473    }
474    ret = _kadm5_c_get_cred_cache(context,
475				  ctx->client_name,
476				  ctx->service_name,
477				  NULL, ctx->prompter, ctx->keytab,
478				  ctx->ccache, &cc);
479
480    if(ret) {
481	freeaddrinfo (ai);
482	rk_closesocket(s);
483	return ret;
484    }
485
486    if (ctx->realm)
487	asprintf(&service_name, "%s@%s", KADM5_ADMIN_SERVICE, ctx->realm);
488    else
489	asprintf(&service_name, "%s", KADM5_ADMIN_SERVICE);
490
491    if (service_name == NULL) {
492	freeaddrinfo (ai);
493	rk_closesocket(s);
494	krb5_clear_error_message(context);
495	return ENOMEM;
496    }
497
498    ret = krb5_parse_name(context, service_name, &server);
499    free(service_name);
500    if(ret) {
501	freeaddrinfo (ai);
502	if(ctx->ccache == NULL)
503	    krb5_cc_close(context, cc);
504	rk_closesocket(s);
505	return ret;
506    }
507    ctx->ac = NULL;
508
509    ret = krb5_sendauth(context, &ctx->ac, &s,
510			KADMIN_APPL_VERSION, NULL,
511			server, AP_OPTS_MUTUAL_REQUIRED,
512			NULL, NULL, cc, NULL, NULL, NULL);
513    if(ret == 0) {
514	krb5_data params;
515	kadm5_config_params p;
516	memset(&p, 0, sizeof(p));
517	if(ctx->realm) {
518	    p.mask |= KADM5_CONFIG_REALM;
519	    p.realm = ctx->realm;
520	}
521	(void)_kadm5_marshal_params(context, &p, &params);
522
523	ret = krb5_write_priv_message(context, ctx->ac, &s, &params);
524	krb5_data_free(&params);
525	if(ret) {
526	    freeaddrinfo (ai);
527	    rk_closesocket(s);
528	    if(ctx->ccache == NULL)
529		krb5_cc_close(context, cc);
530	    return ret;
531	}
532    } else if(ret == KRB5_SENDAUTH_BADAPPLVERS) {
533	rk_closesocket(s);
534
535	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
536	if (s < 0) {
537	    freeaddrinfo (ai);
538	    krb5_clear_error_message(context);
539	    return errno;
540	}
541	socket_set_nopipe(s, 1);
542	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
543	    rk_closesocket (s);
544	    freeaddrinfo (ai);
545	    krb5_clear_error_message(context);
546	    return errno;
547	}
548	ret = krb5_sendauth(context, &ctx->ac, &s,
549			    KADMIN_OLD_APPL_VERSION, NULL,
550			    server, AP_OPTS_MUTUAL_REQUIRED,
551			    NULL, NULL, cc, NULL, NULL, NULL);
552    }
553    freeaddrinfo (ai);
554    if(ret) {
555	rk_closesocket(s);
556	return ret;
557    }
558
559    krb5_free_principal(context, server);
560    if(ctx->ccache == NULL)
561	krb5_cc_close(context, cc);
562    ctx->sock = s;
563
564    return 0;
565}
566
567kadm5_ret_t
568_kadm5_connect(void *handle)
569{
570    kadm5_client_context *ctx = handle;
571    if(ctx->sock == -1)
572	return kadm_connect(ctx);
573    return 0;
574}
575
576static kadm5_ret_t
577kadm5_c_init_with_context(krb5_context context,
578			  const char *client_name,
579			  const char *password,
580			  krb5_prompter_fct prompter,
581			  const char *keytab,
582			  krb5_ccache ccache,
583			  const char *service_name,
584			  kadm5_config_params *realm_params,
585			  unsigned long struct_version,
586			  unsigned long api_version,
587			  void **server_handle)
588{
589    kadm5_ret_t ret;
590    kadm5_client_context *ctx;
591    krb5_ccache cc;
592
593    ret = _kadm5_c_init_context(&ctx, realm_params, context);
594    if(ret)
595	return ret;
596
597    if(password != NULL && *password != '\0') {
598	ret = _kadm5_c_get_cred_cache(context,
599				      client_name,
600				      service_name,
601				      password, prompter, keytab, ccache, &cc);
602	if(ret)
603	    return ret; /* XXX */
604	ccache = cc;
605    }
606
607
608    if (client_name != NULL)
609	ctx->client_name = strdup(client_name);
610    else
611	ctx->client_name = NULL;
612    if (service_name != NULL)
613	ctx->service_name = strdup(service_name);
614    else
615	ctx->service_name = NULL;
616    ctx->prompter = prompter;
617    ctx->keytab = keytab;
618    ctx->ccache = ccache;
619    /* maybe we should copy the params here */
620    ctx->sock = -1;
621
622    *server_handle = ctx;
623    return 0;
624}
625
626static kadm5_ret_t
627init_context(const char *client_name,
628	     const char *password,
629	     krb5_prompter_fct prompter,
630	     const char *keytab,
631	     krb5_ccache ccache,
632	     const char *service_name,
633	     kadm5_config_params *realm_params,
634	     unsigned long struct_version,
635	     unsigned long api_version,
636	     void **server_handle)
637{
638    krb5_context context;
639    kadm5_ret_t ret;
640    kadm5_server_context *ctx;
641
642    ret = krb5_init_context(&context);
643    if (ret)
644	return ret;
645    ret = kadm5_c_init_with_context(context,
646				    client_name,
647				    password,
648				    prompter,
649				    keytab,
650				    ccache,
651				    service_name,
652				    realm_params,
653				    struct_version,
654				    api_version,
655				    server_handle);
656    if(ret){
657	krb5_free_context(context);
658	return ret;
659    }
660    ctx = *server_handle;
661    ctx->my_context = 1;
662    return 0;
663}
664
665kadm5_ret_t
666kadm5_c_init_with_password_ctx(krb5_context context,
667			       const char *client_name,
668			       const char *password,
669			       const char *service_name,
670			       kadm5_config_params *realm_params,
671			       unsigned long struct_version,
672			       unsigned long api_version,
673			       void **server_handle)
674{
675    return kadm5_c_init_with_context(context,
676				     client_name,
677				     password,
678				     krb5_prompter_posix,
679				     NULL,
680				     NULL,
681				     service_name,
682				     realm_params,
683				     struct_version,
684				     api_version,
685				     server_handle);
686}
687
688kadm5_ret_t
689kadm5_c_init_with_password(const char *client_name,
690			   const char *password,
691			   const char *service_name,
692			   kadm5_config_params *realm_params,
693			   unsigned long struct_version,
694			   unsigned long api_version,
695			   void **server_handle)
696{
697    return init_context(client_name,
698			password,
699			krb5_prompter_posix,
700			NULL,
701			NULL,
702			service_name,
703			realm_params,
704			struct_version,
705			api_version,
706			server_handle);
707}
708
709kadm5_ret_t
710kadm5_c_init_with_skey_ctx(krb5_context context,
711			   const char *client_name,
712			   const char *keytab,
713			   const char *service_name,
714			   kadm5_config_params *realm_params,
715			   unsigned long struct_version,
716			   unsigned long api_version,
717			   void **server_handle)
718{
719    return kadm5_c_init_with_context(context,
720				     client_name,
721				     NULL,
722				     NULL,
723				     keytab,
724				     NULL,
725				     service_name,
726				     realm_params,
727				     struct_version,
728				     api_version,
729				     server_handle);
730}
731
732
733kadm5_ret_t
734kadm5_c_init_with_skey(const char *client_name,
735		     const char *keytab,
736		     const char *service_name,
737		     kadm5_config_params *realm_params,
738		     unsigned long struct_version,
739		     unsigned long api_version,
740		     void **server_handle)
741{
742    return init_context(client_name,
743			NULL,
744			NULL,
745			keytab,
746			NULL,
747			service_name,
748			realm_params,
749			struct_version,
750			api_version,
751			server_handle);
752}
753
754kadm5_ret_t
755kadm5_c_init_with_creds_ctx(krb5_context context,
756			    const char *client_name,
757			    krb5_ccache ccache,
758			    const char *service_name,
759			    kadm5_config_params *realm_params,
760			    unsigned long struct_version,
761			    unsigned long api_version,
762			    void **server_handle)
763{
764    return kadm5_c_init_with_context(context,
765				     client_name,
766				     NULL,
767				     NULL,
768				     NULL,
769				     ccache,
770				     service_name,
771				     realm_params,
772				     struct_version,
773				     api_version,
774				     server_handle);
775}
776
777kadm5_ret_t
778kadm5_c_init_with_creds(const char *client_name,
779			krb5_ccache ccache,
780			const char *service_name,
781			kadm5_config_params *realm_params,
782			unsigned long struct_version,
783			unsigned long api_version,
784			void **server_handle)
785{
786    return init_context(client_name,
787			NULL,
788			NULL,
789			NULL,
790			ccache,
791			service_name,
792			realm_params,
793			struct_version,
794			api_version,
795			server_handle);
796}
797
798#if 0
799kadm5_ret_t
800kadm5_init(char *client_name, char *pass,
801	   char *service_name,
802	   kadm5_config_params *realm_params,
803	   unsigned long struct_version,
804	   unsigned long api_version,
805	   void **server_handle)
806{
807}
808#endif
809
810