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