1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2000-2001 Boris Popov
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/kernel.h>
35#include <sys/iconv.h>
36#include <sys/malloc.h>
37#include <sys/mount.h>
38#include <sys/sx.h>
39#include <sys/syslog.h>
40
41#include "iconv_converter_if.h"
42
43SYSCTL_DECL(_kern_iconv);
44
45SYSCTL_NODE(_kern, OID_AUTO, iconv, CTLFLAG_RW, NULL, "kernel iconv interface");
46
47MALLOC_DEFINE(M_ICONV, "iconv", "ICONV structures");
48static MALLOC_DEFINE(M_ICONVDATA, "iconv_data", "ICONV data");
49
50MODULE_VERSION(libiconv, 2);
51
52static struct sx iconv_lock;
53
54#ifdef notnow
55/*
56 * iconv converter instance
57 */
58struct iconv_converter {
59	KOBJ_FIELDS;
60	void *			c_data;
61};
62#endif
63
64struct sysctl_oid *iconv_oid_hook = &sysctl___kern_iconv;
65
66/*
67 * List of loaded converters
68 */
69static TAILQ_HEAD(iconv_converter_list, iconv_converter_class)
70    iconv_converters = TAILQ_HEAD_INITIALIZER(iconv_converters);
71
72/*
73 * List of supported/loaded charsets pairs
74 */
75static TAILQ_HEAD(, iconv_cspair)
76    iconv_cslist = TAILQ_HEAD_INITIALIZER(iconv_cslist);
77static int iconv_csid = 1;
78
79static char iconv_unicode_string[] = "unicode";	/* save eight bytes when possible */
80
81static void iconv_unregister_cspair(struct iconv_cspair *csp);
82
83static int
84iconv_mod_unload(void)
85{
86	struct iconv_cspair *csp;
87
88	sx_xlock(&iconv_lock);
89	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
90		if (csp->cp_refcount) {
91			sx_xunlock(&iconv_lock);
92			return EBUSY;
93		}
94	}
95
96	while ((csp = TAILQ_FIRST(&iconv_cslist)) != NULL)
97		iconv_unregister_cspair(csp);
98	sx_xunlock(&iconv_lock);
99	sx_destroy(&iconv_lock);
100	return 0;
101}
102
103static int
104iconv_mod_handler(module_t mod, int type, void *data)
105{
106	int error;
107
108	switch (type) {
109	    case MOD_LOAD:
110		error = 0;
111		sx_init(&iconv_lock, "iconv");
112		break;
113	    case MOD_UNLOAD:
114		error = iconv_mod_unload();
115		break;
116	    default:
117		error = EINVAL;
118	}
119	return error;
120}
121
122static moduledata_t iconv_mod = {
123	"iconv", iconv_mod_handler, NULL
124};
125
126DECLARE_MODULE(iconv, iconv_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
127
128static int
129iconv_register_converter(struct iconv_converter_class *dcp)
130{
131	kobj_class_compile((struct kobj_class*)dcp);
132	dcp->refs++;
133	TAILQ_INSERT_TAIL(&iconv_converters, dcp, cc_link);
134	return 0;
135}
136
137static int
138iconv_unregister_converter(struct iconv_converter_class *dcp)
139{
140	dcp->refs--;
141	if (dcp->refs > 1) {
142		ICDEBUG("converter has %d references left\n", dcp->refs);
143		return EBUSY;
144	}
145	TAILQ_REMOVE(&iconv_converters, dcp, cc_link);
146	kobj_class_free((struct kobj_class*)dcp);
147	return 0;
148}
149
150static int
151iconv_lookupconv(const char *name, struct iconv_converter_class **dcpp)
152{
153	struct iconv_converter_class *dcp;
154
155	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
156		if (name == NULL)
157			continue;
158		if (strcmp(name, ICONV_CONVERTER_NAME(dcp)) == 0) {
159			if (dcpp)
160				*dcpp = dcp;
161			return 0;
162		}
163	}
164	return ENOENT;
165}
166
167static int
168iconv_lookupcs(const char *to, const char *from, struct iconv_cspair **cspp)
169{
170	struct iconv_cspair *csp;
171
172	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
173		if (strcasecmp(csp->cp_to, to) == 0 &&
174		    strcasecmp(csp->cp_from, from) == 0) {
175			if (cspp)
176				*cspp = csp;
177			return 0;
178		}
179	}
180	return ENOENT;
181}
182
183static int
184iconv_register_cspair(const char *to, const char *from,
185	struct iconv_converter_class *dcp, void *data,
186	struct iconv_cspair **cspp)
187{
188	struct iconv_cspair *csp;
189	char *cp;
190	int csize, ucsto, ucsfrom;
191
192	if (iconv_lookupcs(to, from, NULL) == 0)
193		return EEXIST;
194	csize = sizeof(*csp);
195	ucsto = strcmp(to, iconv_unicode_string) == 0;
196	if (!ucsto)
197		csize += strlen(to) + 1;
198	ucsfrom = strcmp(from, iconv_unicode_string) == 0;
199	if (!ucsfrom)
200		csize += strlen(from) + 1;
201	csp = malloc(csize, M_ICONV, M_WAITOK);
202	bzero(csp, csize);
203	csp->cp_id = iconv_csid++;
204	csp->cp_dcp = dcp;
205	cp = (char*)(csp + 1);
206	if (!ucsto) {
207		strcpy(cp, to);
208		csp->cp_to = cp;
209		cp += strlen(cp) + 1;
210	} else
211		csp->cp_to = iconv_unicode_string;
212	if (!ucsfrom) {
213		strcpy(cp, from);
214		csp->cp_from = cp;
215	} else
216		csp->cp_from = iconv_unicode_string;
217	csp->cp_data = data;
218
219	TAILQ_INSERT_TAIL(&iconv_cslist, csp, cp_link);
220	*cspp = csp;
221	return 0;
222}
223
224static void
225iconv_unregister_cspair(struct iconv_cspair *csp)
226{
227	TAILQ_REMOVE(&iconv_cslist, csp, cp_link);
228	if (csp->cp_data)
229		free(csp->cp_data, M_ICONVDATA);
230	free(csp, M_ICONV);
231}
232
233/*
234 * Lookup and create an instance of converter.
235 * Currently this layer didn't have associated 'instance' structure
236 * to avoid unnesessary memory allocation.
237 */
238int
239iconv_open(const char *to, const char *from, void **handle)
240{
241	struct iconv_cspair *csp, *cspfrom, *cspto;
242	struct iconv_converter_class *dcp;
243	const char *cnvname;
244	int error;
245
246	/*
247	 * First, lookup fully qualified cspairs
248	 */
249	error = iconv_lookupcs(to, from, &csp);
250	if (error == 0)
251		return ICONV_CONVERTER_OPEN(csp->cp_dcp, csp, NULL, handle);
252
253	/*
254	 * Well, nothing found. Now try to construct a composite conversion
255	 * ToDo: add a 'capability' field to converter
256	 */
257	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
258		cnvname = ICONV_CONVERTER_NAME(dcp);
259		if (cnvname == NULL)
260			continue;
261		error = iconv_lookupcs(cnvname, from, &cspfrom);
262		if (error)
263			continue;
264		error = iconv_lookupcs(to, cnvname, &cspto);
265		if (error)
266			continue;
267		/*
268		 * Fine, we're found a pair which can be combined together
269		 */
270		return ICONV_CONVERTER_OPEN(dcp, cspto, cspfrom, handle);
271	}
272	return ENOENT;
273}
274
275int
276iconv_close(void *handle)
277{
278	return ICONV_CONVERTER_CLOSE(handle);
279}
280
281int
282iconv_conv(void *handle, const char **inbuf,
283	size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
284{
285	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft, 0, 0);
286}
287
288int
289iconv_conv_case(void *handle, const char **inbuf,
290	size_t *inbytesleft, char **outbuf, size_t *outbytesleft, int casetype)
291{
292	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft, 0, casetype);
293}
294
295int
296iconv_convchr(void *handle, const char **inbuf,
297	size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
298{
299	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft, 1, 0);
300}
301
302int
303iconv_convchr_case(void *handle, const char **inbuf,
304	size_t *inbytesleft, char **outbuf, size_t *outbytesleft, int casetype)
305{
306	return ICONV_CONVERTER_CONV(handle, inbuf, inbytesleft, outbuf, outbytesleft, 1, casetype);
307}
308
309int
310towlower(int c, void *handle)
311{
312	return ICONV_CONVERTER_TOLOWER(handle, c);
313}
314
315int
316towupper(int c, void *handle)
317{
318	return ICONV_CONVERTER_TOUPPER(handle, c);
319}
320
321/*
322 * Give a list of loaded converters. Each name terminated with 0.
323 * An empty string terminates the list.
324 */
325static int
326iconv_sysctl_drvlist(SYSCTL_HANDLER_ARGS)
327{
328	struct iconv_converter_class *dcp;
329	const char *name;
330	char spc;
331	int error;
332
333	error = 0;
334	sx_slock(&iconv_lock);
335	TAILQ_FOREACH(dcp, &iconv_converters, cc_link) {
336		name = ICONV_CONVERTER_NAME(dcp);
337		if (name == NULL)
338			continue;
339		error = SYSCTL_OUT(req, name, strlen(name) + 1);
340		if (error)
341			break;
342	}
343	sx_sunlock(&iconv_lock);
344	if (error)
345		return error;
346	spc = 0;
347	error = SYSCTL_OUT(req, &spc, sizeof(spc));
348	return error;
349}
350
351SYSCTL_PROC(_kern_iconv, OID_AUTO, drvlist, CTLFLAG_RD | CTLTYPE_OPAQUE,
352	    NULL, 0, iconv_sysctl_drvlist, "S,xlat", "registered converters");
353
354/*
355 * List all available charset pairs.
356 */
357static int
358iconv_sysctl_cslist(SYSCTL_HANDLER_ARGS)
359{
360	struct iconv_cspair *csp;
361	struct iconv_cspair_info csi;
362	int error;
363
364	error = 0;
365	bzero(&csi, sizeof(csi));
366	csi.cs_version = ICONV_CSPAIR_INFO_VER;
367	sx_slock(&iconv_lock);
368	TAILQ_FOREACH(csp, &iconv_cslist, cp_link) {
369		csi.cs_id = csp->cp_id;
370		csi.cs_refcount = csp->cp_refcount;
371		csi.cs_base = csp->cp_base ? csp->cp_base->cp_id : 0;
372		strcpy(csi.cs_to, csp->cp_to);
373		strcpy(csi.cs_from, csp->cp_from);
374		error = SYSCTL_OUT(req, &csi, sizeof(csi));
375		if (error)
376			break;
377	}
378	sx_sunlock(&iconv_lock);
379	return error;
380}
381
382SYSCTL_PROC(_kern_iconv, OID_AUTO, cslist, CTLFLAG_RD | CTLTYPE_OPAQUE,
383	    NULL, 0, iconv_sysctl_cslist, "S,xlat", "registered charset pairs");
384
385int
386iconv_add(const char *converter, const char *to, const char *from)
387{
388	struct iconv_converter_class *dcp;
389	struct iconv_cspair *csp;
390
391	if (iconv_lookupconv(converter, &dcp) != 0)
392		return EINVAL;
393
394	return iconv_register_cspair(to, from, dcp, NULL, &csp);
395}
396
397/*
398 * Add new charset pair
399 */
400static int
401iconv_sysctl_add(SYSCTL_HANDLER_ARGS)
402{
403	struct iconv_converter_class *dcp;
404	struct iconv_cspair *csp;
405	struct iconv_add_in din;
406	struct iconv_add_out dout;
407	int error;
408
409	error = SYSCTL_IN(req, &din, sizeof(din));
410	if (error)
411		return error;
412	if (din.ia_version != ICONV_ADD_VER)
413		return EINVAL;
414	if (din.ia_datalen > ICONV_CSMAXDATALEN)
415		return EINVAL;
416	if (strnlen(din.ia_from, sizeof(din.ia_from)) >= ICONV_CSNMAXLEN)
417		return EINVAL;
418	if (strnlen(din.ia_to, sizeof(din.ia_to)) >= ICONV_CSNMAXLEN)
419		return EINVAL;
420	if (strnlen(din.ia_converter, sizeof(din.ia_converter)) >= ICONV_CNVNMAXLEN)
421		return EINVAL;
422	if (iconv_lookupconv(din.ia_converter, &dcp) != 0)
423		return EINVAL;
424	sx_xlock(&iconv_lock);
425	error = iconv_register_cspair(din.ia_to, din.ia_from, dcp, NULL, &csp);
426	if (error) {
427		sx_xunlock(&iconv_lock);
428		return error;
429	}
430	if (din.ia_datalen) {
431		csp->cp_data = malloc(din.ia_datalen, M_ICONVDATA, M_WAITOK);
432		error = copyin(din.ia_data, csp->cp_data, din.ia_datalen);
433		if (error)
434			goto bad;
435	}
436	dout.ia_csid = csp->cp_id;
437	error = SYSCTL_OUT(req, &dout, sizeof(dout));
438	if (error)
439		goto bad;
440	sx_xunlock(&iconv_lock);
441	ICDEBUG("%s => %s, %d bytes\n",din.ia_from, din.ia_to, din.ia_datalen);
442	return 0;
443bad:
444	iconv_unregister_cspair(csp);
445	sx_xunlock(&iconv_lock);
446	return error;
447}
448
449SYSCTL_PROC(_kern_iconv, OID_AUTO, add, CTLFLAG_RW | CTLTYPE_OPAQUE,
450	    NULL, 0, iconv_sysctl_add, "S,xlat", "register charset pair");
451
452/*
453 * Default stubs for converters
454 */
455int
456iconv_converter_initstub(struct iconv_converter_class *dp)
457{
458	return 0;
459}
460
461int
462iconv_converter_donestub(struct iconv_converter_class *dp)
463{
464	return 0;
465}
466
467int
468iconv_converter_tolowerstub(int c, void *handle)
469{
470	return (c);
471}
472
473int
474iconv_converter_handler(module_t mod, int type, void *data)
475{
476	struct iconv_converter_class *dcp = data;
477	int error;
478
479	switch (type) {
480	    case MOD_LOAD:
481		sx_xlock(&iconv_lock);
482		error = iconv_register_converter(dcp);
483		if (error) {
484			sx_xunlock(&iconv_lock);
485			break;
486		}
487		error = ICONV_CONVERTER_INIT(dcp);
488		if (error)
489			iconv_unregister_converter(dcp);
490		sx_xunlock(&iconv_lock);
491		break;
492	    case MOD_UNLOAD:
493		sx_xlock(&iconv_lock);
494		ICONV_CONVERTER_DONE(dcp);
495		error = iconv_unregister_converter(dcp);
496		sx_xunlock(&iconv_lock);
497		break;
498	    default:
499		error = EINVAL;
500	}
501	return error;
502}
503
504/*
505 * Common used functions (don't use with unicode)
506 */
507char *
508iconv_convstr(void *handle, char *dst, const char *src)
509{
510	char *p = dst;
511	size_t inlen, outlen;
512	int error;
513
514	if (handle == NULL) {
515		strcpy(dst, src);
516		return dst;
517	}
518	inlen = outlen = strlen(src);
519	error = iconv_conv(handle, NULL, NULL, &p, &outlen);
520	if (error)
521		return NULL;
522	error = iconv_conv(handle, &src, &inlen, &p, &outlen);
523	if (error)
524		return NULL;
525	*p = 0;
526	return dst;
527}
528
529void *
530iconv_convmem(void *handle, void *dst, const void *src, int size)
531{
532	const char *s = src;
533	char *d = dst;
534	size_t inlen, outlen;
535	int error;
536
537	if (size == 0)
538		return dst;
539	if (handle == NULL) {
540		memcpy(dst, src, size);
541		return dst;
542	}
543	inlen = outlen = size;
544	error = iconv_conv(handle, NULL, NULL, &d, &outlen);
545	if (error)
546		return NULL;
547	error = iconv_conv(handle, &s, &inlen, &d, &outlen);
548	if (error)
549		return NULL;
550	return dst;
551}
552
553int
554iconv_lookupcp(char **cpp, const char *s)
555{
556	if (cpp == NULL) {
557		ICDEBUG("warning a NULL list passed\n", "");
558		return ENOENT;
559	}
560	for (; *cpp; cpp++)
561		if (strcmp(*cpp, s) == 0)
562			return 0;
563	return ENOENT;
564}
565
566/*
567 * Return if fsname is in use of not
568 */
569int
570iconv_vfs_refcount(const char *fsname)
571{
572	struct vfsconf *vfsp;
573
574	vfsp = vfs_byname(fsname);
575	if (vfsp != NULL && vfsp->vfc_refcount > 0)
576		return (EBUSY);
577	return (0);
578}
579