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 
  71 static int      parse_files_in_dir(const char *dir);
  72 
  73 static int      parse_file(char *filename);
  74 
  75 static int      split_line(char *line, char *argvec[], int maxargvec);
  76 
  77 static int      parse_params(char *famstr, char *typestr, char *protostr,
  78                                 char *path, const char *file, int line);
  79 
  80 static int      parse_int(char *str);
  81 
  82 static void     usage(void);
  83 
  84 static int      parse_filter_params(int argc, char **argv);
  85 
  86 static int      print_socktable();
  87 
  88 int
  89 main(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 
 135 static void
 136 usage(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  */
 149 static int
 150 parse_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  */
 195 static int
 196 parse_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  */
 262 static int
 263 split_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 = NULL;
 272 
 273         /* CONSTCOND */
 274         while (1) {
 275                 /* Skip any whitespace */
 276                 while (isspace(*line) && *line != NULL)
 277                         line++;
 278 
 279                 if (i >= maxargvec)
 280                         return (i);
 281 
 282                 argvec[i] = line;
 283                 if (*line == NULL)
 284                         return (i);
 285                 i++;
 286                 /* Skip until next whitespace */
 287                 while (!isspace(*line) && *line != NULL)
 288                         line++;
 289                 if (*line != NULL) {
 290                         /* Break off argument */
 291                         *line++ = NULL;
 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  */
 301 static int
 302 parse_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 
 401 static int
 402 parse_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  */
 416 static int
 417 parse_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 
 595 static int
 596 print_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 }