1 /*
   2  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
   3  * Use is subject to license terms.
   4  */
   5 /*
   6  * Copyright (c) 1983 Regents of the University of California.
   7  * All rights reserved.  The Berkeley software License Agreement
   8  * specifies the terms and conditions for redistribution.
   9  */
  10 
  11 /*
  12  * Ifparse splits up an ifconfig command line, and was written for use
  13  * with the networking boot scripts; see $SRC/cmd/svc/shell/net_include.sh
  14  *
  15  * Ifparse can extract selected parts of the ifconfig command line,
  16  * such as failover address configuration ("ifparse -f"), or everything
  17  * except failover address configuration ("ifparse -s").  By default,
  18  * all parts of the command line are extracted (equivalent to ("ifparse -fs").
  19  *
  20  * Examples:
  21  *
  22  * The command:
  23  *
  24  *      ifparse inet 1.2.3.4 up group two addif 1.2.3.5 up addif 1.2.3.6 up
  25  *
  26  * Produces the following on standard output:
  27  *
  28  *      set 1.2.3.4 up
  29  *      group two
  30  *      addif 1.2.3.5 up
  31  *      addif 1.2.3.6 up
  32  *
  33  * The optional "set" and "destination" keywords are added to make the
  34  * output easier to process by a script or another command.
  35  *
  36  * The command:
  37  *
  38  *      ifparse -f inet 1.2.3.4 -failover up group two addif 1.2.3.5 up
  39  *
  40  * Produces:
  41  *
  42  *      addif 1.2.3.5  up
  43  *
  44  * Only failover address configuration has been requested.  Address
  45  * 1.2.3.4 is a non-failover address, and so isn't output.
  46  *
  47  * The "failover" and "-failover" commands can occur several times for
  48  * a given logical interface.  Only the last one counts.  For example:
  49  *
  50  *      ifparse -f inet 1.2.3.4 -failover failover -failover failover up
  51  *
  52  * Produces:
  53  *
  54  *      set 1.2.3.4 -failover failover -failover failover up
  55  *
  56  * No attempt is made to clean up such "pathological" command lines, by
  57  * removing redundant "failover" and "-failover" commands.
  58  */
  59 
  60 #include        <sys/types.h>
  61 #include        <stdlib.h>
  62 #include        <stdio.h>
  63 #include        <string.h>
  64 #include        <assert.h>
  65 #include        <unistd.h>
  66 
  67 /*
  68  * Parser flags:
  69  *
  70  *      PARSEFIXED
  71  *              Command should only appear if non-failover commands
  72  *              are requested.
  73  *      PARSEMOVABLE
  74  *              Command should only appear if failover commands are
  75  *              requested.
  76  *      PARSENOW
  77  *              Don't buffer the command, dump it to output immediately.
  78  *      PARSEADD
  79  *              Indicates processing has moved on to additional
  80  *              logical interfaces.
  81  *              Dump the buffer to output and clear buffer contents.
  82  *      PARSESET
  83  *              The "set" and "destination" keywords are optional.
  84  *              This flag indicates that the next address not prefixed
  85  *              with a keyword will be a destination address.
  86  *      PARSELOG0
  87  *              Command not valid on additional logical interfaces.
  88  */
  89 
  90 #define PARSEFIXED      0x01
  91 #define PARSEMOVABLE    0x02
  92 #define PARSENOW        0x04
  93 #define PARSEADD        0x08
  94 #define PARSESET        0x10
  95 #define PARSELOG0       0x20
  96 
  97 typedef enum { AF_UNSPEC, AF_INET, AF_INET6, AF_ANY } ac_t;
  98 
  99 #define NEXTARG         (-1)    /* command takes an argument */
 100 #define OPTARG          (-2)    /* command takes an optional argument */
 101 
 102 #define END_OF_TABLE    (-1)
 103 
 104 /* Parsemode, the type of commands requested by the user. */
 105 int     parsemode = 0;
 106 
 107 /* Parsetype, the type of the command currently in the buffer. */
 108 int     parsetype = PARSEFIXED | PARSEMOVABLE;
 109 
 110 /* Parsebuf, pointer to the buffer. */
 111 char    *parsebuf = NULL;
 112 
 113 /* Parsebuflen, the size of the buffer area. */
 114 unsigned parsebuflen = 0;
 115 
 116 /* Parsedumplen, the amount of the buffer currently in use. */
 117 unsigned parsedumplen = 0;
 118 
 119 /*
 120  * Setaddr, used to decide whether an address without a keyword
 121  * prefix is a source or destination address.
 122  */
 123 boolean_t setaddr = _B_FALSE;
 124 
 125 /*
 126  * Some ifconfig commands are only valid on the first logical interface.
 127  * As soon as an "addif" command is seen, "addint" is set.
 128  */
 129 boolean_t addint = _B_FALSE;
 130 
 131 /*
 132  * The parser table is based on that in ifconfig.  A command may or
 133  * may not have an argument, as indicated by whether NEXTARG/OPTARG is
 134  * in the second column.  Some commands can only be used with certain
 135  * address families, as indicated in the third column.  The fourth column
 136  * contains flags that control parser action.
 137  *
 138  * Ifparse buffers logical interface configuration commands such as "set",
 139  * "netmask" and "broadcast".  This buffering continues until an "addif"
 140  * command is seen, at which point the buffer is emptied, and the process
 141  * starts again.
 142  *
 143  * Some commands do not relate to logical interface configuration and are
 144  * dumped to output as soon as they are seen, such as "group" and "standby".
 145  *
 146  */
 147 
 148 struct  cmd {
 149         char    *c_name;
 150         int     c_parameter;            /* NEXTARG means next argv */
 151         int     c_af;                   /* address family restrictions */
 152         int     c_parseflags;           /* parsing flags */
 153 } cmds[] = {
 154         { "up",                 0,              AF_ANY, 0 },
 155         { "down",               0,              AF_ANY, 0 },
 156         { "trailers",           0,              AF_ANY, PARSENOW },
 157         { "-trailers",          0,              AF_ANY, PARSENOW },
 158         { "arp",                0,              AF_INET, PARSENOW },
 159         { "-arp",               0,              AF_INET, PARSENOW },
 160         { "private",            0,              AF_ANY, 0 },
 161         { "-private",           0,              AF_ANY, 0 },
 162         { "router",             0,              AF_ANY, PARSELOG0 },
 163         { "-router",            0,              AF_ANY, PARSELOG0 },
 164         { "xmit",               0,              AF_ANY, 0 },
 165         { "-xmit",              0,              AF_ANY, 0 },
 166         { "-nud",               0,              AF_INET6, PARSENOW },
 167         { "nud",                0,              AF_INET6, PARSENOW },
 168         { "anycast",            0,              AF_ANY, 0 },
 169         { "-anycast",           0,              AF_ANY, 0 },
 170         { "local",              0,              AF_ANY, 0 },
 171         { "-local",             0,              AF_ANY, 0 },
 172         { "deprecated",         0,              AF_ANY, 0 },
 173         { "-deprecated",        0,              AF_ANY, 0 },
 174         { "preferred",          0,              AF_INET6, 0 },
 175         { "-preferred",         0,              AF_INET6, 0 },
 176         { "debug",              0,              AF_ANY, PARSENOW },
 177         { "verbose",            0,              AF_ANY, PARSENOW },
 178         { "netmask",            NEXTARG,        AF_INET, 0 },
 179         { "metric",             NEXTARG,        AF_ANY, 0 },
 180         { "mtu",                NEXTARG,        AF_ANY, 0 },
 181         { "index",              NEXTARG,        AF_ANY, PARSELOG0 },
 182         { "broadcast",          NEXTARG,        AF_INET, 0 },
 183         { "auto-revarp",        0,              AF_INET, PARSEFIXED},
 184         { "plumb",              0,              AF_ANY, PARSENOW },
 185         { "unplumb",            0,              AF_ANY, PARSENOW },
 186         { "ipmp",               0,              AF_ANY, PARSELOG0 },
 187         { "subnet",             NEXTARG,        AF_ANY, 0 },
 188         { "token",              NEXTARG,        AF_INET6, PARSELOG0 },
 189         { "tsrc",               NEXTARG,        AF_ANY, PARSELOG0 },
 190         { "tdst",               NEXTARG,        AF_ANY, PARSELOG0 },
 191         { "encr_auth_algs",     NEXTARG,        AF_ANY, PARSELOG0 },
 192         { "encr_algs",          NEXTARG,        AF_ANY, PARSELOG0 },
 193         { "auth_algs",          NEXTARG,        AF_ANY, PARSELOG0 },
 194         { "addif",              NEXTARG,        AF_ANY, PARSEADD },
 195         { "removeif",           NEXTARG,        AF_ANY, PARSELOG0 },
 196         { "modlist",            0,              AF_ANY, PARSENOW },
 197         { "modinsert",          NEXTARG,        AF_ANY, PARSENOW },
 198         { "modremove",          NEXTARG,        AF_ANY, PARSENOW },
 199         { "failover",           0,              AF_ANY, PARSEMOVABLE },
 200         { "-failover",          0,              AF_ANY, PARSEFIXED },
 201         { "standby",            0,              AF_ANY, PARSENOW },
 202         { "-standby",           0,              AF_ANY, PARSENOW },
 203         { "failed",             0,              AF_ANY, PARSENOW },
 204         { "-failed",            0,              AF_ANY, PARSENOW },
 205         { "group",              NEXTARG,        AF_ANY, PARSELOG0 },
 206         { "configinfo",         0,              AF_ANY, PARSENOW },
 207         { "encaplimit",         NEXTARG,        AF_ANY, PARSELOG0 },
 208         { "-encaplimit",        0,              AF_ANY, PARSELOG0 },
 209         { "thoplimit",          NEXTARG,        AF_ANY, PARSELOG0 },
 210         { "set",                NEXTARG,        AF_ANY, PARSESET },
 211         { "destination",        NEXTARG,        AF_ANY, 0 },
 212         { "zone",               NEXTARG,        AF_ANY, 0 },
 213         { "-zone",              0,              AF_ANY, 0 },
 214         { "all-zones",          0,              AF_ANY, 0 },
 215         { "ether",              OPTARG,         AF_ANY, PARSENOW },
 216         { "usesrc",             NEXTARG,        AF_ANY, PARSENOW },
 217         { 0 /* ether addr */,   0,              AF_UNSPEC, PARSELOG0 },
 218         { 0 /* set */,          0,              AF_ANY, PARSESET },
 219         { 0 /* destination */,  0,              AF_ANY, 0 },
 220         { 0,                    END_OF_TABLE,   END_OF_TABLE, END_OF_TABLE},
 221 };
 222 
 223 
 224 /* Known address families */
 225 struct afswtch {
 226         char *af_name;
 227         short af_af;
 228 } afs[] = {
 229         { "inet",       AF_INET },
 230         { "ether",      AF_UNSPEC },
 231         { "inet6",      AF_INET6 },
 232         { 0,            0 }
 233 };
 234 
 235 /*
 236  * Append "item" to the buffer.  If there isn't enough room in the buffer,
 237  * expand it.
 238  */
 239 static void
 240 parse_append_buf(char *item)
 241 {
 242         unsigned itemlen;
 243         unsigned newdumplen;
 244 
 245         if (item == NULL)
 246                 return;
 247 
 248         itemlen = strlen(item);
 249         newdumplen = parsedumplen + itemlen;
 250 
 251         /* Expand dump buffer as needed */
 252         if (parsebuflen < newdumplen)  {
 253                 if ((parsebuf = realloc(parsebuf, newdumplen)) == NULL) {
 254                         perror("ifparse");
 255                         exit(1);
 256                 }
 257                 parsebuflen = newdumplen;
 258         }
 259         (void) memcpy(parsebuf + parsedumplen, item, itemlen);
 260 
 261         parsedumplen = newdumplen;
 262 }
 263 
 264 /*
 265  * Dump the buffer to output.
 266  */
 267 static void
 268 parse_dump_buf(void)
 269 {
 270         /*
 271          * When parsing, a set or addif command,  we may be some way into
 272          * the command before we definitely know it is movable or fixed.
 273          * If we get to the end of the command, and haven't seen a
 274          * "failover" or "-failover" flag, the command is movable.
 275          */
 276         if (!((parsemode == PARSEFIXED) && (parsetype & PARSEMOVABLE) != 0) &&
 277             (parsemode & parsetype) != 0 && parsedumplen != 0) {
 278                 unsigned i;
 279 
 280                 if (parsebuf[parsedumplen] == ' ')
 281                         parsedumplen--;
 282 
 283                 for (i = 0; i < parsedumplen; i++)
 284                         (void) putchar(parsebuf[i]);
 285 
 286                 (void) putchar('\n');
 287         }
 288         /* The buffer is kept in case there is more parsing to do */
 289         parsedumplen = 0;
 290         parsetype = PARSEFIXED | PARSEMOVABLE;
 291 }
 292 
 293 /*
 294  * Process a command.  The command will either be put in the buffer,
 295  * or dumped directly to output.  The current contents of the buffer
 296  * may be dumped to output.
 297  *
 298  * The buffer holds commands relating to a particular logical interface.
 299  * For example, "set", "destination", "failover", "broadcast", all relate
 300  * to a particular interface.  Such commands have to be buffered until
 301  * all the "failover" and "-failover" commands for that interface have
 302  * been seen, only then will we know whether the command is movable
 303  * or not.  When the "addif" command is seen, we know we are about to
 304  * start processing a new logical interface, we've seen all the
 305  * "failover" and "-failover" commands for the previous interface, and
 306  * can decide whether the buffer contents are movable or not.
 307  *
 308  */
 309 static void
 310 parsedump(char *cmd, int param, int flags, char *arg)
 311 {
 312         char *cmdname;  /* Command name */
 313         char *cmdarg;   /* Argument to command, if it takes one, or NULL */
 314 
 315         /*
 316          * Is command only valid on logical interface 0?
 317          * If processing commands on an additional logical interface, ignore
 318          * the command.
 319          * If processing commands on logical interface 0, don't buffer the
 320          * command, dump it straight to output.
 321          */
 322         if ((flags & PARSELOG0) != 0) {
 323                 if (addint)
 324                         return;
 325                 flags |= PARSENOW;
 326         }
 327 
 328         /*
 329          * If processing the "addif" command, a destination address may
 330          * follow without the "destination" prefix.  Add PARSESET to the
 331          * flags so that such an anonymous address is processed correctly.
 332          */
 333         if ((flags & PARSEADD) != 0) {
 334                 flags |= PARSESET;
 335                 addint = _B_TRUE;
 336         }
 337 
 338         /*
 339          * Commands that must be dumped straight to output are always fixed
 340          * (non-movable) commands.
 341          *
 342          */
 343         if ((flags & PARSENOW) != 0)
 344                 flags |= PARSEFIXED;
 345 
 346         /*
 347          * Source and destination addresses do not have to be prefixed
 348          * with the keywords "set" or "destination".  Ifparse always
 349          * inserts the optional keyword.
 350          */
 351         if (cmd == NULL) {
 352                 cmdarg = arg;
 353                 if ((flags & PARSESET) != 0)
 354                         cmdname = "set";
 355                 else if (setaddr) {
 356                         cmdname = "destination";
 357                         setaddr = _B_FALSE;
 358                 } else
 359                         cmdname = "";
 360         } else {
 361                 cmdarg = (param == 0) ? NULL : arg;
 362                 cmdname = cmd;
 363         }
 364 
 365         /*
 366          * The next address without a prefix will be a destination
 367          * address.
 368          */
 369         if ((flags & PARSESET) != 0)
 370                 setaddr = _B_TRUE;
 371 
 372         /*
 373          * Dump the command straight to output?
 374          * Only dump the command if the parse mode specified on
 375          * the command line matches the type of the command.
 376          */
 377         if ((flags & PARSENOW) != 0) {
 378                 if ((parsemode & flags) != 0)  {
 379                         (void) fputs(cmdname, stdout);
 380                         if (cmdarg != NULL) {
 381                                 (void) fputc(' ', stdout);
 382                                 (void) fputs(cmdarg, stdout);
 383                         }
 384                         (void) fputc('\n', stdout);
 385                 }
 386                 return;
 387         }
 388 
 389         /*
 390          * Only the commands relating to a particular logical interface
 391          * are buffered.  When an "addif" command is seen, processing is
 392          * about to start on a new logical interface, so dump the
 393          * buffer to output.
 394          */
 395         if ((flags & PARSEADD) != 0)
 396                 parse_dump_buf();
 397 
 398         /*
 399          * If the command flags indicate the command is fixed or
 400          * movable, update the type of the interface in the buffer
 401          * accordingly.  For example, "-failover" has the "PARSEFIXED"
 402          * flag, and the contents of the buffer are not movable if
 403          * "-failover" is seen.
 404          */
 405         if ((flags & PARSEFIXED) != 0)
 406                 parsetype &= ~PARSEMOVABLE;
 407 
 408         if ((flags & PARSEMOVABLE) != 0)
 409                 parsetype &= ~PARSEFIXED;
 410 
 411         parsetype |= flags & (PARSEFIXED | PARSEMOVABLE);
 412 
 413         parse_append_buf(cmdname);
 414 
 415         if (cmdarg != NULL) {
 416                 parse_append_buf(" ");
 417                 parse_append_buf(cmdarg);
 418         }
 419 
 420         parse_append_buf(" ");
 421 }
 422 
 423 /*
 424  * Parse the part of the command line following the address family
 425  * specification, if any.
 426  *
 427  * This function is a modified version of the function "ifconfig" in
 428  * ifconfig.c.
 429  */
 430 static int
 431 ifparse(int argc, char *argv[], struct afswtch *afp)
 432 {
 433         int af = afp->af_af;
 434 
 435         if (argc == 0)
 436                 return (0);
 437 
 438         if (strcmp(*argv, "auto-dhcp") == 0 || strcmp(*argv, "dhcp") == 0) {
 439                 if ((parsemode & PARSEFIXED) != NULL) {
 440                         while (argc) {
 441                                 (void) fputs(*argv++, stdout);
 442                                 if (--argc != 0)
 443                                         (void) fputc(' ', stdout);
 444                                 else
 445                                         (void) fputc('\n', stdout);
 446                         }
 447                 }
 448                 return (0);
 449         }
 450 
 451         while (argc > 0) {
 452                 struct cmd *p;
 453                 boolean_t found_cmd;
 454 
 455                 found_cmd = _B_FALSE;
 456                 for (p = cmds; ; p++) {
 457                         assert(p->c_parseflags != END_OF_TABLE);
 458                         if (p->c_name) {
 459                                 if (strcmp(*argv, p->c_name) == 0) {
 460                                         /*
 461                                          * indicate that the command was
 462                                          * found and check to see if
 463                                          * the address family is valid
 464                                          */
 465                                         found_cmd = _B_TRUE;
 466                                         if (p->c_af == AF_ANY ||
 467                                             af == p->c_af)
 468                                                 break;
 469                                 }
 470                         } else {
 471                                 if (p->c_af == AF_ANY ||
 472                                     af == p->c_af)
 473                                         break;
 474                         }
 475                 }
 476                 assert(p->c_parseflags != END_OF_TABLE);
 477                 /*
 478                  * If we found the keyword, but the address family
 479                  * did not match spit out an error
 480                  */
 481                 if (found_cmd && p->c_name == 0) {
 482                         (void) fprintf(stderr, "ifparse: Operation %s not"
 483                             " supported for %s\n", *argv, afp->af_name);
 484                         return (1);
 485                 }
 486                 /*
 487                  * else (no keyword found), we assume it's an address
 488                  * of some sort
 489                  */
 490                 if (p->c_name == 0 && setaddr) {
 491                         p++;    /* got src, do dst */
 492                         assert(p->c_parseflags != END_OF_TABLE);
 493                 }
 494 
 495                 if (p->c_parameter == NEXTARG || p->c_parameter == OPTARG) {
 496                         argc--, argv++;
 497                         if (argc == 0 && p->c_parameter == NEXTARG) {
 498                                 (void) fprintf(stderr,
 499                                     "ifparse: no argument for %s\n",
 500                                     p->c_name);
 501                                 return (1);
 502                         }
 503                 }
 504 
 505                 /*
 506                  *      Dump the command if:
 507                  *
 508                  *              there's no address family
 509                  *              restriction
 510                  *      OR
 511                  *              there is a restriction AND
 512                  *              the address families match
 513                  */
 514                 if ((p->c_af == AF_ANY)      || (af == p->c_af))
 515                         parsedump(p->c_name, p->c_parameter, p->c_parseflags,
 516                             *argv);
 517                 argc--, argv++;
 518         }
 519         parse_dump_buf();
 520 
 521         return (0);
 522 }
 523 
 524 /*
 525  * Print command usage on standard error.
 526  */
 527 static void
 528 usage(void)
 529 {
 530         (void) fprintf(stderr,
 531             "usage: ifparse [ -fs ] <addr_family> <commands>\n");
 532 }
 533 
 534 int
 535 main(int argc, char *argv[])
 536 {
 537         int c;
 538         struct afswtch *afp;
 539 
 540         while ((c = getopt(argc, argv, "fs")) != -1) {
 541                 switch ((char)c) {
 542                 case 'f':
 543                         parsemode |= PARSEMOVABLE;
 544                         break;
 545                 case 's':
 546                         parsemode |= PARSEFIXED;
 547                         break;
 548                 case '?':
 549                         usage();
 550                         exit(1);
 551                 }
 552         }
 553 
 554         if (parsemode == 0)
 555                 parsemode = PARSEFIXED | PARSEMOVABLE;
 556 
 557         argc -= optind;
 558         argv += optind;
 559 
 560         afp = afs;
 561         if (argc > 0) {
 562                 struct afswtch *aftp;
 563                 for (aftp = afs; aftp->af_name; aftp++) {
 564                         if (strcmp(aftp->af_name, *argv) == 0) {
 565                                 argc--; argv++;
 566                                 afp = aftp;
 567                                 break;
 568                         }
 569                 }
 570         }
 571 
 572         return (ifparse(argc, argv, afp));
 573 }