/* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "preshared.h" #include "defs.h" #include static FILE *fp; /* Preshared key lock */ static pthread_rwlock_t ps_rwlock = PTHREAD_RWLOCK_INITIALIZER; #define hd2num(hd) (((hd) >= '0' && (hd) <= '9') ? ((hd) - '0') : \ (((hd) >= 'a' && (hd) <= 'f') ? ((hd) - 'a' + 10) : ((hd) - 'A' + 10))) #define ADDRBITS_V4 32 /* number of bits in IPv4 address */ #define ADDRBITS_V6 128 /* number of bits in IPv6 address */ #define CHR_CURLY_BRACE_OPEN '{' #define CHR_CURLY_BRACE_CLOSE '}' #define CHR_COMMENT_BEGIN '#' #define CHR_COLON ':' #define CHR_SLASH '/' #define CHR_BACKSLASH '\\' #define CHR_DOUBLEQUOTE '"' #define CHR_SPACE ' ' #define CHR_NEWLINE '\n' #define CHR_TAB '\t' #define CHR_NULL '\0' /* * Error message strings */ static const char *err_arr [] = { #define ERR_LOCID_MISMATCH 0 "Local Id value does not match the specified Localid type", #define ERR_REMID_MISMATCH 1 "Remote Id does not match the remoteid type", #define ERR_MAINMODE_LOCID_NOTIP 2 "Local Id can only be an IP address", #define ERR_MAINMODE_REMID_NOTIP 3 "Remote Id can only be an IP address", #define ERR_ENTRY_NOBEGINBRACE 4 "Syntax error - Entry does not start with an open brace", #define ERR_INVALID_FIELDNAME 5 "Invalid attribute field name", #define ERR_REPEATED_FIELD 6 "Field in an entry is repeated", #define ERR_INVALID_LOCALID_TYPE 7 "Invalid Local Id type", #define ERR_MISSING_LOCALID 8 "localid not defined", #define ERR_MISSING_REMID 9 "remotedid not defined", #define ERR_INVALID_REMID_TYPE 10 "Invalid Remote Id type", #define ERR_INVALID_IKE_MODE 11 "Invalid Ike Mode", #define ERR_INVALID_BIT_SPECIFIER 12 "Invalid Bit Specifier", #define ERR_BITLEN2BIG 13 "Bit length too large", #define ERR_WARN_LOWBITS_TRUNC 14 "Lower bits will be truncated", #define ERR_STRING_NOT_HEX 15 "Invalid string format - hex string expected", #define ERR_BADADDR_PREFIXLEN_PART 16 "Invalid prefix length format in address", #define ERR_BADADDR_SLASH_UNEXPECTED 17 "Unxpected '/' in address string", #define ERR_BADADDR_TRY_AGAIN 18 "Bad address string - try again later", #define ERR_BADADDR_ADDRESS 19 "Bad address string", #define ERR_BADADDR_MISMATCH 20 "Address versions do not match", #define ERR_BADADDR_MCAST 21 "Multicast address not allowed", #define ERR_BADADDR_4MAPPED 22 "V4 mapped in v6 address not allowed", #define ERR_BADADDR_4COMPAT 23 "V4 compatible in v6 address not allowed", #define ERR_INVALID_ASCII_STRING 24 "Syntax error in ASCII string or quotes", }; /* * Buffer to hold input line */ static char linebuf[1024]; /* * Types of tokens */ #define PS_TOK_BEGIN_CURLY_BRACE 1 #define PS_TOK_END_CURLY_BRACE 2 #define PS_TOK_FLD_TYPE 3 #define PS_TOK_FLD_VALUE 4 /* * The prototype syntax of this file used, e.g. "localid" and "localidtype". * The aliases support transitioning to using same keywords as the ike/config * parser, e.g. "local_id" and "local_id_type". * * Also, {local,remote}_addr are aliases for {local,remote}_id for now, * eventually they should be handled differently. */ static const keywdtab_t fldstab[] = { { PS_FLD_LOCID, "localid" }, { PS_FLD_LOCID, "local_id" }, { PS_FLD_LOCID, "local_addr" }, { PS_FLD_LOCID_TYPE, "localidtype" }, { PS_FLD_LOCID_TYPE, "local_id_type" }, { PS_FLD_REMID, "remoteid" }, { PS_FLD_REMID, "remote_id" }, { PS_FLD_REMID, "remote_addr" }, { PS_FLD_REMID_TYPE, "remoteidtype" }, { PS_FLD_REMID_TYPE, "remote_id_type" }, { PS_FLD_IKE_MODE, "ike_mode" }, { PS_FLD_IKE_MODE, "mode" }, { PS_FLD_KEY, "key" }, }; static const keywdtab_t idstab[] = { { PS_ID_IP, "IP" }, { PS_ID_IP4, "IPv4" }, { PS_ID_IP6, "IPv6" }, { PS_ID_IP, "IP_SUBNET" }, { PS_ID_IP4, "IPv4_SUBNET" }, { PS_ID_IP6, "IPv6_SUBNET" }, }; static const keywdtab_t ikmstab[] = { { PS_IKM_MAIN, "main" }, }; /* * Head and tail of linked list of entry data structures */ static preshared_entry_t *ps_head, *ps_tail; /* * Global counters for use in reporting approximate error location * in config file */ static int err_line_number; static int err_entry_number; /* * Function prototypes */ static int getidtype(char *); static int getfldtype(char *); static int getikmtype(char *); static int postprocess_entry(preshared_entry_t *, char **); static char *get_next_token(char **); static char *readnextline(FILE *); static preshared_entry_t *getnextentry(FILE *, char **); static uint8_t *parsekey(char *, uint_t *, uint_t *, char **); /* Note: in_get{prefilen,addr}, in6_getaddr stolen from ifconfig.c */ static int in_getprefixlen(char *, boolean_t, int); static int in_getaddr(char *, struct sockaddr_storage *, int *, char **); static int in6_getaddr(char *, struct sockaddr_storage *, int *, char **); static boolean_t check_if_v6(char *); /* * Functions */ /* * Check for same preshared entry. * Caller holds ps_rwlock as reader */ static boolean_t same_psent(preshared_entry_t *ptr, preshared_entry_t *ps) { struct sockaddr_in *ptr_v4, *ps_v4; struct sockaddr_in6 *ptr_v6, *ps_v6; assert(RW_LOCK_HELD(&ps_rwlock)); /* * Check for duplicate entries. Preshared type * of IPv4/IPv6 has a NULL locid/remid string and * has the sockaddr_storages structures populated. * All others store themselves in the aforementioned * strings. */ /* Local and Remote ID types must match */ if ((ptr->pe_locidtype != ps->pe_locidtype) || (ptr->pe_remidtype != ps->pe_remidtype)) return (B_FALSE); /* Check local ids */ switch (ptr->pe_locidtype) { case PS_ID_IP: case PS_ID_IP4: case PS_ID_SUBNET4: ptr_v4 = (struct sockaddr_in *)&ptr->pe_locid_sa; ps_v4 = (struct sockaddr_in *)&ps->pe_locid_sa; if ((uint32_t)ptr_v4->sin_addr.s_addr != (uint32_t)ps_v4->sin_addr.s_addr) return (B_FALSE); /* * Prefix length either same number or same error * PS_PLEN_NO_PREFIX for non subnet */ if (&ptr->pe_locid_plen != &ps->pe_locid_plen) return (B_FALSE); break; case PS_ID_IP6: case PS_ID_SUBNET6: ptr_v6 = (struct sockaddr_in6 *)&ptr->pe_locid_sa; ps_v6 = (struct sockaddr_in6 *)&ps->pe_locid_sa; if (!(IN6_ARE_ADDR_EQUAL(&ptr_v6->sin6_addr, &ps_v6->sin6_addr))) return (B_FALSE); /* * Prefix length either same number or same error * PS_PLEN_NO_PREFIX for non subnet */ if (&ptr->pe_locid_plen != &ps->pe_locid_plen) return (B_FALSE); break; default: if (strcmp(ptr->pe_locid, ps->pe_locid) != 0) return (B_FALSE); } /* Check remote ids */ switch (ptr->pe_remidtype) { case PS_ID_IP: case PS_ID_IP4: ptr_v4 = (struct sockaddr_in *)&ptr->pe_remid_sa; ps_v4 = (struct sockaddr_in *)&ps->pe_remid_sa; if ((uint32_t)ptr_v4->sin_addr.s_addr != (uint32_t)ps_v4->sin_addr.s_addr) return (B_FALSE); /* * Prefix length either same number or same error * PS_PLEN_NO_PREFIX for non subnet */ if (&ptr->pe_remid_plen != &ps->pe_remid_plen) return (B_FALSE); break; case PS_ID_IP6: ptr_v6 = (struct sockaddr_in6 *)&ptr->pe_remid_sa; ps_v6 = (struct sockaddr_in6 *)&ps->pe_remid_sa; if (!(IN6_ARE_ADDR_EQUAL(&ptr_v6->sin6_addr, &ps_v6->sin6_addr))) return (B_FALSE); /* * Prefix length either same number or same error * PS_PLEN_NO_PREFIX for non subnet */ if (&ptr->pe_remid_plen != &ps->pe_remid_plen) return (B_FALSE); break; default: if (strcmp(ptr->pe_remid, ps->pe_remid) != 0) return (B_FALSE); } return (B_TRUE); } /* * Check for duplicate preshared entry. * Caller holds ps_rwlock as reader */ static boolean_t has_dup(preshared_entry_t *ps, preshared_entry_t **head) { preshared_entry_t *ptr; assert(RW_LOCK_HELD(&ps_rwlock)); if (ps == NULL) /* no op */ return (B_FALSE); ptr = *head; while (ptr != NULL) { if (same_psent(ptr, ps)) return (B_TRUE); ptr = ptr->pe_next; } return (B_FALSE); } /* * Append entries to list. * Caller holds ps_rwlock as writer */ static boolean_t append_to_list(preshared_entry_t *ps, preshared_entry_t **head, preshared_entry_t **tail) { assert(RW_WRITE_HELD(&ps_rwlock)); if (ps == NULL) /* no op */ return (B_TRUE); assert(ps->pe_refcnt > 0); if (has_dup(ps, head)) { (void) fprintf(stderr, gettext("Ignoring duplicate preshared entry.\n")); PE_REFRELE(ps); return (B_FALSE); } ps->pe_next = NULL; /* will be last entry */ if (*head == NULL) { /* list initialization */ *head = *tail = ps; } else { (*tail)->pe_next = ps; *tail = (*tail)->pe_next; } return (B_TRUE); } /* * Appends preshared entry to list * Holds ps_rwlock as writer */ boolean_t append_preshared_entry(preshared_entry_t *ps) { boolean_t rc; (void) pthread_rwlock_wrlock(&ps_rwlock); rc = append_to_list(ps, &ps_head, &ps_tail); (void) pthread_rwlock_unlock(&ps_rwlock); return (rc); } /* * Frees preshared list * Caller holds ps_rwlock as writer */ static void free_preshared_list(preshared_entry_t **head, preshared_entry_t **tail) { preshared_entry_t *ps; assert(RW_WRITE_HELD(&ps_rwlock)); while (*head != NULL) { /* Zero the old list in case of reloading. */ ps = *head; if (ps == *tail) *tail = NULL; *head = ps->pe_next; PE_REFRELE(ps); } } static char * getidstr(int val) { const keywdtab_t *idt; for (idt = idstab; idt < A_END(idstab); idt++) { if (val == idt->kw_tag) return (idt->kw_str); } return (NULL); /* not found */ } static int getidtype(char *valp) { const keywdtab_t *idt; for (idt = idstab; idt < A_END(idstab); idt++) { if (strcasecmp(valp, idt->kw_str) == 0) return (idt->kw_tag); } return (-1); /* not found */ } static int getfldtype(char *valp) { const keywdtab_t *fldt; for (fldt = fldstab; fldt < A_END(fldstab); fldt++) { if (strcasecmp(valp, fldt->kw_str) == 0) return (fldt->kw_tag); } return (-1); /* not found */ } static char * getikmstr(int val) { const keywdtab_t *idt; for (idt = ikmstab; idt < A_END(ikmstab); idt++) { if (val == idt->kw_tag) return (idt->kw_str); } return (NULL); /* not found */ } static int getikmtype(char *valp) { const keywdtab_t *ikmt; for (ikmt = ikmstab; ikmt < A_END(ikmstab); ikmt++) { if (strcasecmp(valp, ikmt->kw_str) == 0) return (ikmt->kw_tag); } return (-1); /* not found */ } /* * unlike the other get*str functions, this one mallocs the returned * string, so the caller will need to free it. */ char * getkeystr(uint8_t *key, uint_t bytes, uint_t bits) { uint8_t *sp; char *dp, *buf; uint_t len; /* assume two digits per byte, and no more than 4 digits of bitlen */ len = (bytes < 1) + 6; if ((buf = malloc(len)) == NULL) return (NULL); sp = key; dp = buf; while (bytes-- != 0) { (void) sprintf(dp, "%02x", *sp++); dp += 2; } (void) sprintf(dp, "/%d", bits); return (buf); } /* * Parsing for hex key values. * Return value: * Pointer to allocated buffer containing the key * Parameter, "len" contains length of key buffer on successful return * Parameter, "lbits" contains length of key in bits in the key buffer. * Note:Stolen from ipseckey.c and then adapted for use here. */ static uint8_t * parsehexkey(char *input, uint_t *keybuflen, uint_t *lbits, char **errp) { uint8_t *keyp, *keybufp; uint_t i, hexlen = 0, bits, alloclen; for (i = 0; input[i] != CHR_NULL && input[i] != CHR_SLASH; i++) hexlen++; if (input[i] == CHR_NULL) { bits = 0; } else { /* Have /nn. */ input[i] = CHR_NULL; if (sscanf((input + i + 1), "%u", &bits) != 1) { *errp = (char *)err_arr[ERR_INVALID_BIT_SPECIFIER]; return (NULL); } /* hexlen in nibbles */ if (((bits + 3) >> 2) > hexlen) { *errp = (char *)err_arr[ERR_BITLEN2BIG]; return (NULL); } /* * Adjust hexlen down if user gave us too small of a bit * count. */ if ((hexlen << 2) > bits + 3) { /* * NOTE: Callers don't necessarily handle warnings * a successful return. */ *errp = (char *)err_arr[ERR_WARN_LOWBITS_TRUNC]; hexlen = (bits + 3) >> 2; input[hexlen] = CHR_NULL; } } /* * Allocate. Remember, hexlen is in nibbles. */ alloclen = (hexlen/2 + (hexlen & 0x1)); keyp = malloc(alloclen); if (keyp == NULL) { *errp = strerror(errno); return (NULL); } keybufp = keyp; *keybuflen = alloclen; if (bits == 0) *lbits = (hexlen + (hexlen & 0x1)) << 2; else *lbits = bits; /* * Read in nibbles. Read in odd-numbered as shifted high. * (e.g. 123 becomes 0x1230). */ for (i = 0; input[i] != CHR_NULL; i += 2) { boolean_t second = (input[i + 1] != CHR_NULL); if (!isxdigit(input[i]) || (!isxdigit(input[i + 1]) && second)) { free(keybufp); /* free allocated memory on error */ *errp = (char *)err_arr[ERR_STRING_NOT_HEX]; return (NULL); } *keyp = (hd2num(input[i]) << 4); if (second) *keyp |= hd2num(input[i + 1]); else break; /* out of for loop. */ keyp++; } /* bzero the remaining bits if we're a non-octet amount. */ if (bits & 0x7) *((input[i] == CHR_NULL) ? keyp - 1 : keyp) &= 0xff << (8 - (bits & 0x7)); return (keybufp); } /* * Parsing for ASCII key values. * Return value: * Pointer to allocated buffer containing the key * Parameter, "len" contains length of key buffer on successful return * Parameter, "lbits" contains length of key in bits in the key buffer. * Note:Stolen from ipseckey.c and then adapted for use here. */ static uint8_t * parseasciikey(char *input, uint_t *keybuflen, uint_t *lbits, char **errp) { uint8_t *keyp, *keybufp; uint_t i, asciilen = 0; /* Make sure the first and last characters are '"', unescaped */ if (input[0] != CHR_DOUBLEQUOTE) goto invalid_ascii_string; /* Set pointer past first quote, then find the end */ input++; for (i = 0; input[i] != CHR_NULL; i++) asciilen++; /* * Length without trailing \0 and leading quote lopped off * from before must be at least 3 for a well formed key */ if (--asciilen < 3) goto invalid_ascii_string; /* * Make sure the last quote exists and is not escaped, * but also watch for valid "this string\\" syntax. */ if (input[asciilen] != CHR_DOUBLEQUOTE) goto invalid_ascii_string; /* We know we can dereference these because of length check above */ if ((input[asciilen - 1] == CHR_BACKSLASH) && (input[asciilen - 2] != CHR_BACKSLASH)) goto invalid_ascii_string; input[asciilen] = CHR_NULL; /* Now we have something well-formed in quotes and length set */ /* * Allocate. If we have backslashes, we can shrink later */ keyp = malloc(asciilen); if (keyp == NULL) { *errp = strerror(errno); return (NULL); } keybufp = keyp; /* * Read in bytes, but be aware of next byte for escape purposes */ for (i = 0; input[i] != CHR_NULL; i++) { boolean_t second = (input[i + 1] != CHR_NULL); if (!isascii(input[i]) || (second && !isascii(input[i + 1]))) { goto free_key_buffer; } /* * Consider backslash an escape character for * itself, single and double quotes, but not * for anything else */ if (second && (input[i] == CHR_BACKSLASH)) { if (input[i + 1] == CHR_DOUBLEQUOTE || input[i + 1] == CHR_BACKSLASH) { *keyp = input[i + 1]; i++; keyp++; asciilen--; continue; /* for loop */ } } *keyp = input[i]; keyp++; } /* Shrink to fit */ *keybuflen = asciilen; *lbits = asciilen << 3; keybufp = realloc(keybufp, asciilen); return (keybufp); free_key_buffer: free(keybufp); invalid_ascii_string: *errp = (char *)err_arr[ERR_INVALID_ASCII_STRING]; return (NULL); } static uint8_t * parsekey(char *input, uint_t *keybuflen, uint_t *lbits, char **errp) { /* Determine if this is an ASCII key or hex key */ if (input[0] == CHR_DOUBLEQUOTE) { return (parseasciikey(input, keybuflen, lbits, errp)); } else { return (parsehexkey(input, keybuflen, lbits, errp)); } } static boolean_t check_if_v6(char *abuf) { return (strchr(abuf, CHR_COLON) != NULL ? B_TRUE : B_FALSE); } static boolean_t valid_ip6_address(struct sockaddr_in6 *addr, char **errmsgp) { /* is it mcast */ if (IN6_IS_ADDR_MULTICAST(&addr->sin6_addr)) { *errmsgp = (char *)err_arr[ERR_BADADDR_MCAST]; return (B_FALSE); } /* is it v4 mapped */ if (IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr)) { *errmsgp = (char *)err_arr[ERR_BADADDR_4MAPPED]; return (B_FALSE); } /* is it v4 compat */ if (IN6_IS_ADDR_V4COMPAT(&addr->sin6_addr)) { *errmsgp = (char *)err_arr[ERR_BADADDR_4COMPAT]; return (B_FALSE); } return (B_TRUE); } /* * Post process preshared key entry. * Caller holds ps_rwlock as reader */ static int postprocess_entry(preshared_entry_t *ps, char **errmsgp) { assert(RW_LOCK_HELD(&ps_rwlock)); /* ID types default to IP. */ if ((ps->pe_flds_mask & PS_FLD_LOCID_TYPE) == 0) { ps->pe_flds_mask |= PS_FLD_LOCID_TYPE; ps->pe_locidtype = PS_ID_IP; } if ((ps->pe_flds_mask & PS_FLD_REMID_TYPE) == 0) { ps->pe_flds_mask |= PS_FLD_REMID_TYPE; ps->pe_remidtype = PS_ID_IP; } /* * Verify that all mandatory fields are there. * mandatory: local_id and remote_id (or local_addr and remote_addr), * mode, and key. */ /* * Note: verify that semantic relationships among fields is fine * Since we only support main mode, we force the mode to be main. */ ps->pe_ike_mode = PS_IKM_MAIN; if ((ps->pe_locidtype != PS_ID_IP) && (ps->pe_locidtype != PS_ID_IP4) && (ps->pe_locidtype != PS_ID_IP6)) { *errmsgp = (char *)err_arr[ERR_MAINMODE_LOCID_NOTIP]; return (-1); } if ((ps->pe_remidtype != PS_ID_IP) && (ps->pe_remidtype != PS_ID_IP4) && (ps->pe_remidtype != PS_ID_IP6)) { *errmsgp = (char *)err_arr[ERR_MAINMODE_REMID_NOTIP]; return (-1); } /* * Parse "id" values now and verify that they match the * "idtype" associated with them. * NOTE: Real work is TBD */ if (ps->pe_flds_mask & PS_FLD_LOCID_TYPE) { int retval; if (ps->pe_locid == NULL) { *errmsgp = (char *)err_arr[ERR_MISSING_LOCALID]; return (-1); } switch (ps->pe_locidtype) { case PS_ID_IP: { boolean_t isv6 = check_if_v6(ps->pe_locid); if (isv6) { ps->pe_locidtype = PS_ID_IP6; retval = in6_getaddr(ps->pe_locid, &ps->pe_locid_sa, &ps->pe_locid_plen, errmsgp); } else { ps->pe_locidtype = PS_ID_IP4; retval = in_getaddr(ps->pe_locid, &ps->pe_locid_sa, &ps->pe_locid_plen, errmsgp); } if (retval < 0) return (-1); } break; case PS_ID_IP4: case PS_ID_SUBNET4: retval = in_getaddr(ps->pe_locid, &ps->pe_locid_sa, &ps->pe_locid_plen, errmsgp); if (retval < 0) return (-1); break; case PS_ID_IP6: case PS_ID_SUBNET6: retval = in6_getaddr(ps->pe_locid, &ps->pe_locid_sa, &ps->pe_locid_plen, errmsgp); if (retval < 0) return (-1); break; case PS_ID_SUBNET: { boolean_t isv6 = check_if_v6(ps->pe_locid); if (isv6) { ps->pe_locidtype = PS_ID_SUBNET6; retval = in6_getaddr(ps->pe_locid, &ps->pe_locid_sa, &ps->pe_locid_plen, errmsgp); } else { ps->pe_locidtype = PS_ID_SUBNET4; retval = in_getaddr(ps->pe_locid, &ps->pe_locid_sa, &ps->pe_locid_plen, errmsgp); } if (retval < 0) return (-1); } break; case PS_ID_RANGE: break; case PS_ID_RANGE4: break; case PS_ID_RANGE6: break; case PS_ID_ASN1DN: break; case PS_ID_ASN1GN: break; case PS_ID_KEYID: break; case PS_ID_FQDN: break; case PS_ID_USER_FQDN: break; } } if (ps->pe_flds_mask & PS_FLD_REMID_TYPE) { int retval; if (ps->pe_remid == NULL) { *errmsgp = (char *)err_arr[ERR_MISSING_REMID]; return (-1); } switch (ps->pe_remidtype) { case PS_ID_IP: { boolean_t isv6 = check_if_v6(ps->pe_remid); if (isv6) { ps->pe_remidtype = PS_ID_IP6; retval = in6_getaddr(ps->pe_remid, &ps->pe_remid_sa, &ps->pe_remid_plen, errmsgp); } else { ps->pe_remidtype = PS_ID_IP4; retval = in_getaddr(ps->pe_remid, &ps->pe_remid_sa, &ps->pe_remid_plen, errmsgp); } if (retval < 0) return (-1); } break; case PS_ID_IP4: case PS_ID_SUBNET4: retval = in_getaddr(ps->pe_remid, &ps->pe_remid_sa, &ps->pe_remid_plen, errmsgp); if (retval < 0) return (-1); break; case PS_ID_IP6: case PS_ID_SUBNET6: retval = in6_getaddr(ps->pe_remid, &ps->pe_remid_sa, &ps->pe_remid_plen, errmsgp); if (retval < 0) return (-1); break; case PS_ID_SUBNET: { boolean_t isv6 = check_if_v6(ps->pe_remid); if (isv6) { ps->pe_remidtype = PS_ID_SUBNET6; retval = in6_getaddr(ps->pe_remid, &ps->pe_remid_sa, &ps->pe_remid_plen, errmsgp); } else { ps->pe_remidtype = PS_ID_SUBNET4; retval = in_getaddr(ps->pe_remid, &ps->pe_remid_sa, &ps->pe_remid_plen, errmsgp); } if (retval < 0) return (-1); } break; case PS_ID_RANGE: break; case PS_ID_RANGE4: break; case PS_ID_RANGE6: break; case PS_ID_ASN1DN: break; case PS_ID_ASN1GN: break; case PS_ID_KEYID: break; case PS_ID_FQDN: break; case PS_ID_USER_FQDN: break; } } /* * Reality check address families * Remote and local address families must match */ if (check_if_v6(ps->pe_locid) != check_if_v6(ps->pe_remid)) { *errmsgp = (char *)err_arr[ERR_BADADDR_MISMATCH]; return (-1); } /* v6 */ if (ps->pe_remidtype == PS_ID_IP6 || ps->pe_remidtype == PS_ID_SUBNET6) { struct sockaddr_in6 *local = (struct sockaddr_in6 *)(&ps->pe_locid_sa); struct sockaddr_in6 *remote = (struct sockaddr_in6 *)(&ps->pe_remid_sa); if (!valid_ip6_address(local, errmsgp) || !valid_ip6_address(remote, errmsgp)) { return (-1); } } else { /* v4 */ struct sockaddr_in *local = (struct sockaddr_in *)(&ps->pe_locid_sa); struct sockaddr_in *remote = (struct sockaddr_in *)(&ps->pe_remid_sa); /* are either mcast */ if (IN_MULTICAST(ntohl(local->sin_addr.s_addr)) || IN_MULTICAST(ntohl(remote->sin_addr.s_addr))) { *errmsgp = (char *)err_arr[ERR_BADADDR_MCAST]; return (-1); } } return (0); /* OK return */ } /* * Note: Code stolen from ifconfig.c and adapted. * * If "slash" is zero this parses the whole string as * an integer. With "slash" non zero it parses the tail part as an integer. * * If it is not a valid integer this returns BAD_ADDR. * If there is / present this returns PS_PLEN_NO_PREFIX. */ static int in_getprefixlen(char *addr, boolean_t slash, int max_plen) { int prefixlen; char *str, *end; if (slash) { str = strchr(addr, CHR_SLASH); if (str == NULL) return (PS_PLEN_NO_PREFIX); str++; } else str = addr; prefixlen = strtol(str, &end, 10); if (prefixlen < 0) return (PS_PLEN_BAD_ADDR); if (str == end) return (PS_PLEN_BAD_ADDR); if (max_plen != 0 && max_plen < prefixlen) return (PS_PLEN_BAD_ADDR); return (prefixlen); } /* * Note: code stolen from ifconfig.c and adapted * * If the last argument is non-NULL allow a / syntax and * pass out in *plenp. * If doesn't parse return PS_PLEN_BAD_ADDR as *plenp. * If no / is present return PS_PLEN_NO_PREFIX as *plenp. */ static int in_getaddr(char *s, struct sockaddr_storage *saddr, int *plenp, char **errp) { struct sockaddr_in *sin = (struct sockaddr_in *)saddr; struct hostent *hp; struct netent *np; char str[BUFSIZ]; int error_num; (void) strncpy(str, s, sizeof (str)); /* * Look for '/' (CHR_SLASH)is plenp */ if (plenp != NULL) { char *cp; *plenp = in_getprefixlen(str, B_TRUE, ADDRBITS_V4); if (*plenp == PS_PLEN_BAD_ADDR) { *errp = (char *)err_arr[ERR_BADADDR_PREFIXLEN_PART]; return (-1); } cp = strchr(str, CHR_SLASH); if (cp != NULL) *cp = CHR_NULL; } else if (strchr(str, CHR_SLASH) != NULL) { *errp = (char *)err_arr[ERR_BADADDR_SLASH_UNEXPECTED]; return (-1); } (void) memset(sin, 0, sizeof (*sin)); /* * Try to catch attempts to set the broadcast address to all 1's. */ if (strcmp(str, "255.255.255.255") == 0 || (strtoul(str, (char **)NULL, 0) == 0xffffffffUL)) { sin->sin_family = AF_INET; sin->sin_addr.s_addr = 0xffffffff; return (0); } hp = getipnodebyname(str, AF_INET, 0, &error_num); if (hp) { sin->sin_family = hp->h_addrtype; (void) memcpy(&sin->sin_addr, hp->h_addr, hp->h_length); freehostent(hp); return (0); } np = getnetbyname(str); if (np) { sin->sin_family = np->n_addrtype; sin->sin_addr = inet_makeaddr(np->n_net, INADDR_ANY); return (0); } if (error_num == TRY_AGAIN) { *errp = (char *)err_arr[ERR_BADADDR_TRY_AGAIN]; } else { *errp = (char *)err_arr[ERR_BADADDR_ADDRESS]; } return (-1); } /* * Note: Code stolen from ifconfig.c and adapted. * * If the third argument is non-NULL allow a / syntax and * pass out in *plenp. * If doesn't parse return PS_PLEN_BAD_ADDR as *plenp. * If no / is present return PS_PLEN_NO_PREFIX as *plenp. */ static int in6_getaddr(char *s, struct sockaddr_storage *saddr, int *plenp, char **errp) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)saddr; struct hostent *hp; char str[BUFSIZ]; int error_num; (void) strncpy(str, s, sizeof (str)); /* * Look for '/' (CHR_SLASH) is plenp */ if (plenp != NULL) { char *cp; *plenp = in_getprefixlen(str, B_TRUE, ADDRBITS_V6); if (*plenp == PS_PLEN_BAD_ADDR) { *errp = (char *)err_arr[ERR_BADADDR_PREFIXLEN_PART]; return (-1); } cp = strchr(str, CHR_SLASH); if (cp != NULL) *cp = CHR_NULL; } else if (strchr(str, CHR_SLASH) != NULL) { *errp = (char *)err_arr[ERR_BADADDR_SLASH_UNEXPECTED]; return (-1); } (void) memset(sin6, 0, sizeof (*sin6)); hp = getipnodebyname(str, AF_INET6, 0, &error_num); if (hp) { sin6->sin6_family = hp->h_addrtype; (void) memcpy(&sin6->sin6_addr, hp->h_addr, hp->h_length); freehostent(hp); return (0); } if (error_num == TRY_AGAIN) { *errp = (char *)err_arr[ERR_BADADDR_TRY_AGAIN]; } else { *errp = (char *)err_arr[ERR_BADADDR_ADDRESS]; } return (-1); } /* * char *get_next_token(cpp) * cpp - on input should point within a null terminated buffer. * return value - on output will point to past spaces to first * non-space character that starts a token * cpp - on output will point past non-space token string pointed * by return value. * Side-effects: modifies a potential space character after token * to null to form a null terminated token unless a * double-quote has been seen in the token */ static char * get_next_token(char **cpp) { char *cp = *cpp; char *startcp, *endcp; boolean_t quote_seen = B_FALSE; /* skip leading whitespaces */ while (*cp == CHR_SPACE || *cp == CHR_TAB || *cp == CHR_NEWLINE) cp++; if (*cp == CHR_NULL) { /* end of string */ *cpp = NULL; return (NULL); } startcp = endcp = cp; while (*endcp != CHR_NULL && ((*endcp != CHR_SPACE && *endcp != CHR_TAB) || quote_seen) && *endcp != CHR_NEWLINE) { /* * Within quotes, ignore spaces and tabs as delimiters * and take note of start and end quotes, ignoring * backslash escaped characters. */ if (*endcp == CHR_DOUBLEQUOTE && ((endcp == startcp) || ((endcp > startcp) && *(endcp - 1) != CHR_BACKSLASH))) quote_seen = !quote_seen; endcp++; } if (*endcp != CHR_NULL) *endcp++ = CHR_NULL; *cpp = endcp; return (startcp); } static char * readnextline(FILE *fp) { char *cp; while ((cp = fgets(linebuf, sizeof (linebuf), fp)) != NULL) { err_line_number++; /* NOTE approx - if long lines */ if (*cp == CHR_NEWLINE || *cp == CHR_COMMENT_BEGIN) continue; break; } if (cp == NULL) return (NULL); return (linebuf); } /* * static preshared_entry_t *getnextentry(fp, errmpp) * fp * file pointer to open config file. * errmpp * pointer to pointer to buffer containing error message * return value * NULL when EOF is reached * pointer to valid entry otherwise * * Function scans open input file to get the next entry read from * the file and instantiated in the preshared_entry_t data structure. * In case of invalid syntax entries the function exits and does * not return and process exits. * * Caller holds ps_rwlock as reader. */ static preshared_entry_t * getnextentry(FILE *fp, char **errmpp) { preshared_entry_t *ps; char *cp; /* pointer to scan input */ char *tp; char *errmsg = NULL; int cur_fld_type = -1; /* "uninitialized" */ int entry_done = FALSE; int next_token_expected = PS_TOK_BEGIN_CURLY_BRACE; assert(RW_LOCK_HELD(&ps_rwlock)); *errmpp = NULL; ps = calloc(1, sizeof (preshared_entry_t)); /* Auto-zeroes... */ if (ps == NULL) { errmsg = strerror(errno); goto fatal_error; } ps->pe_refcnt = 1; nextline: /* get next line */ /* skip comment and null lines */ while (((cp = readnextline(fp)) != NULL) && (*cp == CHR_COMMENT_BEGIN || *cp == CHR_NULL)) ; if (cp == NULL) { /* EOF */ PE_REFRELE(ps); return (NULL); } for (;;) { if (entry_done == TRUE) break; tp = get_next_token(&cp); if (tp == NULL) goto nextline; process_newtoken: /* skip rest of line if comment token */ if (*tp == CHR_COMMENT_BEGIN) goto nextline; switch (next_token_expected) { case PS_TOK_BEGIN_CURLY_BRACE: if (*tp != CHR_CURLY_BRACE_OPEN) { errmsg = (char *)err_arr[ ERR_ENTRY_NOBEGINBRACE]; goto bad_syntax; } err_entry_number++; next_token_expected = PS_TOK_FLD_TYPE; /* * Note: We skip tokens assuming whitespace * termination but relax that for this token. * * Reset scan pointer past this token (one character) * so pointer does not miss any tokens that may * be glued to this one with no whitespaces. */ tp++; /* past brace */ if (*tp == CHR_NULL) { /* past whitespace terminated brace */ tp++; } cp = tp; break; case PS_TOK_END_CURLY_BRACE: /* * Do sanity checks on entry and other * post-processing */ if (postprocess_entry(ps, &errmsg) < 0) goto bad_semantics; entry_done = TRUE; break; case PS_TOK_FLD_TYPE: /* * Note: could be end-of-entry too... * here we assume the closing brace is * preceded by a whitespace. We gave some latitude to * opening brace, but not to the closing brace */ if (*tp == CHR_CURLY_BRACE_CLOSE) { /* * End of fields, reset expected token, * Also reset scan token so this */ next_token_expected = PS_TOK_END_CURLY_BRACE; goto process_newtoken; } /* initialize cur_fld_type */ if ((cur_fld_type = getfldtype(tp)) < 0) { errmsg = (char *)err_arr[ERR_INVALID_FIELDNAME]; goto bad_syntax; } if ((ps->pe_flds_mask & cur_fld_type) != 0) { errmsg = (char *)err_arr[ERR_REPEATED_FIELD]; goto bad_syntax; } ps->pe_flds_mask |= cur_fld_type; next_token_expected = PS_TOK_FLD_VALUE; break; case PS_TOK_FLD_VALUE: /* * Note: we assume all value fields do * not embed any whitespace even for complicated * value syntax such as for "range". Is that a * "reasonable" assumption ? * * Note++: All things being strdup()'d below need * to be parsed and put into context dependent data * structures at some point (after entry is read or * right here). */ switch (cur_fld_type) { case PS_FLD_LOCID_TYPE: if ((ps->pe_locidtype = getidtype(tp)) < 0) { errmsg = (char *)err_arr[ ERR_INVALID_LOCALID_TYPE]; goto bad_syntax; } break; case PS_FLD_LOCID: ps->pe_locid = strdup(tp); if (ps->pe_locid == NULL) { errmsg = strerror(errno); goto fatal_error; } break; case PS_FLD_REMID_TYPE: if ((ps->pe_remidtype = getidtype(tp)) < 0) { errmsg = (char *)err_arr[ ERR_INVALID_REMID_TYPE]; goto bad_syntax; } break; case PS_FLD_REMID: ps->pe_remid = strdup(tp); if (ps->pe_remid == NULL) { errmsg = strerror(errno); goto fatal_error; } break; case PS_FLD_IKE_MODE: if ((ps->pe_ike_mode = getikmtype(tp)) < 0) { errmsg = (char *)err_arr[ ERR_INVALID_IKE_MODE]; goto bad_syntax; } break; case PS_FLD_KEY: ps->pe_keybuf = parsekey(tp, &ps->pe_keybuf_bytes, &ps->pe_keybuf_lbits, &errmsg); if (ps->pe_keybuf == NULL) goto fatal_error; else if (errmsg != NULL) { /* * Utter warning on otherwise okay * input. */ (void) fprintf(stderr, gettext("read_preshared: %s\n"), errmsg); } break; default: /* Should never happen - assert ? */ errmsg = (char *)err_arr[ ERR_INVALID_FIELDNAME]; goto fatal_error; } next_token_expected = PS_TOK_FLD_TYPE; break; } } return (ps); /* * TBD any differentiation in these errors ? */ bad_semantics: bad_syntax: fatal_error: if (ps != NULL) PE_REFRELE(ps); *errmpp = errmsg; return (NULL); } /* * Load any preshared keys. * Boolean parameter 'replace' determines whether the newly-read entries * will replace the existing list, or be appended to it. * On success, returns a linked list of preshared entries, and sets the * error message pointer *errmp to NULL. * On failure, returns NULL and sets *errmp to an error message string. * * Holds ps_rwlock as writer. */ char * preshared_load(const char *ps_filename, int ps_fd, boolean_t replace) { preshared_entry_t *ps, *tmp_head = NULL, *tmp_tail = NULL; char *errmp; boolean_t dupfound = B_FALSE; (void) pthread_rwlock_wrlock(&ps_rwlock); /* TODO more paranoia/permissions checks on file needed */ if (ps_filename == NULL) fp = fdopen(ps_fd, "r"); else fp = fopen(ps_filename, "r"); if (fp == NULL) { (void) pthread_rwlock_unlock(&ps_rwlock); if (errno == ENOENT) return (NULL); /* no file == empty file */ return (strerror(errno)); } while ((ps = getnextentry(fp, &errmp)) != NULL) { /* process the new entry */ /* * For now, create a list of just the new entries */ if (!replace) { if (has_dup(ps, &ps_head)) { /* Skip duplicates in master list */ PE_REFRELE(ps); dupfound = B_TRUE; continue; } } assert(ps->pe_refcnt >= 1); if (!append_to_list(ps, &tmp_head, &tmp_tail)) { /* Duplicate found within new list */ dupfound = B_TRUE; } } (void) fclose(fp); if (errmp == NULL) { /* * we read the list in successfully; either * replace or append to global list now. */ if (replace || ps_tail == NULL) { free_preshared_list(&ps_head, &ps_tail); ps_head = tmp_head; ps_tail = tmp_tail; } else { ps_tail->pe_next = tmp_head; ps_tail = tmp_tail; } } else { /* * had problems reading new list; free it */ free_preshared_list(&tmp_head, &tmp_tail); } if (dupfound && (errmp == NULL)) { /* Hack to key off of later, not user visible */ (void) pthread_rwlock_unlock(&ps_rwlock); return ("DUP"); } (void) pthread_rwlock_unlock(&ps_rwlock); return (errmp); } /* * Returns 0 if requested entry not found; 1 if deleted successfully. * Holds ps_rwlock as writer. */ int delete_ps(preshared_entry_t *delp) { preshared_entry_t *curp, *prevp; (void) pthread_rwlock_wrlock(&ps_rwlock); if (ps_head == NULL) { (void) pthread_rwlock_unlock(&ps_rwlock); return (0); } if (delp == ps_head) { /* CORNER CASE: Deletion of last entry */ if (ps_head == ps_tail) ps_tail = NULL; ps_head = delp->pe_next; (void) pthread_rwlock_unlock(&ps_rwlock); PE_REFRELE(delp); return (1); } for (prevp = ps_head, curp = ps_head->pe_next; curp != NULL; prevp = curp, curp = curp->pe_next) { if (curp == delp) break; } if (curp == NULL) { (void) pthread_rwlock_unlock(&ps_rwlock); return (0); } prevp->pe_next = curp->pe_next; if (curp == ps_tail) ps_tail = prevp; (void) pthread_rwlock_unlock(&ps_rwlock); PE_REFRELE(curp); return (1); } /* * return -1 on error, number of preshared entries written on success * Holds ps_rwlock as reader */ int write_preshared(int fd, char **errmp) { preshared_entry_t *pep; FILE *ofile; char *keyp; int written = 0; (void) pthread_rwlock_rdlock(&ps_rwlock); if ((ofile = fdopen(fd, "w+")) == NULL) { *errmp = strerror(errno); (void) pthread_rwlock_unlock(&ps_rwlock); return (-1); } for (pep = ps_head; pep != NULL; pep = pep->pe_next) { const char *mdstr; if ((keyp = getkeystr(pep->pe_keybuf, pep->pe_keybuf_bytes, pep->pe_keybuf_lbits)) == NULL) { *errmp = strerror(errno); (void) pthread_rwlock_unlock(&ps_rwlock); return (-1); } if (pep->pe_locid != NULL) { (void) fprintf(ofile, "{\n" "\tlocalidtype %s\n\tlocalid %s\n", getidstr(pep->pe_locidtype), pep->pe_locid); } else { (void) fprintf(ofile, "{\n\tlocalidtype %s\n\tlocalid ", getidstr(pep->pe_locidtype)); (void) dump_sockaddr( (struct sockaddr *)&pep->pe_locid_sa, (pep->pe_locid_plen > 0) ? pep->pe_locid_plen : 0, B_TRUE, ofile, B_FALSE); (void) fprintf(ofile, "\n"); } if (pep->pe_remid != NULL) { (void) fprintf(ofile, "\tremoteidtype %s\n\tremoteid %s\n", getidstr(pep->pe_remidtype), pep->pe_remid); } else { (void) fprintf(ofile, "\tremoteidtype %s\n\tremoteid ", getidstr(pep->pe_remidtype)); (void) dump_sockaddr( (struct sockaddr *)&pep->pe_remid_sa, (pep->pe_remid_plen > 0) ? pep->pe_remid_plen : 0, B_TRUE, ofile, B_FALSE); (void) fprintf(ofile, "\n"); } mdstr = getikmstr(pep->pe_ike_mode); if (mdstr != NULL) (void) fprintf(ofile, "\tike_mode %s\n", mdstr); (void) fprintf(ofile, "\tkey %s\n}\n", keyp); free(memset(keyp, 0, strlen(keyp))); written++; } (void) fclose(ofile); (void) pthread_rwlock_unlock(&ps_rwlock); return (written); } /* * Convert a PS_ID_* to an SADB_IDENTTYPE_* constant. * Returns SADB_IDENTTYPE_RESERVED on failure. */ int psid2sadb(int psid) { switch (psid) { case PS_ID_IP: case PS_ID_IP4: case PS_ID_IP6: return (SADB_IDENTTYPE_RESERVED); case PS_ID_SUBNET: case PS_ID_SUBNET4: case PS_ID_SUBNET6: return (SADB_IDENTTYPE_PREFIX); case PS_ID_RANGE: case PS_ID_RANGE4: case PS_ID_RANGE6: return (SADB_X_IDENTTYPE_ADDR_RANGE); case PS_ID_ASN1DN: return (SADB_X_IDENTTYPE_DN); case PS_ID_ASN1GN: return (SADB_X_IDENTTYPE_GN); case PS_ID_KEYID: return (SADB_X_IDENTTYPE_KEY_ID); case PS_ID_FQDN: return (SADB_IDENTTYPE_FQDN); case PS_ID_USER_FQDN: return (SADB_IDENTTYPE_USER_FQDN); } return (SADB_IDENTTYPE_RESERVED); } int sadb2psid(int sadb) { switch (sadb) { case SADB_IDENTTYPE_PREFIX: return (PS_ID_SUBNET); case SADB_X_IDENTTYPE_ADDR_RANGE: return (PS_ID_RANGE); case SADB_X_IDENTTYPE_DN: return (PS_ID_ASN1DN); case SADB_X_IDENTTYPE_GN: return (PS_ID_ASN1GN); case SADB_X_IDENTTYPE_KEY_ID: return (PS_ID_KEYID); case SADB_IDENTTYPE_FQDN: return (PS_ID_FQDN); case SADB_IDENTTYPE_USER_FQDN: return (PS_ID_USER_FQDN); } return (0); } /* * Lookup preshared entry by ident * Holds ps_rwlock as reader */ preshared_entry_t * lookup_ps_by_ident(sadb_ident_t *local, sadb_ident_t *remote) { int ltype, rtype; char *lid, *rid; preshared_entry_t *ps; if (local == NULL || remote == NULL) return (NULL); lid = (char *)(local + 1); rid = (char *)(remote + 1); (void) pthread_rwlock_rdlock(&ps_rwlock); for (ps = ps_head; ps != NULL; ps = ps->pe_next) { /* * Have to convert for each preshared (rather than converting * the passed-in sadb ids just once to preshared-style) * because there are multiple PS_ID_* for each SADB_IDENT_*. */ ltype = psid2sadb(ps->pe_locidtype); rtype = psid2sadb(ps->pe_remidtype); if ((local->sadb_ident_type != ltype) || (remote->sadb_ident_type != rtype)) continue; if (ps->pe_locidtype == PS_ID_FQDN || ps->pe_locidtype == PS_ID_USER_FQDN) { /* * Check for case insensitive match. */ if ((strcasecmp(ps->pe_locid, lid) == 0) && (strcasecmp(ps->pe_remid, rid) == 0)) break; } else { /* * Otherwise just go for an exact match. */ if ((strcmp(ps->pe_locid, lid) == 0) && (strcmp(ps->pe_remid, rid) == 0)) break; } } if (ps != NULL) PE_REFHOLD(ps); (void) pthread_rwlock_unlock(&ps_rwlock); return (ps); } /* * Stolen from kernel spd.c - maybe this should go in libipsecutil * * sleazy prefix-length-based compare. */ boolean_t ip_addr_match(uint8_t *addr1, int pfxlen, in6_addr_t *addr2p) { int offset = pfxlen>>3; int bitsleft = pfxlen & 7; uint8_t *addr2 = (uint8_t *)addr2p; /* * and there was much evil.. * XXX should inline-expand the bcmp here and do this 32 bits * or 64 bits at a time.. */ return ((bcmp(addr1, addr2, offset) == 0) && ((bitsleft == 0) || (((addr1[offset] ^ addr2[offset]) & (0xff<<(8-bitsleft))) == 0))); } /* * Lookup preshared entry by v4 or v4 mapped v6 address * Holds ps_rwlock as reader */ preshared_entry_t * lookup_ps_by_in_addr(struct in_addr *local, struct in_addr *remote) { struct in_addr *inp; struct in_addr in; struct sockaddr_in *loc_sin, *rem_sin; struct sockaddr_in6 *loc_sin6, *rem_sin6; preshared_entry_t *ps; boolean_t loc_is_v6, rem_is_v6; int pfxlen; if (local == NULL || remote == NULL) return (NULL); bzero(&in, sizeof (struct in_addr)); (void) pthread_rwlock_rdlock(&ps_rwlock); /* * Also looks up IPv6 id type entries for mapped addresses */ for (ps = ps_head; ps != NULL; ps = ps->pe_next) { /* filter on relevant id type first */ if ((ps->pe_locidtype != PS_ID_IP4 && ps->pe_locidtype != PS_ID_IP6 && ps->pe_locidtype != PS_ID_IP && ps->pe_locidtype != PS_ID_SUBNET4 && ps->pe_locidtype != PS_ID_SUBNET6 && ps->pe_locidtype != PS_ID_SUBNET) || (ps->pe_remidtype != PS_ID_IP4 && ps->pe_remidtype != PS_ID_IP6 && ps->pe_remidtype != PS_ID_IP && ps->pe_remidtype != PS_ID_SUBNET4 && ps->pe_remidtype != PS_ID_SUBNET6 && ps->pe_remidtype != PS_ID_SUBNET)) continue; /* local and/or remote id types invalid */ /* establish format of address */ if ((ps->pe_locidtype == PS_ID_IP6 || ps->pe_locidtype == PS_ID_SUBNET6) || ((ps->pe_locidtype == PS_ID_IP || ps->pe_locidtype == PS_ID_SUBNET) && check_if_v6(ps->pe_locid))) loc_is_v6 = B_TRUE; else loc_is_v6 = B_FALSE; if ((ps->pe_remidtype == PS_ID_IP6 || ps->pe_remidtype == PS_ID_SUBNET6) || ((ps->pe_locidtype == PS_ID_IP || ps->pe_locidtype == PS_ID_SUBNET) && check_if_v6(ps->pe_remid))) rem_is_v6 = B_TRUE; else rem_is_v6 = B_FALSE; loc_sin = (struct sockaddr_in *)&ps->pe_locid_sa; rem_sin = (struct sockaddr_in *)&ps->pe_remid_sa; loc_sin6 = (struct sockaddr_in6 *)&ps->pe_locid_sa; rem_sin6 = (struct sockaddr_in6 *)&ps->pe_remid_sa; /* if v6 format, it is only interesting if v4mapped */ if (loc_is_v6 && !IN6_IS_ADDR_V4MAPPED(&loc_sin6->sin6_addr)) continue; /* not v4 mapped v6 address */ if (rem_is_v6 && !IN6_IS_ADDR_V4MAPPED(&rem_sin6->sin6_addr)) continue; /* not v4 mapped v6 address */ /* match localid */ if (loc_is_v6) { IN6_V4MAPPED_TO_INADDR(&loc_sin6->sin6_addr, &in); inp = ∈ } else { inp = &loc_sin->sin_addr; } if (ps->pe_locid_plen < 0) pfxlen = ADDRBITS_V4; else pfxlen = ps->pe_locid_plen; if (!ip_addr_match((uint8_t *)&inp->s_addr, pfxlen, (in6_addr_t *)&local->s_addr)) continue; /* local id mismatched */ /* match remote id */ if (rem_is_v6) { IN6_V4MAPPED_TO_INADDR(&rem_sin6->sin6_addr, &in); inp = ∈ } else { inp = &rem_sin->sin_addr; } if (ps->pe_remid_plen < 0) pfxlen = ADDRBITS_V4; else pfxlen = ps->pe_remid_plen; if (!ip_addr_match((uint8_t *)&inp->s_addr, pfxlen, (in6_addr_t *)&remote->s_addr)) continue; /* remote id mismatched */ /* match found for both local and remote id */ break; /* out of loop */ } if (ps != NULL) PE_REFHOLD(ps); (void) pthread_rwlock_unlock(&ps_rwlock); return (ps); /* NULL if loop expires on its own */ } /* * Lookup preshared entry by v6 address * Holds ps_rwlock as reader */ preshared_entry_t * lookup_ps_by_in6_addr(struct in6_addr *local, struct in6_addr *remote) { preshared_entry_t *ps; struct sockaddr_in6 *loc_sin6, *rem_sin6; int pfxlen; if (local == NULL || remote == NULL) return (NULL); (void) pthread_rwlock_rdlock(&ps_rwlock); for (ps = ps_head; ps != NULL; ps = ps->pe_next) { /* filter on relevant id type first */ if ((ps->pe_locidtype != PS_ID_IP6 && ps->pe_locidtype != PS_ID_IP && ps->pe_locidtype != PS_ID_SUBNET && ps->pe_locidtype != PS_ID_SUBNET6) || (ps->pe_remidtype != PS_ID_IP6 && ps->pe_remidtype != PS_ID_IP && ps->pe_remidtype != PS_ID_SUBNET && ps->pe_remidtype != PS_ID_SUBNET6)) continue; /* local and/or remote id types invalid */ /* * if PS_ID_IP or PS_ID_SUBNET, it is * only interesting if v6 format */ if ((ps->pe_locidtype == PS_ID_IP || ps->pe_locidtype == PS_ID_SUBNET) && !check_if_v6(ps->pe_locid)) continue; /* not v6 address */ if ((ps->pe_remidtype == PS_ID_IP || ps->pe_remidtype == PS_ID_SUBNET) && !check_if_v6(ps->pe_remid)) continue; /* not v6 address */ loc_sin6 = (struct sockaddr_in6 *)&ps->pe_locid_sa; rem_sin6 = (struct sockaddr_in6 *)&ps->pe_remid_sa; if (ps->pe_locid_plen < 0) pfxlen = ADDRBITS_V6; else pfxlen = ps->pe_locid_plen; if (!ip_addr_match((uint8_t *)&loc_sin6->sin6_addr, pfxlen, (in6_addr_t *)local)) continue; /* local id mismatched */ if (ps->pe_remid_plen < 0) pfxlen = ADDRBITS_V6; else pfxlen = ps->pe_remid_plen; if (!ip_addr_match((uint8_t *)&rem_sin6->sin6_addr, pfxlen, (in6_addr_t *)remote)) continue; /* remote id mismatched */ /* match found for both local and remote id */ break; /* out of loop */ } if (ps != NULL) PE_REFHOLD(ps); (void) pthread_rwlock_unlock(&ps_rwlock); return (ps); /* NULL if loop expires on its own */ } /* * Lookup nth preshared key entry * Holds ps_rwlock as reader */ preshared_entry_t * lookup_nth_ps(int n) { preshared_entry_t *ps; int cnt; (void) pthread_rwlock_rdlock(&ps_rwlock); for (cnt = 0, ps = ps_head; (cnt < n) && (ps != NULL); cnt++, ps = ps->pe_next) ; if (ps != NULL) PE_REFHOLD(ps); (void) pthread_rwlock_unlock(&ps_rwlock); return (ps); } /* * Actual freeing function. */ void free_preshared(preshared_entry_t *pe) { assert(pe->pe_refcnt == 0); free(pe->pe_locid); free(pe->pe_remid); /* Use memset()'s return value to our advantage. */ free(memset(pe->pe_keybuf, 0, pe->pe_keybuf_bytes)); free(pe); } /* * Read in a list of pre-shared keys. Called from main() when the daemon first * starts. */ void preshared_init(boolean_t ignore_errors) { const char *filename = PRESHARED_KEY_FILE; char *errorstr; (void) bunyan_info(log, "Loading preshared keys", BUNYAN_T_END); errorstr = preshared_load(filename, -1, B_TRUE); if (errorstr != NULL) { if (strncmp(errorstr, "DUP", 3) == 0) errorstr = gettext("Duplicate entries ignored\n"); /* * Error handling... Print to terminal, if exists. * Debug logging already taken care of by other functions. * Don't exit because we want to load other policy. */ (void) bunyan_error(log, "Error reading preshared file", BUNYAN_T_STRING, "filename", filename, BUNYAN_T_STRING, "errmsg", errorstr); if (!ignore_errors) err(EXIT_FAILURE, "Fatal errors in %s", filename); } } /* * Called when the daemon catches SIGHUP. * Currently equivalent to preshared_init(). */ void preshared_reload(void) { preshared_init(B_FALSE); } /* * Random Notes: * (a) * Maybe a better syntax for fields is following which eliminates * semantic relationships between two different fields (id and idtype) ??? * E.g. * { * ... * localid ipv4 1.2.3.4 * remoteid rangev4 1.2.3.4-5.6.7.8 * ... * } * Current design causes us to delay certain verifications into a * postprocess_XXX() routine which otherwise could be done while * parsing if syntax was as above. * * (b) * Current error handling has a generic design that prints only * error message, entry number and line number. Other options are * possible (e.g. parsekey() "original" code from danmcd prints * error messages specific to the error context which have more * detailed context). Perhaps we should change to direct printing * of specific error messages globally later, but for now stick * to the simple model coded above. * (c) The getnexttoken() part can be improved by passing it some context * such as "expected token" and maybe the fieldname. This *may* help * allow a more flexible syntax for complex tokens such as ranges. * which cannot currently embed spaces but it would be nice to allow * them to do that and be delimited with a context sensitive delimiter. * (d) Needs detection of duplicates in the database before inserting new * entry. * (e) Can be enhanced to do hashes and better structures than a linked list * of entries. */