1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
27 */
28
29 /*
30 * SMB specific functions
31 */
32 #include <stdio.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <zone.h>
38 #include <errno.h>
39 #include <locale.h>
40 #include <signal.h>
41 #include <fcntl.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <syslog.h>
45 #include "libshare.h"
46 #include "libshare_impl.h"
47 #include <pwd.h>
48 #include <limits.h>
49 #include <libscf.h>
50 #include <strings.h>
51 #include "libshare_smbfs.h"
52 #include <rpcsvc/daemon_utils.h>
53 #include <arpa/inet.h>
54 #include <uuid/uuid.h>
55 #include <netsmb/smb_lib.h>
56
57 #define SMBFS_PROTOCOL_NAME "smbfs"
58
59 /* internal functions */
60 static uint64_t smbfs_features();
61 static int smbfs_init();
62 static void smbfs_fini();
63 static int smbfs_set_proto_prop(sa_property_t);
64 static sa_protocol_properties_t smbfs_get_proto_set();
65 static char *smbfs_get_status();
66 static int smbfs_delete_section(char *);
67 static int smbfs_delete_property_group(char *);
68
69 static int range_check_validator(int, char *, char *);
70 static int string_length_check_validator(int, char *, char *);
71 static int yes_no_validator(int, char *, char *);
72 static int ip_address_validator(int, char *, char *);
73 static int minauth_validator(int, char *, char *);
74 static int password_validator(int, char *, char *);
75 static int protocol_validator(int, char *, char *);
76 static int signing_validator(int, char *, char *);
77
78 int propset_changed = 0;
79
80 /*
81 * ops vector that provides the protocol specific info and operations
82 * for share management.
83 */
84
85 struct sa_plugin_ops sa_plugin_ops = {
86 SA_PLUGIN_VERSION,
87 SMBFS_PROTOCOL_NAME,
88 smbfs_init,
89 smbfs_fini,
90 NULL, /* share */
91 NULL, /* unshare */
92 NULL, /* valid_prop */
93 NULL, /* valid_space */
94 NULL, /* security_prop */
95 NULL, /* legacy_opts */
96 NULL, /* legacy_format */
97 smbfs_set_proto_prop,
98 smbfs_get_proto_set,
99 smbfs_get_status,
100 NULL, /* space_alias */
101 NULL, /* update_legacy */
102 NULL, /* delete_legacy */
103 NULL, /* change_notify */
104 NULL, /* enable_resource */
105 NULL, /* disable_resource */
106 smbfs_features,
107 NULL, /* get_transient_shares */
108 NULL, /* notify_resource */
109 NULL, /* rename_resource */
110 NULL, /* run_command */
111 NULL, /* command_help */
112 smbfs_delete_section,
113 };
114
115 /*
116 * is_a_number(number)
117 *
118 * is the string a number in one of the forms we want to use?
119 */
120
121 static int
is_a_number(char * number)122 is_a_number(char *number)
123 {
124 int ret = 1;
125 int hex = 0;
126
127 if (strncmp(number, "0x", 2) == 0) {
128 number += 2;
129 hex = 1;
130 } else if (*number == '-') {
131 number++; /* skip the minus */
132 }
133
134 while (ret == 1 && *number != '\0') {
135 if (hex) {
136 ret = isxdigit(*number++);
137 } else {
138 ret = isdigit(*number++);
139 }
140 }
141 return (ret);
142 }
143
144 /*
145 * Protocol management functions
146 *
147 * properties defined in the default files are defined in
148 * proto_option_defs for parsing and validation.
149 */
150
151 struct smbclnt_proto_option_defs smbclnt_proto_options[] = {
152 { "section", NULL, PROTO_OPT_SECTION,
153 0, 0, MAX_VALUE_BUFLEN,
154 string_length_check_validator},
155 { "addr", NULL, PROTO_OPT_ADDR,
156 0, 0, MAX_VALUE_BUFLEN,
157 ip_address_validator},
158 { "minauth", NULL, PROTO_OPT_MINAUTH,
159 0, 0, MAX_VALUE_BUFLEN,
160 minauth_validator},
161 { "nbns_broadcast", NULL, PROTO_OPT_NBNS_BROADCAST,
162 0, 0, 0,
163 yes_no_validator},
164 { "nbns_enable", NULL, PROTO_OPT_NBNS_ENABLE,
165 0, 0, 0,
166 yes_no_validator},
167 { "nbns", NULL, PROTO_OPT_NBNSADDR,
168 0, 0, MAX_VALUE_BUFLEN,
169 ip_address_validator},
170 { "password", NULL, PROTO_OPT_PASSWORD,
171 0, 0, MAX_VALUE_BUFLEN,
172 password_validator},
173 { "timeout", NULL, PROTO_OPT_TIMEOUT,
174 0, 0, 60,
175 range_check_validator},
176 { "user", NULL, PROTO_OPT_USER,
177 0, 0, MAX_VALUE_BUFLEN,
178 string_length_check_validator},
179 { "domain", NULL, PROTO_OPT_DOMAIN,
180 0, 0, MAX_VALUE_BUFLEN,
181 string_length_check_validator},
182 { "workgroup", NULL, PROTO_OPT_WORKGROUP,
183 0, 0, MAX_VALUE_BUFLEN,
184 string_length_check_validator},
185 { "signing", NULL, PROTO_OPT_SIGNING,
186 0, 0, MAX_VALUE_BUFLEN,
187 signing_validator},
188 { "min_protocol", NULL, PROTO_OPT_MIN_PROTOCOL,
189 0, 0, MAX_VALUE_BUFLEN,
190 protocol_validator},
191 { "max_protocol", NULL, PROTO_OPT_MAX_PROTOCOL,
192 0, 0, MAX_VALUE_BUFLEN,
193 protocol_validator},
194 {NULL}
195 };
196
197 /*
198 * Check the range of value as int range.
199 */
200 /*ARGSUSED*/
201 static int
range_check_validator(int index,char * section,char * value)202 range_check_validator(int index, char *section, char *value)
203 {
204 int ret = SA_OK;
205
206 if (value == NULL)
207 return (SA_BAD_VALUE);
208 if (strlen(value) == 0)
209 return (SA_OK);
210 if (!is_a_number(value)) {
211 ret = SA_BAD_VALUE;
212 } else {
213 int val;
214 val = strtoul(value, NULL, 0);
215 if (val < smbclnt_proto_options[index].minval ||
216 val > smbclnt_proto_options[index].maxval)
217 ret = SA_BAD_VALUE;
218 }
219 return (ret);
220 }
221
222 /*
223 * Check the length of the string
224 */
225 /*ARGSUSED*/
226 static int
string_length_check_validator(int index,char * section,char * value)227 string_length_check_validator(int index, char *section, char *value)
228 {
229 int ret = SA_OK;
230
231 if (value == NULL)
232 return (SA_BAD_VALUE);
233 if (strlen(value) == 0)
234 return (SA_OK);
235 if (strlen(value) > smbclnt_proto_options[index].maxval)
236 ret = SA_BAD_VALUE;
237 return (ret);
238 }
239
240 /*
241 * Check yes/no
242 */
243 /*ARGSUSED*/
244 static int
yes_no_validator(int index,char * section,char * value)245 yes_no_validator(int index, char *section, char *value)
246 {
247 if (value == NULL)
248 return (SA_BAD_VALUE);
249 if (strlen(value) == 0)
250 return (SA_OK);
251 if ((strcasecmp(value, "yes") == 0) ||
252 (strcasecmp(value, "no") == 0) ||
253 (strcasecmp(value, "true") == 0) ||
254 (strcasecmp(value, "false") == 0))
255 return (SA_OK);
256 return (SA_BAD_VALUE);
257 }
258
259 /*
260 * Check IP address.
261 */
262 /*ARGSUSED*/
263 static int
ip_address_validator(int index,char * section,char * value)264 ip_address_validator(int index, char *section, char *value)
265 {
266 int len;
267
268 if (value == NULL)
269 return (SA_BAD_VALUE);
270 len = strlen(value);
271 if (len == 0)
272 return (SA_OK);
273 if (len > MAX_VALUE_BUFLEN)
274 return (SA_BAD_VALUE);
275 return (SA_OK);
276 }
277
278 /*ARGSUSED*/
279 static int
minauth_validator(int index,char * section,char * value)280 minauth_validator(int index, char *section, char *value)
281 {
282 int ival;
283
284 if (value == NULL)
285 return (SA_BAD_VALUE);
286 ival = smb_cf_minauth_from_str(value);
287 if (ival == -1)
288 return (SA_BAD_VALUE);
289
290 return (SA_OK);
291 }
292
293 /*ARGSUSED*/
294 static int
protocol_validator(int index,char * section,char * value)295 protocol_validator(int index, char *section, char *value)
296 {
297 int ival;
298
299 if (value == NULL)
300 return (SA_BAD_VALUE);
301 ival = smb_cf_version_from_str(value);
302 if (ival == -1)
303 return (SA_BAD_VALUE);
304
305 return (SA_OK);
306 }
307
308 /*ARGSUSED*/
309 static int
signing_validator(int index,char * section,char * value)310 signing_validator(int index, char *section, char *value)
311 {
312 if (value == NULL)
313 return (SA_BAD_VALUE);
314 if (strlen(value) == 0)
315 return (SA_OK);
316 if (strcmp(value, "disabled") == 0 ||
317 strcmp(value, "enabled") == 0 ||
318 strcmp(value, "required") == 0)
319 return (SA_OK);
320 else
321 return (SA_BAD_VALUE);
322 }
323
324 /*ARGSUSED*/
325 static int
password_validator(int index,char * section,char * value)326 password_validator(int index, char *section, char *value)
327 {
328 char buffer[100];
329
330 /* mangled passwords will start with this pattern */
331 if (strlen(value) == 0)
332 return (SA_OK);
333 if (strncmp(value, "$$1", 3) != 0)
334 return (SA_PASSWORD_ENC);
335 if (smb_simpledecrypt(buffer, value) != 0)
336 return (SA_BAD_VALUE);
337 return (SA_OK);
338 }
339
340
341 /*
342 * the protoset holds the defined options so we don't have to read
343 * them multiple times
344 */
345 sa_protocol_properties_t protoset;
346
347 static int
findprotoopt(char * name)348 findprotoopt(char *name)
349 {
350 int i;
351 for (i = 0; smbclnt_proto_options[i].name != NULL; i++) {
352 if (strcasecmp(smbclnt_proto_options[i].name, name) == 0)
353 return (i);
354 }
355 return (-1);
356 }
357
358 /*
359 * Load the persistent settings from SMF. Each section is an SMF
360 * property group with an "S-" prefix and a UUID, and the section
361 * is itself a property which can have a more flexible name than
362 * a property group name can have. The section name need not be
363 * the first property, so we have to be a little flexible, but
364 * the change of name of the property groups is a reliable way
365 * to know that we're seeing a different section.
366 */
367 int
smbclnt_config_load()368 smbclnt_config_load()
369 {
370 scf_simple_app_props_t *props = NULL;
371 scf_simple_prop_t *prop = NULL, *lastprop = NULL;
372 char *lastpgname = NULL, *pgname = NULL;
373 char *name = NULL, *value = NULL;
374 sa_property_t sect, node;
375
376 props = scf_simple_app_props_get(NULL, SMBC_DEFAULT_INSTANCE_FMRI);
377 if (props == NULL)
378 return (-1);
379
380 for (;;) {
381 lastprop = prop;
382 prop = (scf_simple_prop_t *)
383 scf_simple_app_props_next(props, lastprop);
384 if (prop == NULL)
385 break;
386
387 /* Ignore properties that don't have our prefix */
388 pgname = scf_simple_prop_pgname(prop);
389 if (strncmp("S-", pgname, 2) != 0)
390 continue;
391
392 /*
393 * Note property group name changes, which mark sections
394 *
395 * The memory allocated by sa_create_section is
396 * linked into the list of children under protoset,
397 * and will eventually be freed via that list.
398 */
399 if (lastpgname == NULL || strcmp(lastpgname, pgname) != 0) {
400 sect = sa_create_section(NULL, pgname+2);
401 (void) xmlSetProp(sect, (xmlChar *)"type",
402 (xmlChar *)SMBFS_PROTOCOL_NAME);
403 (void) sa_add_protocol_property(protoset, sect);
404 if (lastpgname)
405 free(lastpgname);
406 lastpgname = strdup(pgname);
407 }
408 name = scf_simple_prop_name(prop);
409 value = scf_simple_prop_next_astring(prop);
410
411 /* If we get a section name, apply it and consume it */
412 if (strncmp("section", name, 7) == 0 && value != NULL) {
413 (void) xmlSetProp(sect, (xmlChar *)"name",
414 (xmlChar *)value);
415 continue;
416 }
417
418 /*
419 * We have an ordinary property. Add to the section.
420 *
421 * The memory allocated by sa_create_property is
422 * linked into the list of children under "sect",
423 * and will eventually be freed via that list.
424 */
425 node = sa_create_property(name, value);
426 (void) sa_add_protocol_property(sect, node);
427 }
428 scf_simple_app_props_free(props);
429
430 if (lastpgname)
431 free(lastpgname);
432 return (0);
433 }
434
435 /*
436 * Save the set of properties for a particular section, which is
437 * stored as a single property group. Properties will have been
438 * changed earlier by one or more calls to smbfs_save_property(),
439 * which only set the value in our array and marked them as
440 * SMBC_MODIFIED.
441 */
442 int
smbfs_save_propset()443 smbfs_save_propset()
444 {
445 smb_scfhandle_t *handle = NULL;
446 char propgroup[256];
447 char *section = smbclnt_proto_options[PROTO_OPT_SECTION].value;
448 char *uu = NULL;
449 uuid_t uuid;
450 int i, ret = 0;
451 sa_property_t propset;
452 int new = 0, nonnull = 0;
453
454 propset = sa_get_protocol_section(protoset, section);
455 (void) strlcpy(propgroup, SMBC_PG_PREFIX, sizeof (propgroup));
456 propgroup[SMBC_PG_PREFIX_LEN] = '\0';
457 uu = sa_get_property_attr(propset, "extra");
458 if (uu != NULL) {
459 (void) strlcat(propgroup, uu, sizeof (propgroup));
460 free(uu);
461 } else {
462 new = 1;
463 smbclnt_proto_options[PROTO_OPT_SECTION].flags |= SMBC_MODIFIED;
464 uuid_generate(uuid);
465 uuid_unparse(uuid, &propgroup[SMBC_PG_PREFIX_LEN]);
466 }
467
468 handle = smb_smf_scf_init(SMBC_FMRI_PREFIX);
469 if (handle == NULL) {
470 return (1);
471 }
472
473 if ((ret = smb_smf_instance_create(handle, SMBC_FMRI_PREFIX,
474 SMBC_PG_INSTANCE)) != SMBC_SMF_OK) {
475 goto out;
476 }
477
478 if ((ret = smb_smf_create_instance_pgroup(handle, propgroup))
479 != SMBC_SMF_OK) {
480 goto out;
481 }
482
483 if ((ret = smb_smf_start_transaction(handle)) != SMBC_SMF_OK) {
484 goto out;
485 }
486
487 for (i = PROTO_OPT_SECTION+1; i <= SMBC_OPT_MAX; i++) {
488 if ((smbclnt_proto_options[i].flags & SMBC_MODIFIED) == 0)
489 continue;
490 if (strcmp(smbclnt_proto_options[i].value, "") == 0)
491 ret = smb_smf_delete_property(handle,
492 smbclnt_proto_options[i].name);
493 else {
494 ret = smb_smf_set_string_property(handle,
495 smbclnt_proto_options[i].name,
496 smbclnt_proto_options[i].value);
497 nonnull = 1;
498 }
499 free(smbclnt_proto_options[i].value);
500 smbclnt_proto_options[i].value = NULL;
501 smbclnt_proto_options[i].flags &= ~SMBC_MODIFIED;
502 if (ret != SMBC_SMF_OK)
503 goto outtrans;
504 }
505 /*
506 * Suppress new, null entries by not saving the section name.
507 */
508 if (!new || nonnull) {
509 ret = smb_smf_set_string_property(handle,
510 smbclnt_proto_options[PROTO_OPT_SECTION].name,
511 smbclnt_proto_options[PROTO_OPT_SECTION].value);
512 free(smbclnt_proto_options[PROTO_OPT_SECTION].value);
513 smbclnt_proto_options[PROTO_OPT_SECTION].value = NULL;
514 smbclnt_proto_options[PROTO_OPT_SECTION].flags &=
515 ~SMBC_MODIFIED;
516 }
517 propset_changed = 0;
518
519 outtrans:
520 ret = smb_smf_end_transaction(handle);
521 out:
522 smb_smf_scf_fini(handle);
523 return (ret);
524 }
525
526 /*
527 * initprotofromdefault()
528 *
529 * read the default file(s) and add the defined values to the
530 * protoset. Note that default values are known from the built in
531 * table in case the file doesn't have a definition.
532 */
533
534 static int
initprotofromdefault()535 initprotofromdefault()
536 {
537 protoset = sa_create_protocol_properties(SMBFS_PROTOCOL_NAME);
538 if (protoset == NULL)
539 return (SA_NO_MEMORY);
540 if (smbclnt_config_load() != 0)
541 return (SA_OK);
542
543 return (SA_OK);
544 }
545
546 /*
547 *
548 * smbfs_features()
549 *
550 * Report the plugin's features
551 */
552 static uint64_t
smbfs_features()553 smbfs_features()
554 {
555 return (SA_FEATURE_HAS_SECTIONS | SA_FEATURE_ADD_PROPERTIES);
556 }
557
558 /*
559 * smbfs_init()
560 *
561 * Initialize the smb plugin.
562 */
563
564 static int
smbfs_init()565 smbfs_init()
566 {
567 int ret = SA_OK;
568
569 if (sa_plugin_ops.sa_init != smbfs_init) {
570 return (SA_SYSTEM_ERR);
571 }
572
573 if (initprotofromdefault() != SA_OK) {
574 return (SA_SYSTEM_ERR);
575 }
576
577 return (ret);
578 }
579
580 /*
581 * smbfs_fini()
582 *
583 * uninitialize the smb plugin. Want to avoid memory leaks.
584 */
585
586 static void
smbfs_fini()587 smbfs_fini()
588 {
589 if (propset_changed)
590 (void) smbfs_save_propset();
591 xmlFreeNode(protoset);
592 protoset = NULL;
593 }
594
595 /*
596 * smbfs_get_proto_set()
597 *
598 * Return an optionset with all the protocol specific properties in
599 * it.
600 */
601
602 static sa_protocol_properties_t
smbfs_get_proto_set()603 smbfs_get_proto_set()
604 {
605 return (protoset);
606 }
607
608 /*
609 * smbfs_validate_proto_prop(index, name, value)
610 *
611 * Verify that the property specifed by name can take the new
612 * value. This is a sanity check to prevent bad values getting into
613 * the default files.
614 */
615 static int
smbfs_validate_proto_prop(int index,char * section,char * name,char * value)616 smbfs_validate_proto_prop(int index, char *section, char *name, char *value)
617 {
618 if ((section == NULL) || (name == NULL) || (index < 0))
619 return (SA_BAD_VALUE);
620
621 if (smbclnt_proto_options[index].validator == NULL)
622 return (SA_OK);
623
624 return (smbclnt_proto_options[index].validator(index, section, value));
625 }
626
627 /*
628 * Save a property to our array; it will be stored to SMF later by
629 * smbfs_save_propset().
630 */
631 int
smbfs_save_property(int index,char * section,char * value)632 smbfs_save_property(int index, char *section, char *value)
633 {
634 char *s;
635
636 if (index == PROTO_OPT_WORKGROUP) {
637 index = PROTO_OPT_DOMAIN;
638 }
639 propset_changed = 1;
640 s = strdup(section);
641 if (s == NULL)
642 return (-1);
643 smbclnt_proto_options[PROTO_OPT_SECTION].value = s;
644 s = strdup(value);
645 if (s == NULL)
646 return (-1);
647 smbclnt_proto_options[index].value = s;
648 smbclnt_proto_options[index].flags |= SMBC_MODIFIED;
649 return (0);
650 }
651
652 /*
653 * smbfs_set_proto_prop(prop)
654 *
655 * check that prop is valid.
656 */
657 /*ARGSUSED*/
658 static int
smbfs_set_proto_prop(sa_property_t prop)659 smbfs_set_proto_prop(sa_property_t prop)
660 {
661 int ret = SA_OK;
662 char *name;
663 char *value;
664 char *section;
665 int i = -1;
666
667 section = sa_get_property_attr(prop, "section");
668 if (section == NULL)
669 return (SA_NO_SECTION);
670 name = sa_get_property_attr(prop, "type");
671 value = sa_get_property_attr(prop, "value");
672 if (name != NULL && value != NULL) {
673 i = findprotoopt(name);
674 if (i >= 0) {
675 ret = smbfs_validate_proto_prop(i, section,
676 name, value);
677 if (ret == SA_OK) {
678 if (smbfs_save_property(i, section,
679 value) != 0) {
680 ret = SA_SYSTEM_ERR;
681 errno = EIO;
682 }
683 }
684 } else
685 ret = SA_INVALID_NAME;
686 }
687 if (name != NULL)
688 sa_free_attr_string(name);
689 if (value != NULL)
690 sa_free_attr_string(value);
691 if (section != NULL)
692 sa_free_attr_string(section);
693
694 return (ret);
695 }
696
697 /*
698 * smbfs_get_status()
699 *
700 * What is the current status of the smbd? We use the SMF state here.
701 * Caller must free the returned value.
702 */
703
704 static char *
smbfs_get_status()705 smbfs_get_status()
706 {
707 return (smf_get_state(SMBC_DEFAULT_INSTANCE_FMRI));
708 }
709
710 /*
711 * Delete a section by its name, which we will have read into an
712 * XML optionset above. We need to find it and find its UUID to
713 * be able to generate the property group name in order to call
714 * smbfs_delete_property_group().
715 */
716 static int
smbfs_delete_section(char * section)717 smbfs_delete_section(char *section)
718 {
719 char propgroup[256];
720 char *uu = NULL;
721 sa_property_t propset;
722 int ret = SA_SYSTEM_ERR;
723
724 propset = sa_get_protocol_section(protoset, section);
725 (void) strlcpy(propgroup, SMBC_PG_PREFIX, sizeof (propgroup));
726 propgroup[SMBC_PG_PREFIX_LEN] = '\0';
727 uu = sa_get_property_attr(propset, "extra");
728 if (uu == NULL)
729 goto out;
730 (void) strlcat(propgroup, uu, sizeof (propgroup));
731 free(uu);
732 if ((ret = smbfs_delete_property_group(propgroup)) != SMBC_SMF_OK)
733 goto out;
734 ret = SA_OK;
735 out:
736 return (ret);
737 }
738
739 /*
740 * Delete a property group by its name. Called to do a 'delsect'
741 * or called when smbclnt_config_load() notices an empty section
742 * at the end of the properties.
743 */
744 static int
smbfs_delete_property_group(char * propgroup)745 smbfs_delete_property_group(char *propgroup)
746 {
747 smb_scfhandle_t *handle = NULL;
748 int ret = SA_SYSTEM_ERR;
749
750 handle = smb_smf_scf_init(SMBC_FMRI_PREFIX);
751 if (handle == NULL)
752 goto out;
753
754 if ((ret = smb_smf_instance_create(handle, SMBC_FMRI_PREFIX,
755 SMBC_PG_INSTANCE)) != SMBC_SMF_OK)
756 goto out;
757
758 if ((ret = smb_smf_delete_instance_pgroup(handle, propgroup))
759 != SMBC_SMF_OK)
760 goto out;
761 ret = SA_OK;
762 out:
763 smb_smf_scf_fini(handle);
764 return (ret);
765 }
766