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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
24 */
25
26#include <ctype.h>
27#include <dirent.h>
28#include <errno.h>
29#include <locale.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <sys/socket.h>
34#include <sys/socketvar.h>
35#include <sys/stat.h>
36#include <unistd.h>
37
38#define	MAXLINELEN	4096
39
40/*
41 * Usage:
42 *	soconfig -d <dir>
43 *		Reads input from files in dir.
44 *
45 *	soconfig -f <file>
46 *		Reads input from file. The file is structured as
47 *			 <fam> <type> <protocol> <path|module>
48 *			 <fam> <type> <protocol>
49 *		with the first line registering and the second line
50 *		deregistering.
51 *
52 *	soconfig <fam> <type> <protocol> <path|module>
53 *		registers
54 *
55 *	soconfig <fam> <type> <protocol>
56 *		deregisters
57 *
58 *	soconfig -l
59 *		print the in-kernel socket configuration table
60 *
61 * Filter Operations (Consolidation Private):
62 *
63 *	soconfig -F <name> <modname> {auto [top | bottom | before:filter |
64 *		after:filter] | prog} <fam>:<type>:<proto>,...
65 *		configure filter
66 *
67 *	soconfig -F <name>
68 *		unconfigures filter
69 */
70
71static int	parse_files_in_dir(const char *dir);
72
73static int	parse_file(char *filename);
74
75static int	split_line(char *line, char *argvec[], int maxargvec);
76
77static int	parse_params(char *famstr, char *typestr, char *protostr,
78				char *path, const char *file, int line);
79
80static int	parse_int(char *str);
81
82static void	usage(void);
83
84static int	parse_filter_params(int argc, char **argv);
85
86static int	print_socktable();
87
88int
89main(argc, argv)
90	int argc;
91	char *argv[];
92{
93	int ret;
94
95	argc--; argv++;
96
97	(void) setlocale(LC_ALL, "");
98#if !defined(TEXT_DOMAIN)
99#define	TEXT_DOMAIN "SYS_TEST"
100#endif
101	(void) textdomain(TEXT_DOMAIN);
102
103	if (argc == 1 && strcmp(argv[0], "-l") == 0) {
104		ret = print_socktable();
105		exit(ret);
106	}
107
108	if (argc >= 2 && strcmp(argv[0], "-F") == 0) {
109		argc--; argv++;
110		ret = parse_filter_params(argc, argv);
111		exit(ret);
112	}
113	if (argc == 2 && strcmp(argv[0], "-d") == 0) {
114		ret = parse_files_in_dir(argv[1]);
115		exit(ret);
116	}
117	if (argc == 2 && strcmp(argv[0], "-f") == 0) {
118		ret = parse_file(argv[1]);
119		exit(ret);
120	}
121	if (argc == 3) {
122		ret = parse_params(argv[0], argv[1], argv[2], NULL, NULL, -1);
123		exit(ret);
124	}
125	if (argc == 4) {
126		ret = parse_params(argv[0], argv[1], argv[2], argv[3],
127		    NULL, -1);
128		exit(ret);
129	}
130	usage();
131	exit(1);
132	/* NOTREACHED */
133}
134
135static void
136usage(void)
137{
138	fprintf(stderr, gettext(
139	    "Usage:	soconfig -d <dir>\n"
140	    "\tsoconfig -f <file>\n"
141	    "\tsoconfig <fam> <type> <protocol> <path|module>\n"
142	    "\tsoconfig <fam> <type> <protocol>\n"
143	    "\tsoconfig -l\n"));
144}
145
146/*
147 * Parse all files in the given directory.
148 */
149static int
150parse_files_in_dir(const char *dirname)
151{
152	DIR		*dp;
153	struct dirent 	*dirp;
154	struct stat	stats;
155	char		buf[MAXPATHLEN];
156
157	if ((dp = opendir(dirname)) == NULL) {
158		fprintf(stderr, gettext("failed to open directory '%s': %s\n"),
159		    dirname, strerror(errno));
160		return (1);
161	}
162
163	while ((dirp = readdir(dp)) != NULL) {
164		if (dirp->d_name[0] == '.')
165			continue;
166
167		if (snprintf(buf, sizeof (buf), "%s/%s", dirname,
168		    dirp->d_name) >= sizeof (buf)) {
169			fprintf(stderr,
170			    gettext("path name is too long: %s/%s\n"),
171			    dirname, dirp->d_name);
172			continue;
173		}
174		if (stat(buf, &stats) == -1) {
175			fprintf(stderr,
176			    gettext("failed to stat '%s': %s\n"), buf,
177			    strerror(errno));
178			continue;
179		}
180		if (!S_ISREG(stats.st_mode))
181			continue;
182
183		(void) parse_file(buf);
184	}
185
186	closedir(dp);
187
188	return (0);
189}
190
191/*
192 * Open the specified file and parse each line. Skip comments (everything
193 * after a '#'). Return 1 if at least one error was encountered; otherwise 0.
194 */
195static int
196parse_file(char *filename)
197{
198	char line[MAXLINELEN];
199	char pline[MAXLINELEN];
200	int argcount;
201	char *argvec[20];
202	FILE *fp;
203	int linecount = 0;
204	int numerror = 0;
205
206	fp = fopen(filename, "r");
207	if (fp == NULL) {
208		perror("soconfig: open");
209		fprintf(stderr, "\n");
210		usage();
211		return (1);
212	}
213
214	while (fgets(line, sizeof (line) - 1, fp) != NULL) {
215		linecount++;
216		strcpy(pline, line);
217		argcount = split_line(pline, argvec,
218		    sizeof (argvec) / sizeof (argvec[0]));
219#ifdef DEBUG
220		{
221			int i;
222
223			printf("scanned %d args\n", argcount);
224			for (i = 0; i < argcount; i++)
225				printf("arg[%d]: %s\n", i, argvec[i]);
226		}
227#endif /* DEBUG */
228		switch (argcount) {
229		case 0:
230			/* Empty line - or comment only line */
231			break;
232		case 3:
233			numerror += parse_params(argvec[0], argvec[1],
234			    argvec[2], NULL, filename, linecount);
235			break;
236		case 4:
237			numerror += parse_params(argvec[0], argvec[1],
238			    argvec[2], argvec[3], filename, linecount);
239			break;
240		default:
241			numerror++;
242			fprintf(stderr,
243			    gettext("Malformed line: <%s>\n"), line);
244			fprintf(stderr,
245			    gettext("\ton line %d in %s\n"), linecount,
246			    filename);
247			break;
248		}
249	}
250	(void) fclose(fp);
251
252	if (numerror > 0)
253		return (1);
254	else
255		return (0);
256}
257
258/*
259 * Parse a line splitting it off at whitspace characters.
260 * Modifies the content of the string by inserting NULLs.
261 */
262static int
263split_line(char *line, char *argvec[], int maxargvec)
264{
265	int i = 0;
266	char *cp;
267
268	/* Truncate at the beginning of a comment */
269	cp = strchr(line, '#');
270	if (cp != NULL)
271		*cp = '\0';
272
273	/* CONSTCOND */
274	while (1) {
275		/* Skip any whitespace */
276		while (isspace(*line))
277			line++;
278
279		if (i >= maxargvec)
280			return (i);
281
282		argvec[i] = line;
283		if (*line == '\0')
284			return (i);
285		i++;
286		/* Skip until next whitespace */
287		while (!isspace(*line) && *line != '\0')
288			line++;
289		if (*line != '\0') {
290			/* Break off argument */
291			*line++ = '\0';
292		}
293	}
294	/* NOTREACHED */
295}
296
297/*
298 * Parse the set of parameters and issues the sockconfig syscall.
299 * If line is not -1 it is assumed to be the line number in the file.
300 */
301static int
302parse_params(char *famstr, char *typestr, char *protostr, char *path,
303    const char *file, int line)
304{
305	int cmd, fam, type, protocol;
306
307	fam = parse_int(famstr);
308	if (fam == -1) {
309		fprintf(stderr, gettext("Bad family number: %s\n"), famstr);
310		if (line != -1)
311			fprintf(stderr,
312			    gettext("\ton line %d in %s\n"), line, file);
313		else {
314			fprintf(stderr, "\n");
315			usage();
316		}
317		return (1);
318	}
319
320	type = parse_int(typestr);
321	if (type == -1) {
322		fprintf(stderr,
323		    gettext("Bad socket type number: %s\n"), typestr);
324		if (line != -1)
325			fprintf(stderr,
326			    gettext("\ton line %d in %s\n"), line, file);
327		else {
328			fprintf(stderr, "\n");
329			usage();
330		}
331		return (1);
332	}
333
334	protocol = parse_int(protostr);
335	if (protocol == -1) {
336		fprintf(stderr,
337		    gettext("Bad protocol number: %s\n"), protostr);
338		if (line != -1)
339			fprintf(stderr,
340			    gettext("\ton line %d in %s\n"), line, file);
341		else {
342			fprintf(stderr, "\n");
343			usage();
344		}
345		return (1);
346	}
347
348
349	if (path != NULL) {
350		struct stat stats;
351
352		if (strncmp(path, "/dev", strlen("/dev")) == 0 &&
353		    stat(path, &stats) == -1) {
354			perror(path);
355			if (line != -1)
356				fprintf(stderr,
357				    gettext("\ton line %d in %s\n"), line,
358				    file);
359			else {
360				fprintf(stderr, "\n");
361				usage();
362			}
363			return (1);
364		}
365
366		cmd = SOCKCONFIG_ADD_SOCK;
367	} else {
368		cmd = SOCKCONFIG_REMOVE_SOCK;
369	}
370
371#ifdef DEBUG
372	printf("not calling sockconfig(%d, %d, %d, %d, %s)\n",
373	    cmd, fam, type, protocol, path == NULL ? "(null)" : path);
374#else
375	if (_sockconfig(cmd, fam, type, protocol, path) == -1) {
376		char *s;
377
378		switch (errno) {
379		case EEXIST:
380			s = gettext("Mapping exists");
381			break;
382		default:
383			s = strerror(errno);
384			break;
385		}
386
387		fprintf(stderr,
388		    gettext("warning: socket configuration failed "
389		    "for family %d type %d protocol %d: %s\n"),
390		    fam, type, protocol, s);
391		if (line != -1) {
392			fprintf(stderr,
393			    gettext("\ton line %d in %s\n"), line, file);
394		}
395		return (1);
396	}
397#endif
398	return (0);
399}
400
401static int
402parse_int(char *str)
403{
404	char *end;
405	int res;
406
407	res = strtol(str, &end, 0);
408	if (end == str)
409		return (-1);
410	return (res);
411}
412
413/*
414 * Add and remove socket filters.
415 */
416static int
417parse_filter_params(int argc, char **argv)
418{
419	struct sockconfig_filter_props filprop;
420	sof_socktuple_t *socktuples;
421	size_t tupcnt, nalloc;
422	char *hintarg, *socktup, *tupstr;
423	int i;
424
425	if (argc == 1) {
426		if (_sockconfig(SOCKCONFIG_REMOVE_FILTER, argv[0], 0,
427		    0, 0) < 0) {
428			switch (errno) {
429			case ENXIO:
430				fprintf(stderr,
431				    gettext("socket filter is not configured "
432				    "'%s'\n"), argv[0]);
433				break;
434			default:
435				perror("sockconfig");
436				break;
437			}
438			return (1);
439		}
440		return (0);
441	}
442
443	if (argc < 4 || argc > 5)
444		return (1);
445
446
447	if (strlen(argv[1]) >= MODMAXNAMELEN) {
448		fprintf(stderr,
449		    gettext("invalid module name '%s': name too long\n"),
450		    argv[1]);
451		return (1);
452	}
453	filprop.sfp_modname = argv[1];
454
455	/* Check the attach semantics */
456	if (strcmp(argv[2], "auto") == 0) {
457		filprop.sfp_autoattach = B_TRUE;
458		if (argc == 5) {
459			/* placement hint */
460			if (strcmp(argv[3], "top") == 0) {
461				filprop.sfp_hint = SOF_HINT_TOP;
462			} else if (strcmp(argv[3], "bottom") == 0) {
463				filprop.sfp_hint = SOF_HINT_BOTTOM;
464			} else {
465				if (strncmp(argv[3], "before", 6) == 0) {
466					filprop.sfp_hint = SOF_HINT_BEFORE;
467				} else if (strncmp(argv[3], "after", 5) == 0) {
468					filprop.sfp_hint = SOF_HINT_AFTER;
469				} else {
470					fprintf(stderr,
471					    gettext("invalid placement hint "
472					    "'%s'\n"), argv[3]);
473					return (1);
474				}
475
476				hintarg = strchr(argv[3], ':');
477				if (hintarg == NULL ||
478				    (strlen(++hintarg) == 0) ||
479				    (strlen(hintarg) >= FILNAME_MAX)) {
480					fprintf(stderr,
481					    gettext("invalid placement hint "
482					    "argument '%s': name too long\n"),
483					    argv[3]);
484					return (1);
485				}
486
487				filprop.sfp_hintarg = hintarg;
488			}
489		} else {
490			filprop.sfp_hint = SOF_HINT_NONE;
491		}
492	} else if (strcmp(argv[2], "prog") == 0) {
493		filprop.sfp_autoattach = B_FALSE;
494		filprop.sfp_hint = SOF_HINT_NONE;
495		/* cannot specify placement hint for programmatic filter */
496		if (argc == 5) {
497			fprintf(stderr,
498			    gettext("placement hint specified for programmatic "
499			    "filter\n"));
500			return (1);
501		}
502	} else {
503		fprintf(stderr, gettext("invalid attach semantic '%s'\n"),
504		    argv[2]);
505		return (1);
506	}
507
508	/* parse the socket tuples */
509	nalloc = 4;
510	socktuples = calloc(nalloc, sizeof (sof_socktuple_t));
511	if (socktuples == NULL) {
512		perror("calloc");
513		return (1);
514	}
515
516	tupcnt = 0;
517	tupstr = argv[(argc == 4) ? 3 : 4];
518	while ((socktup = strsep(&tupstr, ",")) != NULL) {
519		int val;
520		char *valstr;
521
522		if (tupcnt == nalloc) {
523			sof_socktuple_t *new;
524
525			nalloc *= 2;
526			new = realloc(socktuples,
527			    nalloc * sizeof (sof_socktuple_t));
528			if (new == NULL) {
529				perror("realloc");
530				free(socktuples);
531				return (1);
532			}
533			socktuples = new;
534		}
535		i = 0;
536		while ((valstr = strsep(&socktup, ":")) != NULL && i < 3) {
537			val = parse_int(valstr);
538			if (val == -1) {
539				fprintf(stderr, gettext("bad socket tuple\n"));
540				free(socktuples);
541				return (1);
542			}
543			switch (i) {
544			case 0:	socktuples[tupcnt].sofst_family = val; break;
545			case 1:	socktuples[tupcnt].sofst_type = val; break;
546			case 2:	socktuples[tupcnt].sofst_protocol = val; break;
547			}
548			i++;
549		}
550		if (i != 3) {
551			fprintf(stderr, gettext("bad socket tuple\n"));
552			free(socktuples);
553			return (1);
554		}
555		tupcnt++;
556	}
557	if (tupcnt == 0) {
558		fprintf(stderr, gettext("no socket tuples specified\n"));
559		free(socktuples);
560		return (1);
561	}
562	filprop.sfp_socktuple_cnt = tupcnt;
563	filprop.sfp_socktuple = socktuples;
564
565	if (_sockconfig(SOCKCONFIG_ADD_FILTER, argv[0], &filprop, 0, 0) < 0) {
566		switch (errno) {
567		case EINVAL:
568			fprintf(stderr,
569			    gettext("invalid socket filter configuration\n"));
570			break;
571		case EEXIST:
572			fprintf(stderr,
573			    gettext("socket filter is already configured "
574			    "'%s'\n"), argv[0]);
575			break;
576		case ENOSPC:
577			fprintf(stderr, gettext("unable to satisfy placement "
578			    "constraint\n"));
579			break;
580		default:
581			perror("sockconfig");
582			break;
583		}
584		free(socktuples);
585		return (1);
586	}
587	free(socktuples);
588	return (0);
589}
590
591/*
592 *  Print the in-kernel socket configuration table
593 */
594
595static int
596print_socktable()
597{
598	sockconfig_socktable_t sc_table;
599	int i;
600
601	(void) memset(&sc_table, 0, sizeof (sockconfig_socktable_t));
602
603	/* get number of entries */
604	if (_sockconfig(SOCKCONFIG_GET_SOCKTABLE, &sc_table) == -1) {
605		fprintf(stderr,
606		    gettext("cannot get in-kernel socket table: %s\n"),
607		    strerror(errno));
608		return (-1);
609	}
610	if (sc_table.num_of_entries == 0)
611		return (0);
612
613	sc_table.st_entries = calloc(sc_table.num_of_entries,
614	    sizeof (sockconfig_socktable_entry_t));
615	if (sc_table.st_entries == NULL) {
616		fprintf(stderr, gettext("out of memory\n"));
617		return (-1);
618	}
619
620	/* get socket table entries */
621	if (_sockconfig(SOCKCONFIG_GET_SOCKTABLE, &sc_table) == -1) {
622		fprintf(stderr,
623		    gettext("cannot get in-kernel socket table: %s\n"),
624		    strerror(errno));
625		return (-1);
626	}
627
628	printf("%6s %4s %5s %15s %15s %6s %6s\n",
629	    "FAMILY", "TYPE", "PROTO", "STRDEV", "SOCKMOD",
630	    "REFS", "FLAGS");
631	for (i = 0; i < sc_table.num_of_entries; i++) {
632		printf("%6u %4u %5u %15s %15s %6u %#6x\n",
633		    sc_table.st_entries[i].se_family,
634		    sc_table.st_entries[i].se_type,
635		    sc_table.st_entries[i].se_protocol,
636		    (strcmp(sc_table.st_entries[i].se_modname,
637		    "socktpi") == 0) ?
638		    sc_table.st_entries[i].se_strdev : "-",
639		    sc_table.st_entries[i].se_modname,
640		    sc_table.st_entries[i].se_refcnt,
641		    sc_table.st_entries[i].se_flags);
642	}
643	free(sc_table.st_entries);
644	return (0);
645}
646