/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipsec_util.h" #include "ikedoor.h" /* * This file contains support functions that are shared by the ipsec * utilities including ipseckey(1m) and ikeadm(1m). */ /* Set standard default/initial values for globals... */ boolean_t pflag = B_FALSE; /* paranoid w.r.t. printing keying material */ boolean_t nflag = B_FALSE; /* avoid nameservice? */ boolean_t interactive = B_FALSE; /* util not running on cmdline */ boolean_t readfile = B_FALSE; /* cmds are being read from a file */ uint_t lineno = 0; /* track location if reading cmds from file */ uint_t lines_added = 0; uint_t lines_parsed = 0; jmp_buf env; /* for error recovery in interactive/readfile modes */ char *my_fmri = NULL; FILE *debugfile = stderr; /* * Print errno and exit if cmdline or readfile, reset state if interactive * The error string *what should be dgettext()'d before calling bail(). */ void bail(char *what) { if (errno != 0) warn(what); else warnx(dgettext(TEXT_DOMAIN, "Error: %s"), what); if (readfile) { return; } if (interactive && !readfile) longjmp(env, 2); EXIT_FATAL(NULL); } /* * Print caller-supplied variable-arg error msg, then exit if cmdline or * readfile, or reset state if interactive. */ /*PRINTFLIKE1*/ void bail_msg(char *fmt, ...) { va_list ap; char msgbuf[BUFSIZ]; va_start(ap, fmt); (void) vsnprintf(msgbuf, BUFSIZ, fmt, ap); va_end(ap); if (readfile) warnx(dgettext(TEXT_DOMAIN, "ERROR on line %u:\n%s\n"), lineno, msgbuf); else warnx(dgettext(TEXT_DOMAIN, "ERROR: %s\n"), msgbuf); if (interactive && !readfile) longjmp(env, 1); EXIT_FATAL(NULL); } /* * dump_XXX functions produce ASCII output from various structures. * * Because certain errors need to do this to stderr, dump_XXX functions * take a FILE pointer. * * If an error occured while writing to the specified file, these * functions return -1, zero otherwise. */ int dump_sockaddr(struct sockaddr *sa, uint8_t prefixlen, boolean_t addr_only, FILE *where) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; char *printable_addr, *protocol; uint8_t *addrptr; /* Add 4 chars to hold '/nnn' for prefixes. */ char storage[INET6_ADDRSTRLEN + 4]; uint16_t port; boolean_t unspec; struct hostent *hp; int getipnode_errno, addrlen; switch (sa->sa_family) { case AF_INET: /* LINTED E_BAD_PTR_CAST_ALIGN */ sin = (struct sockaddr_in *)sa; addrptr = (uint8_t *)&sin->sin_addr; port = sin->sin_port; protocol = "AF_INET"; unspec = (sin->sin_addr.s_addr == 0); addrlen = sizeof (sin->sin_addr); break; case AF_INET6: /* LINTED E_BAD_PTR_CAST_ALIGN */ sin6 = (struct sockaddr_in6 *)sa; addrptr = (uint8_t *)&sin6->sin6_addr; port = sin6->sin6_port; protocol = "AF_INET6"; unspec = IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr); addrlen = sizeof (sin6->sin6_addr); break; default: return (0); } if (inet_ntop(sa->sa_family, addrptr, storage, INET6_ADDRSTRLEN) == NULL) { printable_addr = dgettext(TEXT_DOMAIN, "Invalid IP address."); } else { char prefix[5]; /* "/nnn" with terminator. */ (void) snprintf(prefix, sizeof (prefix), "/%d", prefixlen); printable_addr = storage; if (prefixlen != 0) { (void) strlcat(printable_addr, prefix, sizeof (storage)); } } if (addr_only) { if (fprintf(where, "%s", printable_addr) < 0) return (-1); } else { if (fprintf(where, dgettext(TEXT_DOMAIN, "%s: port %d, %s"), protocol, ntohs(port), printable_addr) < 0) return (-1); if (!nflag) { /* * Do AF_independent reverse hostname lookup here. */ if (unspec) { if (fprintf(where, dgettext(TEXT_DOMAIN, " ")) < 0) return (-1); } else { hp = getipnodebyaddr((char *)addrptr, addrlen, sa->sa_family, &getipnode_errno); if (hp != NULL) { if (fprintf(where, " (%s)", hp->h_name) < 0) return (-1); freehostent(hp); } else { if (fprintf(where, dgettext(TEXT_DOMAIN, " ")) < 0) return (-1); } } } if (fputs(".\n", where) == EOF) return (-1); } return (0); } /* * Dump a key and bitlen */ int dump_key(uint8_t *keyp, uint_t bitlen, FILE *where) { int numbytes; numbytes = SADB_1TO8(bitlen); /* The & 0x7 is to check for leftover bits. */ if ((bitlen & 0x7) != 0) numbytes++; while (numbytes-- != 0) { if (pflag) { /* Print no keys if paranoid */ if (fprintf(where, "XX") < 0) return (-1); } else { if (fprintf(where, "%02x", *keyp++) < 0) return (-1); } } if (fprintf(where, "/%u", bitlen) < 0) return (-1); return (0); } /* * Print an authentication or encryption algorithm */ static int dump_generic_alg(uint8_t alg_num, int proto_num, FILE *where) { struct ipsecalgent *alg; alg = getipsecalgbynum(alg_num, proto_num, NULL); if (alg == NULL) { if (fprintf(where, dgettext(TEXT_DOMAIN, ""), alg_num) < 0) return (-1); return (0); } /* * Special-case for backward output compat. * Assume that SADB_AALG_NONE == SADB_EALG_NONE. */ if (alg_num == SADB_AALG_NONE) { if (fputs(dgettext(TEXT_DOMAIN, ""), where) == EOF) return (-1); } else { if (fputs(alg->a_names[0], where) == EOF) return (-1); } freeipsecalgent(alg); return (0); } int dump_aalg(uint8_t aalg, FILE *where) { return (dump_generic_alg(aalg, IPSEC_PROTO_AH, where)); } int dump_ealg(uint8_t ealg, FILE *where) { return (dump_generic_alg(ealg, IPSEC_PROTO_ESP, where)); } /* * Print an SADB_IDENTTYPE string * * Also return TRUE if the actual ident may be printed, FALSE if not. * * If rc is not NULL, set its value to -1 if an error occured while writing * to the specified file, zero otherwise. */ boolean_t dump_sadb_idtype(uint8_t idtype, FILE *where, int *rc) { boolean_t canprint = B_TRUE; int rc_val = 0; switch (idtype) { case SADB_IDENTTYPE_PREFIX: if (fputs(dgettext(TEXT_DOMAIN, "prefix"), where) == EOF) rc_val = -1; break; case SADB_IDENTTYPE_FQDN: if (fputs(dgettext(TEXT_DOMAIN, "FQDN"), where) == EOF) rc_val = -1; break; case SADB_IDENTTYPE_USER_FQDN: if (fputs(dgettext(TEXT_DOMAIN, "user-FQDN (mbox)"), where) == EOF) rc_val = -1; break; case SADB_X_IDENTTYPE_DN: if (fputs(dgettext(TEXT_DOMAIN, "ASN.1 DER Distinguished Name"), where) == EOF) rc_val = -1; canprint = B_FALSE; break; case SADB_X_IDENTTYPE_GN: if (fputs(dgettext(TEXT_DOMAIN, "ASN.1 DER Generic Name"), where) == EOF) rc_val = -1; canprint = B_FALSE; break; case SADB_X_IDENTTYPE_KEY_ID: if (fputs(dgettext(TEXT_DOMAIN, "Generic key id"), where) == EOF) rc_val = -1; break; case SADB_X_IDENTTYPE_ADDR_RANGE: if (fputs(dgettext(TEXT_DOMAIN, "Address range"), where) == EOF) rc_val = -1; break; default: if (fprintf(where, dgettext(TEXT_DOMAIN, ""), idtype) < 0) rc_val = -1; break; } if (rc != NULL) *rc = rc_val; return (canprint); } /* * Slice an argv/argc vector from an interactive line or a read-file line. */ static int create_argv(char *ibuf, int *newargc, char ***thisargv) { unsigned int argvlen = START_ARG; char **current; boolean_t firstchar = B_TRUE; boolean_t inquotes = B_FALSE; *thisargv = malloc(sizeof (char *) * argvlen); if ((*thisargv) == NULL) return (MEMORY_ALLOCATION); current = *thisargv; *current = NULL; for (; *ibuf != '\0'; ibuf++) { if (isspace(*ibuf)) { if (inquotes) { continue; } if (*current != NULL) { *ibuf = '\0'; current++; if (*thisargv + argvlen == current) { /* Regrow ***thisargv. */ if (argvlen == TOO_MANY_ARGS) { free(*thisargv); return (TOO_MANY_TOKENS); } /* Double the allocation. */ current = realloc(*thisargv, sizeof (char *) * (argvlen << 1)); if (current == NULL) { free(*thisargv); return (MEMORY_ALLOCATION); } *thisargv = current; current += argvlen; argvlen <<= 1; /* Double the size. */ } *current = NULL; } } else { if (firstchar) { firstchar = B_FALSE; if (*ibuf == COMMENT_CHAR || *ibuf == '\n') { free(*thisargv); return (COMMENT_LINE); } } if (*ibuf == QUOTE_CHAR) { if (inquotes) { inquotes = B_FALSE; *ibuf = '\0'; } else { inquotes = B_TRUE; } continue; } if (*current == NULL) { *current = ibuf; (*newargc)++; } } } /* * Tricky corner case... * I've parsed _exactly_ the amount of args as I have space. It * won't return NULL-terminated, and bad things will happen to * the caller. */ if (argvlen == *newargc) { current = realloc(*thisargv, sizeof (char *) * (argvlen + 1)); if (current == NULL) { free(*thisargv); return (MEMORY_ALLOCATION); } *thisargv = current; current[argvlen] = NULL; } return (SUCCESS); } /* * Enter a mode where commands are read from a file. Treat stdin special. */ void do_interactive(FILE *infile, char *configfile, char *promptstring, char *my_fmri, parse_cmdln_fn parseit) { char ibuf[IBUF_SIZE], holder[IBUF_SIZE]; char *hptr, **thisargv, *ebuf; int thisargc; boolean_t continue_in_progress = B_FALSE; (void) setjmp(env); ebuf = NULL; interactive = B_TRUE; bzero(ibuf, IBUF_SIZE); if (infile == stdin) { (void) printf("%s", promptstring); (void) fflush(stdout); } else { readfile = B_TRUE; } while (fgets(ibuf, IBUF_SIZE, infile) != NULL) { if (readfile) lineno++; thisargc = 0; thisargv = NULL; /* * Check byte IBUF_SIZE - 2, because byte IBUF_SIZE - 1 will * be null-terminated because of fgets(). */ if (ibuf[IBUF_SIZE - 2] != '\0') { ipsecutil_exit(SERVICE_FATAL, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "Line %d too big."), lineno); } if (!continue_in_progress) { /* Use -2 because of \n from fgets. */ if (ibuf[strlen(ibuf) - 2] == CONT_CHAR) { /* * Can use strcpy here, I've checked the * length already. */ (void) strcpy(holder, ibuf); hptr = &(holder[strlen(holder)]); /* Remove the CONT_CHAR from the string. */ hptr[-2] = ' '; continue_in_progress = B_TRUE; bzero(ibuf, IBUF_SIZE); continue; } } else { /* Handle continuations... */ (void) strncpy(hptr, ibuf, (size_t)(&(holder[IBUF_SIZE]) - hptr)); if (holder[IBUF_SIZE - 1] != '\0') { ipsecutil_exit(SERVICE_FATAL, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "Command buffer overrun.")); } /* Use - 2 because of \n from fgets. */ if (hptr[strlen(hptr) - 2] == CONT_CHAR) { bzero(ibuf, IBUF_SIZE); hptr += strlen(hptr); /* Remove the CONT_CHAR from the string. */ hptr[-2] = ' '; continue; } else { continue_in_progress = B_FALSE; /* * I've already checked the length... */ (void) strcpy(ibuf, holder); } } /* * Just in case the command fails keep a copy of the * command buffer for diagnostic output. */ if (readfile) { /* * The error buffer needs to be big enough to * hold the longest command string, plus * some extra text, see below. */ ebuf = calloc((IBUF_SIZE * 2), sizeof (char)); if (ebuf == NULL) { ipsecutil_exit(SERVICE_FATAL, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "Memory allocation error.")); } else { (void) snprintf(ebuf, (IBUF_SIZE * 2), dgettext(TEXT_DOMAIN, "Config file entry near line %u " "caused error(s) or warnings:\n\n%s\n\n"), lineno, ibuf); } } switch (create_argv(ibuf, &thisargc, &thisargv)) { case TOO_MANY_TOKENS: ipsecutil_exit(SERVICE_BADCONF, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "Too many input tokens.")); break; case MEMORY_ALLOCATION: ipsecutil_exit(SERVICE_BADCONF, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "Memory allocation error.")); break; case COMMENT_LINE: /* Comment line. */ free(ebuf); break; default: if (thisargc != 0) { lines_parsed++; /* ebuf consumed */ parseit(thisargc, thisargv, ebuf, readfile); } else { free(ebuf); } free(thisargv); if (infile == stdin) { (void) printf("%s", promptstring); (void) fflush(stdout); } break; } bzero(ibuf, IBUF_SIZE); } /* * The following code is ipseckey specific. This should never be * used by ikeadm which also calls this function because ikeadm * only runs interactively. If this ever changes this code block * sould be revisited. */ if (readfile) { if (lines_parsed != 0 && lines_added == 0) { ipsecutil_exit(SERVICE_BADCONF, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "Configuration file did not " "contain any valid SAs")); } /* * There were errors. Putting the service in maintenance mode. * When svc.startd(1M) allows services to degrade themselves, * this should be revisited. * * If this function was called from a program running as a * smf_method(5), print a warning message. Don't spew out the * errors as these will end up in the smf(5) log file which is * publically readable, the errors may contain sensitive * information. */ if ((lines_added < lines_parsed) && (configfile != NULL)) { if (my_fmri != NULL) { ipsecutil_exit(SERVICE_BADCONF, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "The configuration file contained %d " "errors.\n" "Manually check the configuration with:\n" "ipseckey -c %s\n" "Use svcadm(1M) to clear maintenance " "condition when errors are resolved.\n"), lines_parsed - lines_added, configfile); } else { EXIT_BADCONFIG(NULL); } } else { if (my_fmri != NULL) ipsecutil_exit(SERVICE_EXIT_OK, my_fmri, debugfile, dgettext(TEXT_DOMAIN, "%d actions successfully processed."), lines_added); } } else { (void) putchar('\n'); (void) fflush(stdout); } EXIT_OK(NULL); } /* * Functions to parse strings that represent a debug or privilege level. * These functions are copied from main.c and door.c in usr.lib/in.iked/common. * If this file evolves into a common library that may be used by in.iked * as well as the usr.sbin utilities, those duplicate functions should be * deleted. * * A privilege level may be represented by a simple keyword, corresponding * to one of the possible levels. A debug level may be represented by a * series of keywords, separated by '+' or '-', indicating categories to * be added or removed from the set of categories in the debug level. * For example, +all-op corresponds to level 0xfffffffb (all flags except * for D_OP set); while p1+p2+pfkey corresponds to level 0x38. Note that * the leading '+' is implicit; the first keyword in the list must be for * a category that is to be added. * * These parsing functions make use of a local version of strtok, strtok_d, * which includes an additional parameter, char *delim. This param is filled * in with the character which ends the returned token. In other words, * this version of strtok, in addition to returning the token, also returns * the single character delimiter from the original string which marked the * end of the token. */ static char * strtok_d(char *string, const char *sepset, char *delim) { static char *lasts; char *q, *r; /* first or subsequent call */ if (string == NULL) string = lasts; if (string == 0) /* return if no tokens remaining */ return (NULL); q = string + strspn(string, sepset); /* skip leading separators */ if (*q == '\0') /* return if no tokens remaining */ return (NULL); if ((r = strpbrk(q, sepset)) == NULL) { /* move past token */ lasts = 0; /* indicate that this is last token */ } else { *delim = *r; /* save delimitor */ *r = '\0'; lasts = r + 1; } return (q); } static keywdtab_t privtab[] = { { IKE_PRIV_MINIMUM, "base" }, { IKE_PRIV_MODKEYS, "modkeys" }, { IKE_PRIV_KEYMAT, "keymat" }, { IKE_PRIV_MINIMUM, "0" }, }; int privstr2num(char *str) { keywdtab_t *pp; char *endp; int priv; for (pp = privtab; pp < A_END(privtab); pp++) { if (strcasecmp(str, pp->kw_str) == 0) return (pp->kw_tag); } priv = strtol(str, &endp, 0); if (*endp == '\0') return (priv); return (-1); } static keywdtab_t dbgtab[] = { { D_CERT, "cert" }, { D_KEY, "key" }, { D_OP, "op" }, { D_P1, "p1" }, { D_P1, "phase1" }, { D_P2, "p2" }, { D_P2, "phase2" }, { D_PFKEY, "pfkey" }, { D_POL, "pol" }, { D_POL, "policy" }, { D_PROP, "prop" }, { D_DOOR, "door" }, { D_CONFIG, "config" }, { D_ALL, "all" }, { 0, "0" }, }; int dbgstr2num(char *str) { keywdtab_t *dp; for (dp = dbgtab; dp < A_END(dbgtab); dp++) { if (strcasecmp(str, dp->kw_str) == 0) return (dp->kw_tag); } return (D_INVALID); } int parsedbgopts(char *optarg) { char *argp, *endp, op, nextop; int mask = 0, new; mask = strtol(optarg, &endp, 0); if (*endp == '\0') return (mask); op = optarg[0]; if (op != '-') op = '+'; argp = strtok_d(optarg, "+-", &nextop); do { new = dbgstr2num(argp); if (new == D_INVALID) { /* we encountered an invalid keywd */ return (new); } if (op == '+') { mask |= new; } else { mask &= ~new; } op = nextop; } while ((argp = strtok_d(NULL, "+-", &nextop)) != NULL); return (mask); } /* * functions to manipulate the kmcookie-label mapping file */ /* * Open, lockf, fdopen the given file, returning a FILE * on success, * or NULL on failure. */ FILE * kmc_open_and_lock(char *name) { int fd, rtnerr; FILE *fp; if ((fd = open(name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) { return (NULL); } if (lockf(fd, F_LOCK, 0) < 0) { return (NULL); } if ((fp = fdopen(fd, "a+")) == NULL) { return (NULL); } if (fseek(fp, 0, SEEK_SET) < 0) { /* save errno in case fclose changes it */ rtnerr = errno; (void) fclose(fp); errno = rtnerr; return (NULL); } return (fp); } /* * Extract an integer cookie and string label from a line from the * kmcookie-label file. Return -1 on failure, 0 on success. */ int kmc_parse_line(char *line, int *cookie, char **label) { char *cookiestr; *cookie = 0; *label = NULL; cookiestr = strtok(line, " \t\n"); if (cookiestr == NULL) { return (-1); } /* Everything that follows, up to the newline, is the label. */ *label = strtok(NULL, "\n"); if (*label == NULL) { return (-1); } *cookie = atoi(cookiestr); return (0); } /* * Insert a mapping into the file (if it's not already there), given the * new label. Return the assigned cookie, or -1 on error. */ int kmc_insert_mapping(char *label) { FILE *map; char linebuf[MAXLINESIZE]; char *cur_label; int max_cookie = 0, cur_cookie, rtn_cookie; int rtnerr = 0; boolean_t found = B_FALSE; /* open and lock the file; will sleep until lock is available */ if ((map = kmc_open_and_lock(KMCFILE)) == NULL) { /* kmc_open_and_lock() sets errno appropriately */ return (-1); } while (fgets(linebuf, sizeof (linebuf), map) != NULL) { if (kmc_parse_line(linebuf, &cur_cookie, &cur_label) < 0) { rtnerr = EINVAL; goto error; } if (cur_cookie > max_cookie) max_cookie = cur_cookie; if ((!found) && (strcmp(cur_label, label) == 0)) { found = B_TRUE; rtn_cookie = cur_cookie; } } if (!found) { rtn_cookie = ++max_cookie; if ((fprintf(map, "%u\t%s\n", rtn_cookie, label) < 0) || (fflush(map) < 0)) { rtnerr = errno; goto error; } } (void) fclose(map); return (rtn_cookie); error: (void) fclose(map); errno = rtnerr; return (-1); } /* * Lookup the given cookie and return its corresponding label. Return * a pointer to the label on success, NULL on error (or if the label is * not found). Note that the returned label pointer points to a static * string, so the label will be overwritten by a subsequent call to the * function; the function is also not thread-safe as a result. */ char * kmc_lookup_by_cookie(int cookie) { FILE *map; static char linebuf[MAXLINESIZE]; char *cur_label; int cur_cookie; if ((map = kmc_open_and_lock(KMCFILE)) == NULL) { return (NULL); } while (fgets(linebuf, sizeof (linebuf), map) != NULL) { if (kmc_parse_line(linebuf, &cur_cookie, &cur_label) < 0) { (void) fclose(map); return (NULL); } if (cookie == cur_cookie) { (void) fclose(map); return (cur_label); } } (void) fclose(map); return (NULL); } /* * Parse basic extension headers and return in the passed-in pointer vector. * Return values include: * * KGE_OK Everything's nice and parsed out. * If there are no extensions, place NULL in extv[0]. * KGE_DUP There is a duplicate extension. * First instance in appropriate bin. First duplicate in * extv[0]. * KGE_UNK Unknown extension type encountered. extv[0] contains * unknown header. * KGE_LEN Extension length error. * KGE_CHK High-level reality check failed on specific extension. * * My apologies for some of the pointer arithmetic in here. I'm thinking * like an assembly programmer, yet trying to make the compiler happy. */ int spdsock_get_ext(spd_ext_t *extv[], spd_msg_t *basehdr, uint_t msgsize, char *diag_buf, uint_t diag_buf_len) { int i; if (diag_buf != NULL) diag_buf[0] = '\0'; for (i = 1; i <= SPD_EXT_MAX; i++) extv[i] = NULL; i = 0; /* Use extv[0] as the "current working pointer". */ extv[0] = (spd_ext_t *)(basehdr + 1); msgsize = SPD_64TO8(msgsize); while ((char *)extv[0] < ((char *)basehdr + msgsize)) { /* Check for unknown headers. */ i++; if (extv[0]->spd_ext_type == 0 || extv[0]->spd_ext_type > SPD_EXT_MAX) { if (diag_buf != NULL) { (void) snprintf(diag_buf, diag_buf_len, "spdsock ext 0x%X unknown: 0x%X", i, extv[0]->spd_ext_type); } return (KGE_UNK); } /* * Check length. Use uint64_t because extlen is in units * of 64-bit words. If length goes beyond the msgsize, * return an error. (Zero length also qualifies here.) */ if (extv[0]->spd_ext_len == 0 || (uint8_t *)((uint64_t *)extv[0] + extv[0]->spd_ext_len) > (uint8_t *)((uint8_t *)basehdr + msgsize)) return (KGE_LEN); /* Check for redundant headers. */ if (extv[extv[0]->spd_ext_type] != NULL) return (KGE_DUP); /* If I make it here, assign the appropriate bin. */ extv[extv[0]->spd_ext_type] = extv[0]; /* Advance pointer (See above for uint64_t ptr reasoning.) */ extv[0] = (spd_ext_t *) ((uint64_t *)extv[0] + extv[0]->spd_ext_len); } /* Everything's cool. */ /* * If extv[0] == NULL, then there are no extension headers in this * message. Ensure that this is the case. */ if (extv[0] == (spd_ext_t *)(basehdr + 1)) extv[0] = NULL; return (KGE_OK); } const char * spdsock_diag(int diagnostic) { switch (diagnostic) { case SPD_DIAGNOSTIC_NONE: return (dgettext(TEXT_DOMAIN, "no error")); case SPD_DIAGNOSTIC_UNKNOWN_EXT: return (dgettext(TEXT_DOMAIN, "unknown extension")); case SPD_DIAGNOSTIC_BAD_EXTLEN: return (dgettext(TEXT_DOMAIN, "bad extension length")); case SPD_DIAGNOSTIC_NO_RULE_EXT: return (dgettext(TEXT_DOMAIN, "no rule extension")); case SPD_DIAGNOSTIC_BAD_ADDR_LEN: return (dgettext(TEXT_DOMAIN, "bad address len")); case SPD_DIAGNOSTIC_MIXED_AF: return (dgettext(TEXT_DOMAIN, "mixed address family")); case SPD_DIAGNOSTIC_ADD_NO_MEM: return (dgettext(TEXT_DOMAIN, "add: no memory")); case SPD_DIAGNOSTIC_ADD_WRONG_ACT_COUNT: return (dgettext(TEXT_DOMAIN, "add: wrong action count")); case SPD_DIAGNOSTIC_ADD_BAD_TYPE: return (dgettext(TEXT_DOMAIN, "add: bad type")); case SPD_DIAGNOSTIC_ADD_BAD_FLAGS: return (dgettext(TEXT_DOMAIN, "add: bad flags")); case SPD_DIAGNOSTIC_ADD_INCON_FLAGS: return (dgettext(TEXT_DOMAIN, "add: inconsistent flags")); case SPD_DIAGNOSTIC_MALFORMED_LCLPORT: return (dgettext(TEXT_DOMAIN, "malformed local port")); case SPD_DIAGNOSTIC_DUPLICATE_LCLPORT: return (dgettext(TEXT_DOMAIN, "duplicate local port")); case SPD_DIAGNOSTIC_MALFORMED_REMPORT: return (dgettext(TEXT_DOMAIN, "malformed remote port")); case SPD_DIAGNOSTIC_DUPLICATE_REMPORT: return (dgettext(TEXT_DOMAIN, "duplicate remote port")); case SPD_DIAGNOSTIC_MALFORMED_PROTO: return (dgettext(TEXT_DOMAIN, "malformed proto")); case SPD_DIAGNOSTIC_DUPLICATE_PROTO: return (dgettext(TEXT_DOMAIN, "duplicate proto")); case SPD_DIAGNOSTIC_MALFORMED_LCLADDR: return (dgettext(TEXT_DOMAIN, "malformed local address")); case SPD_DIAGNOSTIC_DUPLICATE_LCLADDR: return (dgettext(TEXT_DOMAIN, "duplicate local address")); case SPD_DIAGNOSTIC_MALFORMED_REMADDR: return (dgettext(TEXT_DOMAIN, "malformed remote address")); case SPD_DIAGNOSTIC_DUPLICATE_REMADDR: return (dgettext(TEXT_DOMAIN, "duplicate remote address")); case SPD_DIAGNOSTIC_MALFORMED_ACTION: return (dgettext(TEXT_DOMAIN, "malformed action")); case SPD_DIAGNOSTIC_DUPLICATE_ACTION: return (dgettext(TEXT_DOMAIN, "duplicate action")); case SPD_DIAGNOSTIC_MALFORMED_RULE: return (dgettext(TEXT_DOMAIN, "malformed rule")); case SPD_DIAGNOSTIC_DUPLICATE_RULE: return (dgettext(TEXT_DOMAIN, "duplicate rule")); case SPD_DIAGNOSTIC_MALFORMED_RULESET: return (dgettext(TEXT_DOMAIN, "malformed ruleset")); case SPD_DIAGNOSTIC_DUPLICATE_RULESET: return (dgettext(TEXT_DOMAIN, "duplicate ruleset")); case SPD_DIAGNOSTIC_INVALID_RULE_INDEX: return (dgettext(TEXT_DOMAIN, "invalid rule index")); case SPD_DIAGNOSTIC_BAD_SPDID: return (dgettext(TEXT_DOMAIN, "bad spdid")); case SPD_DIAGNOSTIC_BAD_MSG_TYPE: return (dgettext(TEXT_DOMAIN, "bad message type")); case SPD_DIAGNOSTIC_UNSUPP_AH_ALG: return (dgettext(TEXT_DOMAIN, "unsupported AH algorithm")); case SPD_DIAGNOSTIC_UNSUPP_ESP_ENCR_ALG: return (dgettext(TEXT_DOMAIN, "unsupported ESP encryption algorithm")); case SPD_DIAGNOSTIC_UNSUPP_ESP_AUTH_ALG: return (dgettext(TEXT_DOMAIN, "unsupported ESP authentication algorithm")); case SPD_DIAGNOSTIC_UNSUPP_AH_KEYSIZE: return (dgettext(TEXT_DOMAIN, "unsupported AH key size")); case SPD_DIAGNOSTIC_UNSUPP_ESP_ENCR_KEYSIZE: return (dgettext(TEXT_DOMAIN, "unsupported ESP encryption key size")); case SPD_DIAGNOSTIC_UNSUPP_ESP_AUTH_KEYSIZE: return (dgettext(TEXT_DOMAIN, "unsupported ESP authentication key size")); case SPD_DIAGNOSTIC_NO_ACTION_EXT: return (dgettext(TEXT_DOMAIN, "No ACTION extension")); case SPD_DIAGNOSTIC_ALG_ID_RANGE: return (dgettext(TEXT_DOMAIN, "invalid algorithm identifer")); case SPD_DIAGNOSTIC_ALG_NUM_KEY_SIZES: return (dgettext(TEXT_DOMAIN, "number of key sizes inconsistent")); case SPD_DIAGNOSTIC_ALG_NUM_BLOCK_SIZES: return (dgettext(TEXT_DOMAIN, "number of block sizes inconsistent")); case SPD_DIAGNOSTIC_ALG_MECH_NAME_LEN: return (dgettext(TEXT_DOMAIN, "invalid mechanism name length")); case SPD_DIAGNOSTIC_NOT_GLOBAL_OP: return (dgettext(TEXT_DOMAIN, "operation not applicable to all policies")); case SPD_DIAGNOSTIC_NO_TUNNEL_SELECTORS: return (dgettext(TEXT_DOMAIN, "using selectors on a transport-mode tunnel")); default: return (dgettext(TEXT_DOMAIN, "unknown diagnostic")); } } /* * PF_KEY Diagnostic table. * * PF_KEY NOTE: If you change pfkeyv2.h's SADB_X_DIAGNOSTIC_* space, this is * where you need to add new messages. */ const char * keysock_diag(int diagnostic) { switch (diagnostic) { case SADB_X_DIAGNOSTIC_NONE: return (dgettext(TEXT_DOMAIN, "No diagnostic")); case SADB_X_DIAGNOSTIC_UNKNOWN_MSG: return (dgettext(TEXT_DOMAIN, "Unknown message type")); case SADB_X_DIAGNOSTIC_UNKNOWN_EXT: return (dgettext(TEXT_DOMAIN, "Unknown extension type")); case SADB_X_DIAGNOSTIC_BAD_EXTLEN: return (dgettext(TEXT_DOMAIN, "Bad extension length")); case SADB_X_DIAGNOSTIC_UNKNOWN_SATYPE: return (dgettext(TEXT_DOMAIN, "Unknown Security Association type")); case SADB_X_DIAGNOSTIC_SATYPE_NEEDED: return (dgettext(TEXT_DOMAIN, "Specific Security Association type needed")); case SADB_X_DIAGNOSTIC_NO_SADBS: return (dgettext(TEXT_DOMAIN, "No Security Association Databases present")); case SADB_X_DIAGNOSTIC_NO_EXT: return (dgettext(TEXT_DOMAIN, "No extensions needed for message")); case SADB_X_DIAGNOSTIC_BAD_SRC_AF: return (dgettext(TEXT_DOMAIN, "Bad source address family")); case SADB_X_DIAGNOSTIC_BAD_DST_AF: return (dgettext(TEXT_DOMAIN, "Bad destination address family")); case SADB_X_DIAGNOSTIC_BAD_PROXY_AF: return (dgettext(TEXT_DOMAIN, "Bad inner-source address family")); case SADB_X_DIAGNOSTIC_AF_MISMATCH: return (dgettext(TEXT_DOMAIN, "Source/destination address family mismatch")); case SADB_X_DIAGNOSTIC_BAD_SRC: return (dgettext(TEXT_DOMAIN, "Bad source address value")); case SADB_X_DIAGNOSTIC_BAD_DST: return (dgettext(TEXT_DOMAIN, "Bad destination address value")); case SADB_X_DIAGNOSTIC_ALLOC_HSERR: return (dgettext(TEXT_DOMAIN, "Soft allocations limit more than hard limit")); case SADB_X_DIAGNOSTIC_BYTES_HSERR: return (dgettext(TEXT_DOMAIN, "Soft bytes limit more than hard limit")); case SADB_X_DIAGNOSTIC_ADDTIME_HSERR: return (dgettext(TEXT_DOMAIN, "Soft add expiration time later " "than hard expiration time")); case SADB_X_DIAGNOSTIC_USETIME_HSERR: return (dgettext(TEXT_DOMAIN, "Soft use expiration time later " "than hard expiration time")); case SADB_X_DIAGNOSTIC_MISSING_SRC: return (dgettext(TEXT_DOMAIN, "Missing source address")); case SADB_X_DIAGNOSTIC_MISSING_DST: return (dgettext(TEXT_DOMAIN, "Missing destination address")); case SADB_X_DIAGNOSTIC_MISSING_SA: return (dgettext(TEXT_DOMAIN, "Missing SA extension")); case SADB_X_DIAGNOSTIC_MISSING_EKEY: return (dgettext(TEXT_DOMAIN, "Missing encryption key")); case SADB_X_DIAGNOSTIC_MISSING_AKEY: return (dgettext(TEXT_DOMAIN, "Missing authentication key")); case SADB_X_DIAGNOSTIC_MISSING_RANGE: return (dgettext(TEXT_DOMAIN, "Missing SPI range")); case SADB_X_DIAGNOSTIC_DUPLICATE_SRC: return (dgettext(TEXT_DOMAIN, "Duplicate source address")); case SADB_X_DIAGNOSTIC_DUPLICATE_DST: return (dgettext(TEXT_DOMAIN, "Duplicate destination address")); case SADB_X_DIAGNOSTIC_DUPLICATE_SA: return (dgettext(TEXT_DOMAIN, "Duplicate SA extension")); case SADB_X_DIAGNOSTIC_DUPLICATE_EKEY: return (dgettext(TEXT_DOMAIN, "Duplicate encryption key")); case SADB_X_DIAGNOSTIC_DUPLICATE_AKEY: return (dgettext(TEXT_DOMAIN, "Duplicate authentication key")); case SADB_X_DIAGNOSTIC_DUPLICATE_RANGE: return (dgettext(TEXT_DOMAIN, "Duplicate SPI range")); case SADB_X_DIAGNOSTIC_MALFORMED_SRC: return (dgettext(TEXT_DOMAIN, "Malformed source address")); case SADB_X_DIAGNOSTIC_MALFORMED_DST: return (dgettext(TEXT_DOMAIN, "Malformed destination address")); case SADB_X_DIAGNOSTIC_MALFORMED_SA: return (dgettext(TEXT_DOMAIN, "Malformed SA extension")); case SADB_X_DIAGNOSTIC_MALFORMED_EKEY: return (dgettext(TEXT_DOMAIN, "Malformed encryption key")); case SADB_X_DIAGNOSTIC_MALFORMED_AKEY: return (dgettext(TEXT_DOMAIN, "Malformed authentication key")); case SADB_X_DIAGNOSTIC_MALFORMED_RANGE: return (dgettext(TEXT_DOMAIN, "Malformed SPI range")); case SADB_X_DIAGNOSTIC_AKEY_PRESENT: return (dgettext(TEXT_DOMAIN, "Authentication key not needed")); case SADB_X_DIAGNOSTIC_EKEY_PRESENT: return (dgettext(TEXT_DOMAIN, "Encryption key not needed")); case SADB_X_DIAGNOSTIC_PROP_PRESENT: return (dgettext(TEXT_DOMAIN, "Proposal extension not needed")); case SADB_X_DIAGNOSTIC_SUPP_PRESENT: return (dgettext(TEXT_DOMAIN, "Supported algorithms extension not needed")); case SADB_X_DIAGNOSTIC_BAD_AALG: return (dgettext(TEXT_DOMAIN, "Unsupported authentication algorithm")); case SADB_X_DIAGNOSTIC_BAD_EALG: return (dgettext(TEXT_DOMAIN, "Unsupported encryption algorithm")); case SADB_X_DIAGNOSTIC_BAD_SAFLAGS: return (dgettext(TEXT_DOMAIN, "Invalid SA flags")); case SADB_X_DIAGNOSTIC_BAD_SASTATE: return (dgettext(TEXT_DOMAIN, "Invalid SA state")); case SADB_X_DIAGNOSTIC_BAD_AKEYBITS: return (dgettext(TEXT_DOMAIN, "Bad number of authentication bits")); case SADB_X_DIAGNOSTIC_BAD_EKEYBITS: return (dgettext(TEXT_DOMAIN, "Bad number of encryption bits")); case SADB_X_DIAGNOSTIC_ENCR_NOTSUPP: return (dgettext(TEXT_DOMAIN, "Encryption not supported for this SA type")); case SADB_X_DIAGNOSTIC_WEAK_EKEY: return (dgettext(TEXT_DOMAIN, "Weak encryption key")); case SADB_X_DIAGNOSTIC_WEAK_AKEY: return (dgettext(TEXT_DOMAIN, "Weak authentication key")); case SADB_X_DIAGNOSTIC_DUPLICATE_KMP: return (dgettext(TEXT_DOMAIN, "Duplicate key management protocol")); case SADB_X_DIAGNOSTIC_DUPLICATE_KMC: return (dgettext(TEXT_DOMAIN, "Duplicate key management cookie")); case SADB_X_DIAGNOSTIC_MISSING_NATT_LOC: return (dgettext(TEXT_DOMAIN, "Missing NAT-T local address")); case SADB_X_DIAGNOSTIC_MISSING_NATT_REM: return (dgettext(TEXT_DOMAIN, "Missing NAT-T remote address")); case SADB_X_DIAGNOSTIC_DUPLICATE_NATT_LOC: return (dgettext(TEXT_DOMAIN, "Duplicate NAT-T local address")); case SADB_X_DIAGNOSTIC_DUPLICATE_NATT_REM: return (dgettext(TEXT_DOMAIN, "Duplicate NAT-T remote address")); case SADB_X_DIAGNOSTIC_MALFORMED_NATT_LOC: return (dgettext(TEXT_DOMAIN, "Malformed NAT-T local address")); case SADB_X_DIAGNOSTIC_MALFORMED_NATT_REM: return (dgettext(TEXT_DOMAIN, "Malformed NAT-T remote address")); case SADB_X_DIAGNOSTIC_DUPLICATE_NATT_PORTS: return (dgettext(TEXT_DOMAIN, "Duplicate NAT-T ports")); case SADB_X_DIAGNOSTIC_MISSING_INNER_SRC: return (dgettext(TEXT_DOMAIN, "Missing inner source address")); case SADB_X_DIAGNOSTIC_MISSING_INNER_DST: return (dgettext(TEXT_DOMAIN, "Missing inner destination address")); case SADB_X_DIAGNOSTIC_DUPLICATE_INNER_SRC: return (dgettext(TEXT_DOMAIN, "Duplicate inner source address")); case SADB_X_DIAGNOSTIC_DUPLICATE_INNER_DST: return (dgettext(TEXT_DOMAIN, "Duplicate inner destination address")); case SADB_X_DIAGNOSTIC_MALFORMED_INNER_SRC: return (dgettext(TEXT_DOMAIN, "Malformed inner source address")); case SADB_X_DIAGNOSTIC_MALFORMED_INNER_DST: return (dgettext(TEXT_DOMAIN, "Malformed inner destination address")); case SADB_X_DIAGNOSTIC_PREFIX_INNER_SRC: return (dgettext(TEXT_DOMAIN, "Invalid inner-source prefix length ")); case SADB_X_DIAGNOSTIC_PREFIX_INNER_DST: return (dgettext(TEXT_DOMAIN, "Invalid inner-destination prefix length")); case SADB_X_DIAGNOSTIC_BAD_INNER_DST_AF: return (dgettext(TEXT_DOMAIN, "Bad inner-destination address family")); case SADB_X_DIAGNOSTIC_INNER_AF_MISMATCH: return (dgettext(TEXT_DOMAIN, "Inner source/destination address family mismatch")); case SADB_X_DIAGNOSTIC_BAD_NATT_REM_AF: return (dgettext(TEXT_DOMAIN, "Bad NAT-T remote address family")); case SADB_X_DIAGNOSTIC_BAD_NATT_LOC_AF: return (dgettext(TEXT_DOMAIN, "Bad NAT-T local address family")); case SADB_X_DIAGNOSTIC_PROTO_MISMATCH: return (dgettext(TEXT_DOMAIN, "Source/desination protocol mismatch")); case SADB_X_DIAGNOSTIC_INNER_PROTO_MISMATCH: return (dgettext(TEXT_DOMAIN, "Inner source/desination protocol mismatch")); case SADB_X_DIAGNOSTIC_DUAL_PORT_SETS: return (dgettext(TEXT_DOMAIN, "Both inner ports and outer ports are set")); default: return (dgettext(TEXT_DOMAIN, "Unknown diagnostic code")); } } /* * Convert an IPv6 mask to a prefix len. I assume all IPv6 masks are * contiguous, so I stop at the first zero bit! */ int in_masktoprefix(uint8_t *mask, boolean_t is_v4mapped) { int rc = 0; uint8_t last; int limit = IPV6_ABITS; if (is_v4mapped) { mask += ((IPV6_ABITS - IP_ABITS)/8); limit = IP_ABITS; } while (*mask == 0xff) { rc += 8; if (rc == limit) return (limit); mask++; } last = *mask; while (last != 0) { rc++; last = (last << 1) & 0xff; } return (rc); } /* * Expand the diagnostic code into a message. */ void print_diagnostic(FILE *file, uint16_t diagnostic) { /* Use two spaces so above strings can fit on the line. */ (void) fprintf(file, dgettext(TEXT_DOMAIN, " Diagnostic code %u: %s.\n"), diagnostic, keysock_diag(diagnostic)); } /* * Prints the base PF_KEY message. */ void print_sadb_msg(struct sadb_msg *samsg, time_t wallclock, boolean_t vflag) { if (wallclock != 0) printsatime(wallclock, dgettext(TEXT_DOMAIN, "%sTimestamp: %s\n"), "", NULL, vflag); (void) printf(dgettext(TEXT_DOMAIN, "Base message (version %u) type "), samsg->sadb_msg_version); switch (samsg->sadb_msg_type) { case SADB_RESERVED: (void) printf(dgettext(TEXT_DOMAIN, "RESERVED (warning: set to 0)")); break; case SADB_GETSPI: (void) printf("GETSPI"); break; case SADB_UPDATE: (void) printf("UPDATE"); break; case SADB_ADD: (void) printf("ADD"); break; case SADB_DELETE: (void) printf("DELETE"); break; case SADB_GET: (void) printf("GET"); break; case SADB_ACQUIRE: (void) printf("ACQUIRE"); break; case SADB_REGISTER: (void) printf("REGISTER"); break; case SADB_EXPIRE: (void) printf("EXPIRE"); break; case SADB_FLUSH: (void) printf("FLUSH"); break; case SADB_DUMP: (void) printf("DUMP"); break; case SADB_X_PROMISC: (void) printf("X_PROMISC"); break; case SADB_X_INVERSE_ACQUIRE: (void) printf("X_INVERSE_ACQUIRE"); break; default: (void) printf(dgettext(TEXT_DOMAIN, "Unknown (%u)"), samsg->sadb_msg_type); break; } (void) printf(dgettext(TEXT_DOMAIN, ", SA type ")); switch (samsg->sadb_msg_satype) { case SADB_SATYPE_UNSPEC: (void) printf(dgettext(TEXT_DOMAIN, "")); break; case SADB_SATYPE_AH: (void) printf("AH"); break; case SADB_SATYPE_ESP: (void) printf("ESP"); break; case SADB_SATYPE_RSVP: (void) printf("RSVP"); break; case SADB_SATYPE_OSPFV2: (void) printf("OSPFv2"); break; case SADB_SATYPE_RIPV2: (void) printf("RIPv2"); break; case SADB_SATYPE_MIP: (void) printf(dgettext(TEXT_DOMAIN, "Mobile IP")); break; default: (void) printf(dgettext(TEXT_DOMAIN, ""), samsg->sadb_msg_satype); break; } (void) printf(".\n"); if (samsg->sadb_msg_errno != 0) { (void) printf(dgettext(TEXT_DOMAIN, "Error %s from PF_KEY.\n"), strerror(samsg->sadb_msg_errno)); print_diagnostic(stdout, samsg->sadb_x_msg_diagnostic); } (void) printf(dgettext(TEXT_DOMAIN, "Message length %u bytes, seq=%u, pid=%u.\n"), SADB_64TO8(samsg->sadb_msg_len), samsg->sadb_msg_seq, samsg->sadb_msg_pid); } /* * Print the SA extension for PF_KEY. */ void print_sa(char *prefix, struct sadb_sa *assoc) { if (assoc->sadb_sa_len != SADB_8TO64(sizeof (*assoc))) { warnx(dgettext(TEXT_DOMAIN, "WARNING: SA info extension length (%u) is bad."), SADB_64TO8(assoc->sadb_sa_len)); } (void) printf(dgettext(TEXT_DOMAIN, "%sSADB_ASSOC spi=0x%x, replay=%u, state="), prefix, ntohl(assoc->sadb_sa_spi), assoc->sadb_sa_replay); switch (assoc->sadb_sa_state) { case SADB_SASTATE_LARVAL: (void) printf(dgettext(TEXT_DOMAIN, "LARVAL")); break; case SADB_SASTATE_MATURE: (void) printf(dgettext(TEXT_DOMAIN, "MATURE")); break; case SADB_SASTATE_DYING: (void) printf(dgettext(TEXT_DOMAIN, "DYING")); break; case SADB_SASTATE_DEAD: (void) printf(dgettext(TEXT_DOMAIN, "DEAD")); break; default: (void) printf(dgettext(TEXT_DOMAIN, ""), assoc->sadb_sa_state); } if (assoc->sadb_sa_auth != SADB_AALG_NONE) { (void) printf(dgettext(TEXT_DOMAIN, "\n%sAuthentication algorithm = "), prefix); (void) dump_aalg(assoc->sadb_sa_auth, stdout); } if (assoc->sadb_sa_encrypt != SADB_EALG_NONE) { (void) printf(dgettext(TEXT_DOMAIN, "\n%sEncryption algorithm = "), prefix); (void) dump_ealg(assoc->sadb_sa_encrypt, stdout); } (void) printf(dgettext(TEXT_DOMAIN, "\n%sflags=0x%x < "), prefix, assoc->sadb_sa_flags); if (assoc->sadb_sa_flags & SADB_SAFLAGS_PFS) (void) printf("PFS "); if (assoc->sadb_sa_flags & SADB_SAFLAGS_NOREPLAY) (void) printf("NOREPLAY "); /* BEGIN Solaris-specific flags. */ if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_USED) (void) printf("X_USED "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_UNIQUE) (void) printf("X_UNIQUE "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_AALG1) (void) printf("X_AALG1 "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_AALG2) (void) printf("X_AALG2 "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_EALG1) (void) printf("X_EALG1 "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_EALG2) (void) printf("X_EALG2 "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_NATT_LOC) (void) printf("X_NATT_LOC "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_NATT_REM) (void) printf("X_NATT_REM "); if (assoc->sadb_sa_flags & SADB_X_SAFLAGS_TUNNEL) (void) printf("X_TUNNEL "); /* END Solaris-specific flags. */ (void) printf(">\n"); } void printsatime(int64_t lt, const char *msg, const char *pfx, const char *pfx2, boolean_t vflag) { char tbuf[TBUF_SIZE]; /* For strftime() call. */ const char *tp = tbuf; time_t t = lt; struct tm res; if (t != lt) { if (lt > 0) t = LONG_MAX; else t = LONG_MIN; } if (strftime(tbuf, TBUF_SIZE, NULL, localtime_r(&t, &res)) == 0) tp = dgettext(TEXT_DOMAIN, "