xref: /illumos-gate/usr/src/lib/libdladm/common/secobj.c (revision aecfc01d1bad84e66649703f7fc2926ef70b34ba)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <strings.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #include <sys/dld.h>
34 #include <libinetutil.h>
35 #include <libdllink.h>
36 #include <libdladm_impl.h>
37 
38 static dladm_status_t	i_dladm_set_secobj_db(const char *,
39 			    dladm_secobj_class_t, uint8_t *, uint_t);
40 static dladm_status_t	i_dladm_get_secobj_db(const char *,
41 			    dladm_secobj_class_t *, uint8_t *, uint_t *);
42 static dladm_status_t	i_dladm_unset_secobj_db(const char *);
43 static dladm_status_t	i_dladm_walk_secobj_db(void *,
44 			    boolean_t (*)(void *, const char *));
45 
46 typedef struct secobj_class_info {
47 	const char		*sc_name;
48 	dld_secobj_class_t	sc_dldclass;
49 } secobj_class_info_t;
50 
51 static secobj_class_info_t secobj_class_table[] = {
52 	{"wep",	DLD_SECOBJ_CLASS_WEP},
53 	{"wpa",	DLD_SECOBJ_CLASS_WPA}
54 };
55 
56 #define	SECOBJ_MAXBUFSZ	65536
57 #define	NSECOBJCLASS \
58 	(sizeof (secobj_class_table) / sizeof (secobj_class_info_t))
59 
60 static boolean_t
61 dladm_check_secobjclass(dladm_secobj_class_t class)
62 {
63 	return (class >= 0 && class < NSECOBJCLASS);
64 }
65 
66 dladm_status_t
67 dladm_str2secobjclass(const char *str, dladm_secobj_class_t *class)
68 {
69 	int			i;
70 	secobj_class_info_t	*sp;
71 
72 	for (i = 0; i < NSECOBJCLASS; i++) {
73 		sp = &secobj_class_table[i];
74 		if (strcasecmp(str, sp->sc_name) == 0) {
75 			*class = i;
76 			return (DLADM_STATUS_OK);
77 		}
78 	}
79 	return (DLADM_STATUS_BADARG);
80 }
81 
82 const char *
83 dladm_secobjclass2str(dladm_secobj_class_t class, char *buf)
84 {
85 	const char		*s;
86 
87 	if (!dladm_check_secobjclass(class))
88 		s = "";
89 	else
90 		s = secobj_class_table[class].sc_name;
91 
92 	(void) snprintf(buf, DLADM_STRSIZE, "%s", s);
93 	return (buf);
94 }
95 
96 static boolean_t
97 dladm_convert_secobjclass(dladm_secobj_class_t class,
98     dld_secobj_class_t *dldclass)
99 {
100 	if (!dladm_check_secobjclass(class))
101 		return (B_FALSE);
102 
103 	*dldclass = secobj_class_table[class].sc_dldclass;
104 	return (B_TRUE);
105 }
106 
107 static boolean_t
108 dladm_convert_dldsecobjclass(dld_secobj_class_t dldclass,
109     dladm_secobj_class_t *class)
110 {
111 	int			i;
112 	secobj_class_info_t	*sp;
113 
114 	for (i = 0; i < NSECOBJCLASS; i++) {
115 		sp = &secobj_class_table[i];
116 		if (dldclass == sp->sc_dldclass) {
117 			*class = i;
118 			return (B_TRUE);
119 		}
120 	}
121 	return (B_FALSE);
122 }
123 
124 dladm_status_t
125 dladm_set_secobj(const char *obj_name, dladm_secobj_class_t class,
126     uint8_t *obj_val, uint_t obj_len, uint_t flags)
127 {
128 	int			fd;
129 	dladm_status_t		status = DLADM_STATUS_OK;
130 	dld_ioc_secobj_set_t	secobj_set;
131 	dld_secobj_t		*objp;
132 
133 	if (!dladm_check_secobjclass(class) || flags == 0 ||
134 	    obj_name == NULL || strlen(obj_name) > DLD_SECOBJ_NAME_MAX ||
135 	    obj_val == NULL || obj_len == 0 || obj_len > DLD_SECOBJ_VAL_MAX)
136 		return (DLADM_STATUS_BADARG);
137 
138 	if ((flags & DLADM_OPT_ACTIVE) == 0)
139 		goto persist;
140 
141 	bzero(&secobj_set, sizeof (secobj_set));
142 	objp = &secobj_set.ss_obj;
143 	if (!dladm_convert_secobjclass(class, &objp->so_class))
144 		return (DLADM_STATUS_BADARG);
145 
146 	(void) strlcpy(objp->so_name, obj_name, DLD_SECOBJ_NAME_MAX);
147 	bcopy(obj_val, objp->so_val, obj_len);
148 	objp->so_len = obj_len;
149 
150 	if ((flags & DLADM_OPT_CREATE) != 0)
151 		secobj_set.ss_flags = DLD_SECOBJ_OPT_CREATE;
152 
153 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
154 		return (dladm_errno2status(errno));
155 
156 	if (ioctl(fd, DLDIOC_SECOBJ_SET, &secobj_set) < 0)
157 		status = dladm_errno2status(errno);
158 
159 	(void) close(fd);
160 
161 	if (status != DLADM_STATUS_OK)
162 		return (status);
163 
164 persist:
165 	if ((flags & DLADM_OPT_PERSIST) != 0) {
166 		status = i_dladm_set_secobj_db(obj_name, class,
167 		    obj_val, obj_len);
168 	}
169 	return (status);
170 }
171 
172 dladm_status_t
173 dladm_get_secobj(const char *obj_name, dladm_secobj_class_t *classp,
174     uint8_t *obj_val, uint_t *obj_lenp, uint_t flags)
175 {
176 	int			fd;
177 	dladm_status_t		status = DLADM_STATUS_OK;
178 	dld_ioc_secobj_get_t	secobj_get;
179 	dld_secobj_t		*objp;
180 
181 	if (obj_name == NULL || strlen(obj_name) > DLD_SECOBJ_NAME_MAX ||
182 	    obj_val == NULL || obj_lenp == NULL || *obj_lenp == 0 ||
183 	    *obj_lenp > DLD_SECOBJ_VAL_MAX)
184 		return (DLADM_STATUS_BADARG);
185 
186 	if ((flags & DLADM_OPT_PERSIST) != 0) {
187 		return (i_dladm_get_secobj_db(obj_name, classp,
188 		    obj_val, obj_lenp));
189 	}
190 
191 	bzero(&secobj_get, sizeof (secobj_get));
192 	objp = &secobj_get.sg_obj;
193 	(void) strlcpy(objp->so_name, obj_name, DLD_SECOBJ_NAME_MAX);
194 
195 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
196 		return (dladm_errno2status(errno));
197 
198 	secobj_get.sg_size = sizeof (secobj_get);
199 	if (ioctl(fd, DLDIOC_SECOBJ_GET, &secobj_get) < 0)
200 		status = dladm_errno2status(errno);
201 
202 	(void) close(fd);
203 	if (objp->so_len > *obj_lenp)
204 		return (DLADM_STATUS_TOOSMALL);
205 
206 	if (!dladm_convert_dldsecobjclass(objp->so_class, classp))
207 		return (DLADM_STATUS_FAILED);
208 
209 	*obj_lenp = objp->so_len;
210 	bcopy(objp->so_val, obj_val, *obj_lenp);
211 	return (status);
212 }
213 
214 dladm_status_t
215 dladm_unset_secobj(const char *obj_name, uint_t flags)
216 {
217 	int			fd;
218 	dladm_status_t		status = DLADM_STATUS_OK;
219 	dld_ioc_secobj_unset_t	secobj_unset;
220 
221 	if (obj_name == NULL || strlen(obj_name) > DLD_SECOBJ_NAME_MAX ||
222 	    flags == 0)
223 		return (DLADM_STATUS_BADARG);
224 
225 	if ((flags & DLADM_OPT_ACTIVE) == 0)
226 		goto persist;
227 
228 	bzero(&secobj_unset, sizeof (secobj_unset));
229 	(void) strlcpy(secobj_unset.su_name, obj_name, DLD_SECOBJ_NAME_MAX);
230 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
231 		return (dladm_errno2status(errno));
232 
233 	if (ioctl(fd, DLDIOC_SECOBJ_UNSET, &secobj_unset) < 0)
234 		status = dladm_errno2status(errno);
235 
236 	(void) close(fd);
237 	if (status != DLADM_STATUS_OK)
238 		return (status);
239 
240 persist:
241 	if ((flags & DLADM_OPT_PERSIST) != 0)
242 		status = i_dladm_unset_secobj_db(obj_name);
243 
244 	return (status);
245 }
246 
247 dladm_status_t
248 dladm_walk_secobj(void *arg, boolean_t (*func)(void *, const char *),
249     uint_t flags)
250 {
251 	int			fd = -1;
252 	dladm_status_t		status = DLADM_STATUS_OK;
253 	dld_ioc_secobj_get_t	*secobj_getp;
254 	dld_secobj_t		*objp;
255 	size_t			secobj_bufsz;
256 
257 	if ((flags & DLADM_OPT_PERSIST) != 0)
258 		return (i_dladm_walk_secobj_db(arg, func));
259 
260 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
261 		return (dladm_errno2status(errno));
262 
263 	/* Start with enough room for 10 objects, increase if necessary. */
264 	secobj_bufsz = sizeof (*secobj_getp) + (10 * sizeof (*objp));
265 	secobj_getp = calloc(1, secobj_bufsz);
266 	if (secobj_getp == NULL) {
267 		status = dladm_errno2status(errno);
268 		goto done;
269 	}
270 
271 tryagain:
272 	secobj_getp->sg_size = secobj_bufsz;
273 	if (ioctl(fd, DLDIOC_SECOBJ_GET, secobj_getp) < 0) {
274 		if (errno == ENOSPC) {
275 			/* Increase the buffer size and try again. */
276 			secobj_bufsz *= 2;
277 			if (secobj_bufsz > SECOBJ_MAXBUFSZ) {
278 				status = dladm_errno2status(errno);
279 				goto done;
280 			}
281 			secobj_getp = realloc(secobj_getp, secobj_bufsz);
282 			if (secobj_getp == NULL) {
283 				status = dladm_errno2status(errno);
284 				goto done;
285 			}
286 			bzero(secobj_getp, secobj_bufsz);
287 			goto tryagain;
288 		}
289 		status = dladm_errno2status(errno);
290 		goto done;
291 	}
292 
293 	objp = (dld_secobj_t *)(secobj_getp + 1);
294 	while (secobj_getp->sg_count > 0) {
295 		if (!func(arg, objp->so_name))
296 			goto done;
297 		secobj_getp->sg_count--;
298 		objp++;
299 	}
300 done:
301 	(void) close(fd);
302 	free(secobj_getp);
303 	return (status);
304 }
305 
306 /*
307  * Data structures used for implementing persistent secure objects
308  */
309 typedef struct secobj_info {
310 	const char		*si_name;
311 	dladm_secobj_class_t	*si_classp;
312 	uint8_t			*si_val;
313 	uint_t			*si_lenp;
314 } secobj_info_t;
315 
316 typedef struct secobj_name {
317 	char			*sn_name;
318 	struct secobj_name	*sn_next;
319 } secobj_name_t;
320 
321 typedef struct secobj_db_state	secobj_db_state_t;
322 
323 typedef boolean_t (*secobj_db_op_t)(struct secobj_db_state *, char *,
324     secobj_info_t *, dladm_status_t *);
325 
326 struct secobj_db_state {
327 	secobj_db_op_t		ss_op;
328 	secobj_info_t		ss_info;
329 	secobj_name_t		**ss_namelist;
330 };
331 
332 /*
333  * Update or generate a secobj entry using the info in ssp->ss_info.
334  */
335 /* ARGSUSED */
336 static boolean_t
337 process_secobj_set(secobj_db_state_t *ssp, char *buf, secobj_info_t *sip,
338     dladm_status_t *statusp)
339 {
340 	char	tmpbuf[MAXLINELEN];
341 	char	classbuf[DLADM_STRSIZE];
342 	char	*ptr = tmpbuf, *lim = tmpbuf + MAXLINELEN;
343 	int	i;
344 
345 	sip = &ssp->ss_info;
346 
347 	ptr += snprintf(ptr, BUFLEN(lim, ptr), "%s\t", sip->si_name);
348 	ptr += snprintf(ptr, BUFLEN(lim, ptr), "%s\t",
349 	    dladm_secobjclass2str(*sip->si_classp, classbuf));
350 
351 	ptr += snprintf(ptr, BUFLEN(lim, ptr), "0x");
352 	for (i = 0; i < *sip->si_lenp; i++) {
353 		ptr += snprintf(ptr, BUFLEN(lim, ptr), "%02x",
354 		    sip->si_val[i] & 0xff);
355 	}
356 	if (ptr > lim) {
357 		*statusp = DLADM_STATUS_TOOSMALL;
358 		return (B_FALSE);
359 	}
360 	(void) snprintf(buf, MAXLINELEN, "%s\n", tmpbuf);
361 	return (B_FALSE);
362 }
363 
364 /* ARGSUSED */
365 static boolean_t
366 process_secobj_get(secobj_db_state_t *ssp, char *buf, secobj_info_t *sip,
367     dladm_status_t *statusp)
368 {
369 	if (*sip->si_lenp > *ssp->ss_info.si_lenp) {
370 		*statusp = DLADM_STATUS_TOOSMALL;
371 		return (B_FALSE);
372 	}
373 	bcopy(sip->si_val, ssp->ss_info.si_val, *sip->si_lenp);
374 	*ssp->ss_info.si_lenp = *sip->si_lenp;
375 	*ssp->ss_info.si_classp = *sip->si_classp;
376 	return (B_FALSE);
377 }
378 
379 /* ARGSUSED */
380 static boolean_t
381 process_secobj_unset(secobj_db_state_t *ssp, char *buf, secobj_info_t *sip,
382     dladm_status_t *statusp)
383 {
384 	/*
385 	 * Delete line.
386 	 */
387 	buf[0] = '\0';
388 	return (B_FALSE);
389 }
390 
391 /* ARGSUSED */
392 static boolean_t
393 process_secobj_walk(secobj_db_state_t *ssp, char *buf, secobj_info_t *sip,
394     dladm_status_t *statusp)
395 {
396 	secobj_name_t	*snp;
397 
398 	if ((snp = malloc(sizeof (*snp))) == NULL)
399 		return (B_TRUE);
400 
401 	if ((snp->sn_name = strdup(sip->si_name)) == NULL) {
402 		free(snp);
403 		return (B_TRUE);
404 	}
405 
406 	snp->sn_next = NULL;
407 	*ssp->ss_namelist = snp;
408 	ssp->ss_namelist = &snp->sn_next;
409 	return (B_TRUE);
410 }
411 
412 /* ARGSUSED */
413 static boolean_t
414 process_secobj_init(secobj_db_state_t *ssp, char *buf, secobj_info_t *sip,
415     dladm_status_t *statusp)
416 {
417 	*statusp = dladm_set_secobj(sip->si_name, *sip->si_classp, sip->si_val,
418 	    *sip->si_lenp, DLADM_OPT_ACTIVE | DLADM_OPT_CREATE);
419 	return (B_TRUE);
420 }
421 
422 static int
423 parse_secobj_val(char *buf, secobj_info_t *sip)
424 {
425 	if (strncmp(buf, "0x", 2) != 0)
426 		return (EINVAL);
427 
428 	return (hexascii_to_octet(buf + 2, strlen(buf) - 2,
429 	    sip->si_val, sip->si_lenp));
430 }
431 
432 static boolean_t
433 process_secobj_line(secobj_db_state_t *ssp, char *buf,
434     dladm_status_t *statusp)
435 {
436 	secobj_info_t		sinfo;
437 	dladm_secobj_class_t	class;
438 	uint8_t			val[DLADM_SECOBJ_VAL_MAX];
439 	uint_t			vlen;
440 	int			i, len, nlen;
441 	char			*str, *lasts;
442 
443 	/*
444 	 * Skip leading spaces, blank lines, and comments.
445 	 */
446 	len = strlen(buf);
447 	for (i = 0; i < len; i++) {
448 		if (!isspace(buf[i]))
449 			break;
450 	}
451 	if (i == len || buf[i] == '#')
452 		return (B_TRUE);
453 
454 	str = buf + i;
455 	if (ssp->ss_info.si_name != NULL) {
456 		/*
457 		 * Skip objects we're not interested in.
458 		 */
459 		nlen = strlen(ssp->ss_info.si_name);
460 		if (strncmp(str, ssp->ss_info.si_name, nlen) != 0 ||
461 		    !isspace(str[nlen]))
462 			return (B_TRUE);
463 
464 		sinfo.si_name = ssp->ss_info.si_name;
465 	} else {
466 		/*
467 		 * If an object is not specified, find the object name
468 		 * and assign it to sinfo.si_name.
469 		 */
470 		if (strtok_r(str, " \n\t", &lasts) == NULL)
471 			goto fail;
472 
473 		nlen = strlen(str);
474 		sinfo.si_name = str;
475 	}
476 	str += nlen + 1;
477 	if (str >= buf + len)
478 		goto fail;
479 
480 	/*
481 	 * Find the class name.
482 	 */
483 	if ((str = strtok_r(str, " \n\t", &lasts)) == NULL)
484 		goto fail;
485 
486 	*statusp = dladm_str2secobjclass(str, &class);
487 	if (*statusp != DLADM_STATUS_OK)
488 		goto fail;
489 
490 	/*
491 	 * Find the object value.
492 	 */
493 	if ((str = strtok_r(NULL, " \n\t", &lasts)) == NULL)
494 		goto fail;
495 
496 	vlen = DLADM_SECOBJ_VAL_MAX;
497 	sinfo.si_classp = &class;
498 	sinfo.si_val = val;
499 	sinfo.si_lenp = &vlen;
500 	if (parse_secobj_val(str, &sinfo) != 0)
501 		goto fail;
502 
503 	return ((*ssp->ss_op)(ssp, buf, &sinfo, statusp));
504 
505 fail:
506 	/*
507 	 * Delete corrupted line.
508 	 */
509 	buf[0] = '\0';
510 	return (B_TRUE);
511 }
512 
513 static dladm_status_t
514 process_secobj_db(void *arg, FILE *fp, FILE *nfp)
515 {
516 	secobj_db_state_t	*ssp = arg;
517 	dladm_status_t		status = DLADM_STATUS_OK;
518 	char			buf[MAXLINELEN];
519 	boolean_t		cont = B_TRUE;
520 
521 	/*
522 	 * This loop processes each line of the configuration file.
523 	 * buf can potentially be modified by process_secobj_line().
524 	 * If this is a write operation and buf is not truncated, buf will
525 	 * be written to disk. process_secobj_line() will no longer be
526 	 * called after it returns B_FALSE; at which point the remainder
527 	 * of the file will continue to be read and, if necessary, written
528 	 * to disk as well.
529 	 */
530 	while (fgets(buf, MAXLINELEN, fp) != NULL) {
531 		if (cont)
532 			cont = process_secobj_line(ssp, buf, &status);
533 
534 		if (nfp != NULL && buf[0] != '\0' && fputs(buf, nfp) == EOF) {
535 			status = dladm_errno2status(errno);
536 			break;
537 		}
538 	}
539 	if (status != DLADM_STATUS_OK || !cont)
540 		return (status);
541 
542 	if (ssp->ss_op == process_secobj_set) {
543 		/*
544 		 * If the specified object is not found above, we add the
545 		 * object to the configuration file.
546 		 */
547 		(void) (*ssp->ss_op)(ssp, buf, NULL, &status);
548 		if (status == DLADM_STATUS_OK && fputs(buf, nfp) == EOF)
549 			status = dladm_errno2status(errno);
550 	}
551 
552 	if (ssp->ss_op == process_secobj_unset ||
553 	    ssp->ss_op == process_secobj_get)
554 		status = DLADM_STATUS_NOTFOUND;
555 
556 	return (status);
557 }
558 
559 #define	SECOBJ_RW_DB(statep, writeop) \
560 	(i_dladm_rw_db("/etc/dladm/secobj.conf", S_IRUSR | S_IWUSR, \
561 	process_secobj_db, (statep), (writeop)))
562 
563 static dladm_status_t
564 i_dladm_set_secobj_db(const char *obj_name, dladm_secobj_class_t class,
565     uint8_t *obj_val, uint_t obj_len)
566 {
567 	secobj_db_state_t	state;
568 
569 	state.ss_op = process_secobj_set;
570 	state.ss_info.si_name = obj_name;
571 	state.ss_info.si_classp = &class;
572 	state.ss_info.si_val = obj_val;
573 	state.ss_info.si_lenp = &obj_len;
574 	state.ss_namelist = NULL;
575 
576 	return (SECOBJ_RW_DB(&state, B_TRUE));
577 }
578 
579 static dladm_status_t
580 i_dladm_get_secobj_db(const char *obj_name, dladm_secobj_class_t *classp,
581     uint8_t *obj_val, uint_t *obj_lenp)
582 {
583 	secobj_db_state_t	state;
584 
585 	state.ss_op = process_secobj_get;
586 	state.ss_info.si_name = obj_name;
587 	state.ss_info.si_classp = classp;
588 	state.ss_info.si_val = obj_val;
589 	state.ss_info.si_lenp = obj_lenp;
590 	state.ss_namelist = NULL;
591 
592 	return (SECOBJ_RW_DB(&state, B_FALSE));
593 }
594 
595 static dladm_status_t
596 i_dladm_unset_secobj_db(const char *obj_name)
597 {
598 	secobj_db_state_t	state;
599 
600 	state.ss_op = process_secobj_unset;
601 	state.ss_info.si_name = obj_name;
602 	state.ss_info.si_classp = NULL;
603 	state.ss_info.si_val = NULL;
604 	state.ss_info.si_lenp = NULL;
605 	state.ss_namelist = NULL;
606 
607 	return (SECOBJ_RW_DB(&state, B_TRUE));
608 }
609 
610 static dladm_status_t
611 i_dladm_walk_secobj_db(void *arg, boolean_t (*func)(void *, const char *))
612 {
613 	secobj_db_state_t	state;
614 	secobj_name_t		*snp = NULL, *fsnp;
615 	dladm_status_t		status;
616 	boolean_t		cont = B_TRUE;
617 
618 	state.ss_op = process_secobj_walk;
619 	state.ss_info.si_name = NULL;
620 	state.ss_info.si_classp = NULL;
621 	state.ss_info.si_val = NULL;
622 	state.ss_info.si_lenp = NULL;
623 	state.ss_namelist = &snp;
624 
625 	status = SECOBJ_RW_DB(&state, B_FALSE);
626 	if (status != DLADM_STATUS_OK)
627 		return (status);
628 
629 	while (snp != NULL) {
630 		fsnp = snp;
631 		snp = snp->sn_next;
632 		if (cont)
633 			cont = func(arg, fsnp->sn_name);
634 		free(fsnp->sn_name);
635 		free(fsnp);
636 	}
637 	return (status);
638 }
639 
640 dladm_status_t
641 dladm_init_secobj(void)
642 {
643 	secobj_db_state_t	state;
644 
645 	state.ss_op = process_secobj_init;
646 	state.ss_info.si_name = NULL;
647 	state.ss_info.si_classp = NULL;
648 	state.ss_info.si_val = NULL;
649 	state.ss_info.si_lenp = NULL;
650 	state.ss_namelist = NULL;
651 
652 	return (SECOBJ_RW_DB(&state, B_FALSE));
653 }
654