/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* enable debug output and some debug asserts */ #undef _IPQOS_CONF_DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _IPQOS_CONF_DEBUG #include #endif #include #include #include #include #include #include #include "ipqosconf.h" #if defined(_IPQOS_CONF_DEBUG) /* debug level */ static int ipqosconf_dbg_flgs = /* */ RBK | MHME | KRET | DIFF | APPLY | L2 | L1 | L0 | 0; #define IPQOSCDBG0(lvl, x)\ if (lvl & ipqosconf_dbg_flgs)\ (void) fprintf(stderr, x) #define IPQOSCDBG1(lvl, x, y)\ if (lvl & ipqosconf_dbg_flgs)\ (void) fprintf(stderr, x, y) #define IPQOSCDBG2(lvl, x, y, z)\ if (lvl & ipqosconf_dbg_flgs)\ (void) fprintf(stderr, x, y, z) #define IPQOSCDBG3(lvl, x, y, z, a)\ if (lvl & ipqosconf_dbg_flgs)\ (void) fprintf(stderr, x, y, z, a) #define IPQOSCDBG4(lvl, x, y, z, a, b)\ if (lvl & ipqosconf_dbg_flgs)\ (void) fprintf(stderr, x, y, z, a, b) #define IPQOSCDBG5(lvl, x, y, z, a, b, c)\ if (lvl & ipqosconf_dbg_flgs)\ (void) fprintf(stderr, x, y, z, a, b, c) #else /* defined(_IPQOS_CONF_DEBUG) && !defined(lint) */ #define IPQOSCDBG0(lvl, x) #define IPQOSCDBG1(lvl, x, y) #define IPQOSCDBG2(lvl, x, y, z) #define IPQOSCDBG3(lvl, x, y, z, a) #define IPQOSCDBG4(lvl, x, y, z, a, b) #define IPQOSCDBG5(lvl, x, y, z, a, b, c) #endif /* defined(_IPQOS_CONF_DEBUG) */ /* function prototypes */ static int modify_params(char *, nvlist_t **, int, boolean_t); static int add_class(char *, char *, int, boolean_t, char *); static int modify_class(char *, char *, int, boolean_t, char *, enum ipp_flags); static int remove_class(char *, char *, int, enum ipp_flags); static int add_filter(char *, ipqos_conf_filter_t *, int); static int modify_filter(char *, ipqos_conf_filter_t *, int); static int remove_filter(char *, char *, int, int); static boolean_t arrays_equal(int *, int *, uint32_t); static int diffclass(ipqos_conf_class_t *, ipqos_conf_class_t *); static int diffparams(ipqos_conf_params_t *, ipqos_conf_params_t *, char *); static int difffilter(ipqos_conf_filter_t *, ipqos_conf_filter_t *, char *); static int add_filters(ipqos_conf_filter_t *, char *, int, boolean_t); static int add_classes(ipqos_conf_class_t *, char *, int, boolean_t); static int modify_items(ipqos_conf_action_t *); static int add_items(ipqos_conf_action_t *, boolean_t); static int add_item(ipqos_conf_action_t *, boolean_t); static int remove_items(ipqos_conf_action_t *, boolean_t); static int remove_item(ipqos_conf_action_t *, boolean_t); static int undo_modifys(ipqos_conf_action_t *, ipqos_conf_action_t *); static int applydiff(ipqos_conf_action_t *, ipqos_conf_action_t *); static int rollback(ipqos_conf_action_t *, ipqos_conf_action_t *); static int rollback_recover(ipqos_conf_action_t *); static ipqos_conf_class_t *classexist(char *, ipqos_conf_class_t *); static ipqos_conf_filter_t *filterexist(char *, int, ipqos_conf_filter_t *); static ipqos_conf_action_t *actionexist(char *, ipqos_conf_action_t *); static int diffnvlists(nvlist_t *, nvlist_t *, char *, int *, place_t); static int diffaction(ipqos_conf_action_t *, ipqos_conf_action_t *); static int diffconf(ipqos_conf_action_t *, ipqos_conf_action_t *); static int readllong(char *, long long *, char **); static int readuint8(char *, uint8_t *, char **); static int readuint16(char *, uint16_t *, char **); static int readint16(char *, int16_t *, char **); static int readint32(char *, int *, char **); static int readuint32(char *, uint32_t *, char **); static int readbool(char *, boolean_t *); static void setmask(int, in6_addr_t *, int); static int readtoken(FILE *, char **); static nvpair_t *find_nvpair(nvlist_t *, char *); static char *prepend_module_name(char *, char *); static int readnvpair(FILE *, FILE *, nvlist_t **, nvpair_t **, ipqos_nvtype_t *, place_t, char *); static int add_aref(ipqos_conf_act_ref_t **, char *, char *); static int readparams(FILE *, FILE *, char *, ipqos_conf_params_t *); static int readclass(FILE *, char *, ipqos_conf_class_t **, char **, int); static int readfilter(FILE *, FILE *, char *, ipqos_conf_filter_t **, char **, int); static FILE *validmod(char *, int *); static int readaction(FILE *, ipqos_conf_action_t **); static int actions_unique(ipqos_conf_action_t *, char **); static int validconf(ipqos_conf_action_t *, int); static int readconf(FILE *, ipqos_conf_action_t **); static int flush(boolean_t *); static int atomic_flush(boolean_t); static int flushconf(); static int writeconf(ipqos_conf_action_t *, char *); static int commitconf(); static int applyconf(char *ifile); static int block_all_signals(); static int restore_all_signals(); static int unlock(int fd); static int lock(); static int viewconf(int); static void usage(); static int valid_name(char *); static int in_cycle(ipqos_conf_action_t *); static int readtype(FILE *, char *, char *, ipqos_nvtype_t *, str_val_nd_t **, char *, boolean_t, place_t *); static int read_int_array_info(char *, str_val_nd_t **, uint32_t *, int *, int *, char *); static str_val_nd_t *read_enum_nvs(char *, char *); static int add_str_val_entry(str_val_nd_t **, char *, uint32_t); static void free_str_val_entrys(str_val_nd_t *); static void get_str_val_value_range(str_val_nd_t *, int *, int *); static int read_enum_value(FILE *, char *, str_val_nd_t *, uint32_t *); static int read_mapped_values(FILE *, nvlist_t **, char *, char *, int); static int read_int_array(FILE *, char *, int **, uint32_t, int, int, str_val_nd_t *); static int str_val_list_lookup(str_val_nd_t *, char *, uint32_t *); static int parse_kparams(char *, ipqos_conf_params_t *, nvlist_t *); static int parse_kclass(ipqos_conf_class_t *, nvlist_t *); static int parse_kfilter(ipqos_conf_filter_t *, nvlist_t *); static int parse_kaction(nvlist_t *, ipqos_actinfo_prm_t *); static int readkconf(ipqos_conf_action_t **); static void print_int_array(FILE *, int *, uint32_t, int, int, str_val_nd_t *, int); static void printrange(FILE *fp, uint32_t, uint32_t); static void printenum(FILE *, uint32_t, str_val_nd_t *); static void printproto(FILE *, uint8_t); static void printport(FILE *, uint16_t); static int printnvlist(FILE *, char *, nvlist_t *, int, ipqos_conf_filter_t *, int, place_t); static int virtual_action(char *); static void free_arefs(ipqos_conf_act_ref_t *); static void print_action_nm(FILE *, char *); static int add_orig_ipqosconf(nvlist_t *); static char *get_originator_nm(uint32_t); static void mark_classes_filters_new(ipqos_conf_action_t *); static void mark_classes_filters_del(ipqos_conf_action_t *); static void mark_config_new(ipqos_conf_action_t *); static int printifname(FILE *, int); static int readifindex(char *, int *); static void cleanup_string_table(char **, int); static int domultihome(ipqos_conf_filter_t *, ipqos_conf_filter_t **, boolean_t); static int dup_filter(ipqos_conf_filter_t *, ipqos_conf_filter_t **, int, int, void *, void *, int); static void free_actions(ipqos_conf_action_t *); static ipqos_conf_filter_t *alloc_filter(); static void free_filter(ipqos_conf_filter_t *); static int read_curl_begin(FILE *); static ipqos_conf_class_t *alloc_class(void); static int diffclasses(ipqos_conf_action_t *old, ipqos_conf_action_t *new); static int difffilters(ipqos_conf_action_t *old, ipqos_conf_action_t *new); static int dup_class(ipqos_conf_class_t *src, ipqos_conf_class_t **dst); static int add_action(ipqos_conf_action_t *act); static int masktocidr(int af, in6_addr_t *mask); static int read_perm_items(int, FILE *, char *, char ***, int *); static int in_string_table(char *stable[], int size, char *string); static void list_end(ipqos_list_el_t **listp, ipqos_list_el_t ***lendpp); static void add_to_list(ipqos_list_el_t **listp, ipqos_list_el_t *el); static int read_cfile_ver(FILE *, char *); static char *quote_ws_string(const char *); static int read_tfile_ver(FILE *, char *, char *); static int ver_str_to_int(char *); static void printuser(FILE *fp, uid_t uid); static int readuser(char *str, uid_t *uid); /* * macros to call list functions with the more complex list element type * cast to the skeletal type iqpos_list_el_t. */ #define GET_LIST_END(list, end)\ list_end((ipqos_list_el_t **)list, (ipqos_list_el_t ***)end) #define ADD_TO_LIST(list, el)\ add_to_list((ipqos_list_el_t **)list, (ipqos_list_el_t *)el) /* * Macros to produce a quoted string containing the value of a * preprocessor macro. For example, if SIZE is defined to be 256, * VAL2STR(SIZE) is "256". This is used to construct format * strings for scanf-family functions below. */ #define QUOTE(x) #x #define VAL2STR(x) QUOTE(x) /* globals */ /* table of supported parameter types and enum value */ static str_val_t nv_types[] = { {"uint8", IPQOS_DATA_TYPE_UINT8}, {"int16", IPQOS_DATA_TYPE_INT16}, {"uint16", IPQOS_DATA_TYPE_UINT16}, {"int32", IPQOS_DATA_TYPE_INT32}, {"uint32", IPQOS_DATA_TYPE_UINT32}, {"boolean", IPQOS_DATA_TYPE_BOOLEAN}, {"string", IPQOS_DATA_TYPE_STRING}, {"action", IPQOS_DATA_TYPE_ACTION}, {"address", IPQOS_DATA_TYPE_ADDRESS}, {"port", IPQOS_DATA_TYPE_PORT}, {"protocol", IPQOS_DATA_TYPE_PROTO}, {"enum", IPQOS_DATA_TYPE_ENUM}, {"ifname", IPQOS_DATA_TYPE_IFNAME}, {"mindex", IPQOS_DATA_TYPE_M_INDEX}, {"int_array", IPQOS_DATA_TYPE_INT_ARRAY}, {"user", IPQOS_DATA_TYPE_USER}, {"", 0} }; /* table of name to id mappings for originator field */ static str_val_t originators[] = { {IPP_CONFIG_NAME_PERMANENT, IPP_CONFIG_PERMANENT}, {IPP_CONFIG_NAME_IPQOSCONF, IPP_CONFIG_IPQOSCONF}, {IPP_CONFIG_NAME_FTPCL, IPP_CONFIG_FTPCL}, {"", -1} }; /* current parse line */ static int lineno; /* verbose output flag */ static int verbose; /* use syslog for msg reporting flag */ static int use_syslog; #ifdef _IPQOS_CONF_DEBUG /* * flag used to indicate that a rollback should be carried out regardless. * Only settable during debug. */ static int force_rback = 0; #endif /* _IPQOS_CONF_DEBUG */ /* * delivers messages to either syslog or stderr, dependant upon the * the state of the flags use_syslog and verbose. The type * of the msg as given in msg_type is indicated in the output msg. * * valid message types are: * o MT_ERROR (standard error message) * o MT_ENOSTR (error message with system error string appended) * o MT_WARNING (warning message) * o MT_LOG (logging message) * * Log messages only go to syslog. Warning messages only go to stderr * and only when the verbose flag is set. All other messages go by default * to the console; to syslog if syslog flag set, and to both if both * syslog and verbose are set. * */ /*PRINTFLIKE2*/ static void ipqos_msg(enum msg_type msgt, char *format, ...) { va_list ap; char str_buf[IPQOS_MSG_BUF_SZ]; char fmt_buf[IPQOS_MSG_BUF_SZ]; char *cp; IPQOSCDBG0(L1, "In ipqos_msg:\n"); va_start(ap, format); /* * send msgs to syslog if use_syslog set (except warning msgs), * or a log msg. */ if ((use_syslog && (msgt != MT_WARNING)) || msgt == MT_LOG) { /* fill in format string */ (void) vsnprintf(str_buf, IPQOS_MSG_BUF_SZ, format, ap); /* * print message to syslog with appropriate severity */ if (msgt == MT_ERROR) { syslog(LOG_ERR, str_buf); } else if (msgt == MT_LOG) { syslog(LOG_INFO, str_buf); /* * for errno message type suffix with %m for syslog to * interpret. */ } else if (msgt == MT_ENOSTR) { /* * remove any newline in message parameter. * syslog will reapply a newline for us later. */ if ((cp = strchr(str_buf, '\n')) != NULL) *cp = '\0'; (void) strlcat(str_buf, ": %m", IPQOS_MSG_BUF_SZ); syslog(LOG_ERR, str_buf); } } /* * send msgs to stderr if use_syslog not set (except log msgs), or * if verbose set. */ if ((!use_syslog && (msgt != MT_LOG)) || (verbose)) { /* * prefix message with appropriate severity string */ if (msgt == MT_ERROR) { (void) strlcpy(fmt_buf, gettext("Error: "), IPQOS_MSG_BUF_SZ); } else if (msgt == MT_WARNING) { if (!verbose) { /* don't show warn msg if !verbose */ va_end(ap); return; } (void) strlcpy(fmt_buf, gettext("Warning: "), IPQOS_MSG_BUF_SZ); } else if (msgt == MT_ENOSTR) { (void) strlcpy(fmt_buf, gettext("Error: "), IPQOS_MSG_BUF_SZ); } else if (msgt == MT_LOG) { (void) strlcpy(fmt_buf, gettext("Notice: "), IPQOS_MSG_BUF_SZ); } (void) strlcat(fmt_buf, format, IPQOS_MSG_BUF_SZ); /* * for errno message type suffix message with errno string */ if (msgt == MT_ENOSTR) { /* * get rid of any newline in passed message. * we'll apply another later. */ if ((cp = strchr(fmt_buf, '\n')) != NULL) *cp = '\0'; (void) strlcat(fmt_buf, ": ", IPQOS_MSG_BUF_SZ); (void) strlcat(fmt_buf, strerror(errno), IPQOS_MSG_BUF_SZ); } /* * append a newline to message if not one already. */ if ((cp = strchr(fmt_buf, '\n')) == NULL) (void) strlcat(fmt_buf, "\n", IPQOS_MSG_BUF_SZ); (void) vfprintf(stderr, fmt_buf, ap); } va_end(ap); } /* **************** kernel filter/class/params manipulation fns *********** */ /* * modify the kernel parameters of the action action_nm using the nvlist * parameter nvl and setting the stats according to stats_enable. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int modify_params( char *action_name, nvlist_t **nvl, int module_version, boolean_t stats_enable) { int res; int created = 0; IPQOSCDBG1(APPLY, "In modify_params: action: %s\n", action_name); /* create nvlist if NULL */ if (*nvl == NULL) { created++; res = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); return (IPQOS_CONF_ERR); } } /* add params modify config type */ res = nvlist_add_byte(*nvl, IPP_CONFIG_TYPE, IPP_SET); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } /* * add module version */ if (nvlist_add_uint32(*nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add stats_enable */ res = nvlist_add_uint32(*nvl, IPP_ACTION_STATS_ENABLE, (uint32_t)stats_enable); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add ipqosconf as originator */ res = add_orig_ipqosconf(*nvl); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* call lib to do modify */ res = ipp_action_modify(action_name, nvl, 0); if (res != 0) { /* invalid parameters */ if (errno == EINVAL) { ipqos_msg(MT_ERROR, gettext("Invalid parameters for action %s.\n"), action_name); } else if (errno == ENOENT) { ipqos_msg(MT_ERROR, gettext("Mandatory parameter missing for " "action %s.\n"), action_name); } else { /* unexpected error */ ipqos_msg(MT_ERROR, gettext("Failed to modify action " "%s parameters: %s.\n"), action_name, strerror(errno)); } goto fail; } return (IPQOS_CONF_SUCCESS); fail: if (created && *nvl != NULL) { nvlist_free(*nvl); *nvl = NULL; } return (IPQOS_CONF_ERR); } /* * add a class to the kernel action action_name called class_name with * stats set according to stats_enable and the first action set to * first_action. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int add_class( char *action_name, char *class_name, int module_version, boolean_t stats_enable, char *first_action) { nvlist_t *nvl; IPQOSCDBG4(APPLY, "add_class: action: %s, class: %s, " "first_action: %s, stats: %s\n", action_name, class_name, first_action, (stats_enable == B_TRUE ? "true" : "false")); /* create nvlist */ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); return (IPQOS_CONF_ERR); } /* add 'add class' config type */ if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_ADD_CLASS) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } /* * add module version */ if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add class name */ if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, class_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } /* add next action */ if (nvlist_add_string(nvl, CLASSIFIER_NEXT_ACTION, first_action) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } /* add stats_enable */ if (nvlist_add_uint32(nvl, CLASSIFIER_CLASS_STATS_ENABLE, (uint32_t)stats_enable) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add ipqosconf as originator */ if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) { goto fail; } /* call lib to do modify */ if (ipp_action_modify(action_name, &nvl, 0) != 0) { /* ipgpc max classes */ if (errno == ENOSPC && strcmp(action_name, IPGPC_CLASSIFY) == 0) { ipqos_msg(MT_ERROR, gettext("Max number of classes reached in %s.\n"), IPGPC_NAME); /* other errors */ } else { ipqos_msg(MT_ERROR, gettext("Failed to create class %s in action " "%s: %s.\n"), class_name, action_name, strerror(errno)); } goto fail; } return (IPQOS_CONF_SUCCESS); fail: nvlist_free(nvl); return (IPQOS_CONF_ERR); } /* * modify the class in the kernel action action_name called class_name with * stats set according to stats_enable and the first action set to * first_action. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int modify_class( char *action_name, char *class_name, int module_version, boolean_t stats_enable, char *first_action, enum ipp_flags flags) { nvlist_t *nvl; IPQOSCDBG5(APPLY, "modify_class: action: %s, class: %s, first: %s, " "stats: %s, flags: %x\n", action_name, class_name, first_action, stats_enable == B_TRUE ? "true" : "false", flags); /* create nvlist */ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); return (IPQOS_CONF_ERR); } /* add 'modify class' config type */ if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_MODIFY_CLASS) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } /* * add module version */ if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add class name */ if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, class_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } /* add next action */ if (nvlist_add_string(nvl, CLASSIFIER_NEXT_ACTION, first_action) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } /* add stats enable */ if (nvlist_add_uint32(nvl, CLASSIFIER_CLASS_STATS_ENABLE, (uint32_t)stats_enable) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add originator ipqosconf */ if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) { goto fail; } /* call lib to do modify */ if (ipp_action_modify(action_name, &nvl, flags) != 0) { /* generic error message */ ipqos_msg(MT_ERROR, gettext("Modifying class %s in action %s failed: %s.\n"), class_name, action_name, strerror(errno)); goto fail; } return (IPQOS_CONF_SUCCESS); fail: nvlist_free(nvl); return (IPQOS_CONF_ERR); } /* * removes the class class_name from the kernel action action_name. The * flags argument can currently be set to IPP_ACTION_DESTROY which will * result in the action this class references being destroyed. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int remove_class( char *action_name, char *class_name, int module_version, enum ipp_flags flags) { nvlist_t *nvl; IPQOSCDBG3(APPLY, "remove_class: action: %s, class: %s, " "flags: %x\n", action_name, class_name, flags); /* allocate nvlist */ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); return (IPQOS_CONF_ERR); } /* add 'remove class' config type */ if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_REMOVE_CLASS) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } /* * add module version */ if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } /* add class name */ if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, class_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } if (ipp_action_modify(action_name, &nvl, flags) != 0) { /* generic error message */ ipqos_msg(MT_ERROR, gettext("Removing class %s in action %s failed: %s.\n"), class_name, action_name, strerror(errno)); goto fail; } return (IPQOS_CONF_SUCCESS); fail: nvlist_free(nvl); return (IPQOS_CONF_ERR); } /* * add the filter flt to the kernel action named action_name. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int add_filter( char *action_name, ipqos_conf_filter_t *flt, int module_version) { nvlist_t *nvl = flt->nvlist; char ipvsbuf[IPQOS_INT_STR_LEN]; IPQOSCDBG4(APPLY, "add_filter: action: %s, filter: %s, " "instance: %d, class: %s\n", action_name, flt->name, flt->instance, flt->class_name); /* add 'add filter' config type to filter nvlist */ if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_ADD_FILTER) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); return (IPQOS_CONF_ERR); } /* * add module version */ if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); return (IPQOS_CONF_ERR); } /* add filter name to nvlist */ if (nvlist_add_string(nvl, CLASSIFIER_FILTER_NAME, flt->name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* add class name to nvlist */ if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, flt->class_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* add ipqosconf as originator to nvlist */ if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* add ipgpc specific nv entrys */ if (strcmp(action_name, IPGPC_CLASSIFY) == 0) { /* add src and dst nodes to nvlist if present */ if (flt->src_nd_name != NULL && nvlist_add_string(nvl, IPGPC_SADDR_HOSTNAME, flt->src_nd_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } if (flt->dst_nd_name != NULL && nvlist_add_string(nvl, IPGPC_DADDR_HOSTNAME, flt->dst_nd_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* * add ip_version to private list element if present. * NOTE: this value is of only real use to ipqosconf so * it is placed in this opaque private field. */ if (flt->ip_versions != 0) { (void) sprintf(ipvsbuf, "%d", flt->ip_versions); if (nvlist_add_string(nvl, IPGPC_FILTER_PRIVATE, ipvsbuf) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } } /* add filter instance if present */ if (nvlist_add_int32(nvl, IPGPC_FILTER_INSTANCE, flt->instance) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int32"); return (IPQOS_CONF_ERR); } } if (ipp_action_modify(action_name, &flt->nvlist, 0) != 0) { /* invalid parameters */ if (errno == EINVAL) { ipqos_msg(MT_ERROR, gettext("Invalid/missing parameters for filter " "%s in action %s.\n"), flt->name, action_name); /* max ipgpc filters/classes */ } else if (errno == ENOSPC && strcmp(action_name, IPGPC_CLASSIFY) == 0) { ipqos_msg(MT_ERROR, gettext("Max number of filters " "reached in action %s.\n"), IPGPC_NAME); /* anything other errnos */ } else { ipqos_msg(MT_ERROR, gettext("Failed to create filter %s in action " "%s: %s.\n"), flt->name, action_name, strerror(errno)); } return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* * modify the filter flt in the kernel action named action_name. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int modify_filter( char *action_name, ipqos_conf_filter_t *flt, int module_version) { nvlist_t *nvl = flt->nvlist; char ipvsbuf[IPQOS_INT_STR_LEN]; IPQOSCDBG4(APPLY, "modify_filter: action: %s, filter: %s, " "instance: %d, class: %s\n", action_name, flt->name, flt->instance, flt->class_name); /* show src address and dst address if present */ #ifdef _IPQOS_CONF_DEBUG if (ipqosconf_dbg_flgs & APPLY) { uint_t tmp; in6_addr_t *add; char st[100]; if (nvlist_lookup_uint32_array(nvl, IPGPC_SADDR, (uint32_t **)&add, &tmp) == 0) { (void) fprintf(stderr, "saddr: %s\n", inet_ntop(AF_INET6, add, st, 100)); } if (nvlist_lookup_uint32_array(nvl, IPGPC_DADDR, (uint32_t **)&add, &tmp) == 0) { (void) fprintf(stderr, "daddr: %s\n", inet_ntop(AF_INET6, add, st, 100)); } } #endif /* _IPQOS_CONF_DEBUG */ /* add 'modify filter' config type to filters nvlist */ if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_MODIFY_FILTER) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); return (IPQOS_CONF_ERR); } /* * add module version */ if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); return (IPQOS_CONF_ERR); } /* add filter name to nvlist */ if (nvlist_add_string(nvl, CLASSIFIER_FILTER_NAME, flt->name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* add class name to nvlist */ if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, flt->class_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* add originator ipqosconf to nvlist */ if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* add ipgpc specific nvpairs */ if (strcmp(action_name, IPGPC_CLASSIFY) == 0) { /* add src and dst nodes to nvlist if present */ if (flt->src_nd_name && nvlist_add_string(nvl, IPGPC_SADDR_HOSTNAME, flt->src_nd_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } if (flt->dst_nd_name && nvlist_add_string(nvl, IPGPC_DADDR_HOSTNAME, flt->dst_nd_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* * add ip_version to private list element if present. * NOTE: this value is of only real use to ipqosconf so * it is placed in this opaque private field. */ if (flt->ip_versions != 0) { (void) sprintf(ipvsbuf, "%d", flt->ip_versions); if (nvlist_add_string(nvl, IPGPC_FILTER_PRIVATE, ipvsbuf) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } } /* add filter instance if present */ if (nvlist_add_int32(nvl, IPGPC_FILTER_INSTANCE, flt->instance) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int32"); return (IPQOS_CONF_ERR); } } if (ipp_action_modify(action_name, &flt->nvlist, 0) != 0) { /* invalid parameters */ if (errno == EINVAL) { ipqos_msg(MT_ERROR, gettext("Missing/Invalid " "parameter for filter %s in action %s.\n"), flt->name, action_name); /* any other errnos */ } else { ipqos_msg(MT_ERROR, gettext("Failed to modify filter %s in action %s: " "%s.\n"), flt->name, action_name, strerror(errno)); } return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* * remove the filter named filter_name instance number instance from the * kernel action action_name. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int remove_filter( char *action_name, char *filter_name, int instance, int module_version) { nvlist_t *nvl; IPQOSCDBG2(APPLY, "remove_filter: action: %s, filter: %s\n", action_name, filter_name); /* create nvlist */ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); return (IPQOS_CONF_ERR); } /* add 'remove filter' config type to list */ if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_REMOVE_FILTER) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); return (IPQOS_CONF_ERR); } /* * add module version */ if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION, (uint32_t)module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); return (IPQOS_CONF_ERR); } /* add filter name to list */ if (nvlist_add_string(nvl, CLASSIFIER_FILTER_NAME, filter_name) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); return (IPQOS_CONF_ERR); } /* add instance number if part of multi-instance filter */ if (instance != -1 && nvlist_add_int32(nvl, IPGPC_FILTER_INSTANCE, instance) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int32"); return (IPQOS_CONF_ERR); } /* call into lib to remove */ if (ipp_action_modify(action_name, &nvl, 0) != 0) { /* generic error message */ ipqos_msg(MT_ERROR, gettext("Removing filter %s in action %s failed: %s.\n"), filter_name, action_name, strerror(errno)); return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* ******************************************************************* */ /* * add originator nvpair set to ipqosconf to nvl. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int add_orig_ipqosconf(nvlist_t *nvl) { if (nvlist_add_uint32(nvl, IPP_CONFIG_ORIGINATOR, IPP_CONFIG_IPQOSCONF) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32: originator:"); return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* ************************* differencing functions ************************ */ /* * compares the contents of arrays array1 and array2, both of size size, and * returns B_TRUE or B_FALSE if they're equal or not respectively. * RETURNS: B_TRUE if equal, else B_FALSE. */ static boolean_t arrays_equal( int array1[], int array2[], uint32_t size) { int x; for (x = 0; x < size; x++) { if (array1[x] != array2[x]) return (B_FALSE); } return (B_TRUE); } /* * difference class old against class new. It marks the new class as * modified if it is different. * RETURNS: IPQOS_CONF_SUCCESS. */ static int diffclass( ipqos_conf_class_t *old, ipqos_conf_class_t *new) { IPQOSCDBG0(L0, "In diffclass:\n"); /* two different spec'd actions */ if (strcmp(old->alist->name, new->alist->name) != 0) { IPQOSCDBG1(DIFF, "marking class %s as modified\n", new->name); new->modified = B_TRUE; return (IPQOS_CONF_SUCCESS); } /* different stats values */ if (old->stats_enable != new->stats_enable) { IPQOSCDBG1(DIFF, "marking class %s as modified\n", new->name); new->modified = B_TRUE; return (IPQOS_CONF_SUCCESS); } return (IPQOS_CONF_SUCCESS); } /* * difference params set old against params set new of module module_name. It * marks the new params as modified if different. * RETURNS: if error IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS. */ static int diffparams( ipqos_conf_params_t *old, ipqos_conf_params_t *new, char *module_name) { int diff; int res; IPQOSCDBG0(L0, "In diffparams\n"); /* diff stats */ if (old->stats_enable != new->stats_enable) { new->modified = B_TRUE; return (IPQOS_CONF_SUCCESS); } /* diff module specific params */ res = diffnvlists(old->nvlist, new->nvlist, module_name, &diff, PL_PARAMS); if (res != IPQOS_CONF_SUCCESS) { return (res); } if (diff) { new->modified = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * differences filter old against filter new of module module_name. It marks * filter new as different if so. * RETURNS: if error IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS. */ static int difffilter( ipqos_conf_filter_t *old, ipqos_conf_filter_t *new, char *module_name) { int res; int diff; IPQOSCDBG0(L0, "In difffilter\n"); /* compare class name */ if (strcmp(old->class_name, new->class_name) != 0) { IPQOSCDBG1(DIFF, "Marking filter %s as modified\n", new->name); new->modified = B_TRUE; return (IPQOS_CONF_SUCCESS); } /* compare module specific params */ res = diffnvlists(old->nvlist, new->nvlist, module_name, &diff, PL_FILTER); if (res != IPQOS_CONF_SUCCESS) { return (res); } if (diff) { IPQOSCDBG1(DIFF, "Marking filter %s as modified\n", new->name); new->modified = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * mark all the filters and classes in parameter action either * for deletion (if they are ipqosconf originated) or for modification. */ static void mark_classes_filters_del(ipqos_conf_action_t *action) { ipqos_conf_filter_t *flt; ipqos_conf_class_t *cls; IPQOSCDBG1(L1, "In mark_classes_filters_del: action: %s\n", action->name); /* mark all non-permanent filters for del and permanent to modify */ for (flt = action->filters; flt; flt = flt->next) { if (flt->originator == IPP_CONFIG_PERMANENT) { IPQOSCDBG1(DIFF, "Marking prm filter %s as modified.\n", flt->name); flt->modified = B_TRUE; } else { IPQOSCDBG1(DIFF, "Marking filter %s as del.\n", flt->name); flt->todel = B_TRUE; } } /* mark all non-permanent classes for del and permanent to modify */ for (cls = action->classes; cls; cls = cls->next) { if (cls->originator == IPP_CONFIG_PERMANENT) { IPQOSCDBG1(DIFF, "Marking prm class %s as modified.\n", cls->name); cls->modified = B_TRUE; } else { IPQOSCDBG1(DIFF, "Marking class %s as del.\n", cls->name); cls->todel = B_TRUE; } } } /* * mark all classes and filters either new (non-permanent) or modified. */ static void mark_classes_filters_new(ipqos_conf_action_t *action) { ipqos_conf_filter_t *flt; ipqos_conf_class_t *cls; IPQOSCDBG1(L1, "In mark_classes_filters_new: action: %s\n", action->name); /* mark all permanent filters as modified and all others new */ for (flt = action->filters; flt; flt = flt->next) { if (flt->originator == IPP_CONFIG_PERMANENT) { IPQOSCDBG1(DIFF, "Marking prm filter %s as modified.\n", flt->name); flt->modified = B_TRUE; action->modified = B_TRUE; } else { IPQOSCDBG1(DIFF, "Marking filter %s as new.\n", flt->name); flt->new = B_TRUE; } } /* mark all permanent classes as modified and all others new */ for (cls = action->classes; cls; cls = cls->next) { if (cls->originator == IPP_CONFIG_PERMANENT) { IPQOSCDBG1(DIFF, "Marking prm class %s as modified.\n", cls->name); cls->modified = B_TRUE; action->modified = B_TRUE; } else { IPQOSCDBG1(DIFF, "Marking class %s as new.\n", cls->name); cls->new = B_TRUE; } } } /* * Marks all the actions and their constituent elements in conf * as new. */ static void mark_config_new( ipqos_conf_action_t *conf) { while (conf != NULL) { IPQOSCDBG1(DIFF, "Marking action %s as new\n", conf->name); mark_classes_filters_new(conf); conf->new = B_TRUE; conf->visited = 0; conf = conf->next; } } /* * differences the configuration in new against old marking the actions * and their contents appropriately. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int diffconf( ipqos_conf_action_t *old, ipqos_conf_action_t *new) { int res; ipqos_conf_action_t *act; ipqos_conf_action_t *tmp; IPQOSCDBG0((L0 | DIFF), "In diffconf\n"); /* check the new actions against the old */ for (act = new; act; act = act->next) { /* if action not in old mark it and it's contents as new */ if ((tmp = actionexist(act->name, old)) == NULL) { IPQOSCDBG1(DIFF, "marking act %s as new\n", act->name); act->new = B_TRUE; mark_classes_filters_new(act); continue; } /* if action in old diff old against new */ res = diffaction(tmp, act); if (res != IPQOS_CONF_SUCCESS) { return (res); } } /* * mark actions, and their contents, in old but not new that were * created by us for del. */ for (act = old; act; act = act->next) { if (act->params->originator == IPP_CONFIG_IPQOSCONF && actionexist(act->name, new) == NULL) { IPQOSCDBG1(DIFF, "marking act %s for del\n", act->name); act->todel = B_TRUE; mark_classes_filters_del(act); } } return (IPQOS_CONF_SUCCESS); } /* * differences action old against action new, comparing its classes, filters * and parameters. If it is different the new action is marked as modified * and it's different sub-objects are also marked approriately. * RETURNS: IPQOS_CONF_ERR if error, else IPQOS_CONF_SUCCESS. */ static int diffaction( ipqos_conf_action_t *old, ipqos_conf_action_t *new) { int res; IPQOSCDBG0(L0, "In diffaction\n"); /* compare and mark classes */ res = diffclasses(old, new); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* compare and mark filters */ res = difffilters(old, new); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* compare and mark parameters */ res = diffparams(old->params, new->params, old->module); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* mark action as modified if params are */ if (new->params->modified == B_TRUE) { IPQOSCDBG1(DIFF, "Marking params for action %s modified\n", new->name); new->modified = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * differences the set of classes in new against those in old, marking any * that are new/modified, approriately in the new class, and any removed * in the old class appropriately. Also marks the action which has had an * object within marked, as modified. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int diffclasses( ipqos_conf_action_t *old, ipqos_conf_action_t *new) { ipqos_conf_class_t *cls; ipqos_conf_class_t *tmpc; ipqos_conf_class_t *ncls; int res; /* loop through old classes checking for classes not present in new */ for (cls = old->classes; cls; cls = cls->next) { if (classexist(cls->name, new->classes) == NULL) { /* if we created original class mark for deletion */ if (cls->originator == IPP_CONFIG_IPQOSCONF) { IPQOSCDBG1(DIFF, "marking class %s for del\n", cls->name); cls->todel = B_TRUE; /* mark old action */ old->modified = B_TRUE; /* * if permanent class and next action created by us * copy it, set it's next action to continue and * add it to new action. This will cause the class * to be marked as and modified. This returns the class * to an assumed default state and prevents the * case where the class is pointing at an action * we want to remove and therefore couldn't without * this forced modify. */ } else if (cls->originator == IPP_CONFIG_PERMANENT && cls->alist->action && /* not virtual action */ cls->alist->action->params->originator == IPP_CONFIG_IPQOSCONF) { /* copy class */ res = dup_class(cls, &ncls); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* set next action to continue */ (void) strcpy(ncls->alist->name, IPP_ANAME_CONT); /* add to news classes to be diffed below */ ADD_TO_LIST(&new->classes, ncls); } } } /* loop through new classes checking for new / modified classes */ for (cls = new->classes; cls; cls = cls->next) { /* new ipqosconf class */ if ((tmpc = classexist(cls->name, old->classes)) == NULL || (tmpc->originator != IPP_CONFIG_IPQOSCONF && tmpc->originator != IPP_CONFIG_PERMANENT)) { IPQOSCDBG1(DIFF, "marking class %s new\n", cls->name); cls->new = B_TRUE; new->modified = B_TRUE; /* mark new action */ continue; /* existing ipqosconf/perm class */ } else { res = diffclass(tmpc, cls); if (res != IPQOS_CONF_SUCCESS) { return (res); } if (cls->modified == B_TRUE) { new->modified = B_TRUE; } } } return (IPQOS_CONF_SUCCESS); } /* * differences the set of filters in new against those in old, marking any * that are new/modified, approriately in the new filter/s, and any removed * in the old filter appropriately. Also marks the action which has had an * object within marked, as modified. * RETURNS: IPQOS_CONF_SUCCESS (we return an int for symmetry with diffclasses * and difffparams). */ static int difffilters( ipqos_conf_action_t *old, ipqos_conf_action_t *new) { ipqos_conf_filter_t *flt; ipqos_conf_filter_t *tmpf; int maxi; int newi; int res; /* check for new/modified filters */ for (flt = new->filters; flt; flt = flt->next) { /* new ipqosconf filter */ if ((tmpf = filterexist(flt->name, -1, old->filters)) == NULL) { /* mark all instances of this filter as new */ for (;;) { IPQOSCDBG1(DIFF, "Marking filter %s as " "new\n", flt->name); flt->new = B_TRUE; if (flt->next == NULL || strcmp(flt->next->name, flt->name) != 0) { break; } flt = flt->next; } new->modified = B_TRUE; /* mark new action */ /* ipqosconf/permanent filter existed */ } else { /* * if ip node name force filter refresh - ie. mark * all old filter instances as todel and all new new. */ if (tmpf->src_nd_name || tmpf->dst_nd_name || flt->src_nd_name || flt->dst_nd_name) { /* init max previous filter instance */ maxi = tmpf->instance; /* mark old instances for deletion */ do { IPQOSCDBG2(DIFF, "Marking filter " "%s, instance %d for del\n", tmpf->name, tmpf->instance); tmpf->todel = B_TRUE; /* * check and update previous instance * max. */ if (tmpf->instance > maxi) { maxi = tmpf->instance; } tmpf = tmpf->next; } while (tmpf != NULL && strcmp(tmpf->name, flt->name) == 0); /* * use the max previous instance + 1 for * the start of the new instance numbers. */ newi = (uint32_t)++maxi % INT_MAX; /* * mark new instances for addition and * give new instance number. */ for (;;) { IPQOSCDBG2(DIFF, "Marking filter " "%s, instance %d as new\n", flt->name, newi); flt->new = B_TRUE; flt->instance = newi++; if (flt->next == NULL || strcmp(flt->next->name, flt->name) != 0) { break; } flt = flt->next; } new->modified = B_TRUE; /* mark new action */ /* mark old action */ old->modified = B_TRUE; /* non-node name filter */ } else { /* compare and mark as modified if diff */ res = difffilter(tmpf, flt, new->module); if (res != IPQOS_CONF_SUCCESS) { return (res); } if (flt->modified == B_TRUE) { /* mark action if diff */ new->modified = B_TRUE; } } } } /* * Check for deleted ipqosconf created filters and mark * any found for deletion. * For non-ipqosconf generated filters, including permanent * ones (none of these exist at the moment) we just leave * the filter unmarked. */ for (flt = old->filters; flt; flt = flt->next) { if (flt->originator == IPP_CONFIG_IPQOSCONF && filterexist(flt->name, -1, new->filters) == NULL) { /* mark all old instances for deletions */ for (;;) { IPQOSCDBG2(DIFF, "marking flt %s, inst %d " "for del\n", flt->name, flt->instance); flt->todel = B_TRUE; old->modified = B_TRUE; /* mark old action */ if (flt->next == NULL || strcmp(flt->next->name, flt->name) != 0) { break; } flt = flt->next; } } } return (IPQOS_CONF_SUCCESS); } /* * differences the elements of nvlists old and new using the types file * for module name to interpret the element types. It sets pdiff to either * 0 or 1 if they are the same or different respectively. * RETURNS: IPQOS_CONF_ERR if any errors, else IPQOS_CONF_SUCCESS. */ static int diffnvlists( nvlist_t *old, nvlist_t *new, char *module_name, int *pdiff, place_t place) { int first_pass = 1; nvlist_t *tmp; int res; nvpair_t *nvp; FILE *tfp; str_val_nd_t *enum_nvs; char dfltst[IPQOS_VALST_MAXLEN+1] = ""; char *lo; ipqos_nvtype_t type; char *nme; int diff; int openerr; IPQOSCDBG0(L0, "In diffnvlists\n"); /* open stream to types file */ tfp = validmod(module_name, &openerr); if (tfp == NULL) { if (openerr) { ipqos_msg(MT_ENOSTR, "fopen"); } return (IPQOS_CONF_ERR); } start: /* * loop through each of the elements of the new list comparing * it with the old one if present. If the old one isn't present * then it is compared with the default value for that type (if * set). Any time the values are determined to be different * or the default value is to be used but isn't present the diff * param is set to 1 and we return. * * If the loop runs its course then the new and old nvlists are * reversed and the loop is entered for a second time. */ nvp = nvlist_next_nvpair(new, NULL); while (nvp != NULL) { /* get name */ nme = nvpair_name(nvp); /* * get type. */ place = PL_ANY; res = readtype(tfp, module_name, SHORT_NAME(nme), &type, &enum_nvs, dfltst, B_TRUE, &place); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* init diff to 1 */ diff = 1; switch (type) { /* interface name */ case IPQOS_DATA_TYPE_IFINDEX: { uint32_t ifidx; uint32_t oifidx; /* get new value */ (void) nvpair_value_uint32(nvp, &ifidx); /* compare against old if present */ res = nvlist_lookup_uint32(old, nme, &oifidx); if (res == 0) { /* diff values */ diff = (ifidx != oifidx); /* not in old so see if new value is default */ } else { diff = (ifidx != 0); } break; } /* protocol */ case IPQOS_DATA_TYPE_PROTO: { uchar_t proto; uchar_t oproto; (void) nvpair_value_byte(nvp, &proto); res = nvlist_lookup_byte(old, nme, &oproto); if (res == 0) { diff = (proto != oproto); } else { diff = (proto != 0); } break; } /* port */ case IPQOS_DATA_TYPE_PORT: { uint16_t port; uint16_t oport; (void) nvpair_value_uint16(nvp, &port); res = nvlist_lookup_uint16(old, nme, &oport); if (res == 0) { diff = (port != oport); } else { diff = (port != 0); } break; } /* action name / string */ case IPQOS_DATA_TYPE_ACTION: case IPQOS_DATA_TYPE_STRING: { char *str; char *ostr; (void) nvpair_value_string(nvp, &str); res = nvlist_lookup_string(old, nme, &ostr); if (res == 0) { diff = strcmp(str, ostr); } else if (*dfltst) { diff = strcmp(str, dfltst); } break; } /* address mask / address */ case IPQOS_DATA_TYPE_ADDRESS_MASK: case IPQOS_DATA_TYPE_ADDRESS: { in6_addr_t *in6; in6_addr_t *oin6; uint_t x; /* * all addresses are stored as v6 addresses, so * a uint32_t[4] array is used. */ /* lookup new value */ (void) nvpair_value_uint32_array(nvp, (uint32_t **)&in6, &x); /* see if there's an old value and diff it */ res = nvlist_lookup_uint32_array(old, nme, (uint32_t **)&oin6, &x); if (res == 0) { /* diff each of the 16 v6 address bytes */ for (x = 0; x < 16; x++) { if (in6->s6_addr[x] != oin6->s6_addr[x]) { diff++; break; } } } break; } /* boolean */ case IPQOS_DATA_TYPE_BOOLEAN: { boolean_t bl; boolean_t obl; (void) nvpair_value_uint32(nvp, (uint32_t *)&bl); /* see if there's an old value and diff it */ res = nvlist_lookup_uint32(old, nme, (uint32_t *)&obl); if (res == 0) { diff = (bl != obl); /* compare against default if present */ } else if (*dfltst) { res = readbool(dfltst, &obl); if (res == IPQOS_CONF_SUCCESS) { diff = (bl != obl); } } break; } /* uint 8 */ case IPQOS_DATA_TYPE_UINT8: { uint8_t u8; uint8_t ou8; (void) nvpair_value_byte(nvp, (uchar_t *)&u8); res = nvlist_lookup_byte(old, nme, (uchar_t *)&ou8); if (res == 0) { diff = (u8 != ou8); } else if (*dfltst) { res = readuint8(dfltst, &ou8, &lo); if (res == IPQOS_CONF_SUCCESS) { diff = (u8 != ou8); } } break; } /* int 16 */ case IPQOS_DATA_TYPE_INT16: { int16_t i16; int16_t oi16; (void) nvpair_value_int16(nvp, &i16); res = nvlist_lookup_int16(old, nme, &oi16); if (res == 0) { diff = (i16 != oi16); } else if (*dfltst) { res = readint16(dfltst, &oi16, &lo); if (res == IPQOS_CONF_SUCCESS) { diff = (i16 != oi16); } } break; } /* uint16 */ case IPQOS_DATA_TYPE_UINT16: { uint16_t ui16; uint16_t oui16; (void) nvpair_value_uint16(nvp, &ui16); res = nvlist_lookup_uint16(old, nme, &oui16); if (res == 0) { diff = (ui16 != oui16); } else if (*dfltst) { res = readuint16(dfltst, &oui16, &lo); if (res == IPQOS_CONF_SUCCESS) { diff = (ui16 != oui16); } } break; } /* * int32 and user. * Since user uids are stored in an int32 nvpair we can use * the same comparison code. */ case IPQOS_DATA_TYPE_USER: case IPQOS_DATA_TYPE_INT32: { int32_t i32; int32_t oi32; (void) nvpair_value_int32(nvp, &i32); res = nvlist_lookup_int32(old, nme, &oi32); if (res == 0) { diff = (i32 != oi32); } else if (*dfltst) { res = readint32(dfltst, &oi32, &lo); if (res == IPQOS_CONF_SUCCESS) { diff = (i32 != oi32); } } break; } /* uint32 */ case IPQOS_DATA_TYPE_UINT32: { uint32_t ui32; uint32_t oui32; (void) nvpair_value_uint32(nvp, &ui32); res = nvlist_lookup_uint32(old, nme, &oui32); if (res == 0) { diff = (ui32 != oui32); } else if (*dfltst) { res = readuint32(dfltst, &oui32, &lo); if (res == IPQOS_CONF_SUCCESS) { diff = (ui32 != oui32); } } break; } /* enumeration */ case IPQOS_DATA_TYPE_ENUM: { uint32_t eval; uint32_t oeval; (void) nvpair_value_uint32(nvp, &eval); res = nvlist_lookup_uint32(old, nme, &oeval); if (res == 0) { diff = (eval != oeval); } else if (*dfltst) { res = readuint32(dfltst, &oeval, &lo); if (res == IPQOS_CONF_SUCCESS) { diff = (eval != oeval); } } break; } case IPQOS_DATA_TYPE_M_INDEX: { uint8_t idx, oidx; (void) nvpair_value_byte(nvp, &idx); res = nvlist_lookup_byte(old, nme, &oidx); if (res == 0) diff = (idx != oidx); break; } case IPQOS_DATA_TYPE_INT_ARRAY: { int *oarr, *arr; uint32_t osize, size; (void) nvpair_value_int32_array(nvp, &arr, &size); res = nvlist_lookup_int32_array(old, nme, &oarr, &osize); if (res == 0) diff = (arrays_equal(arr, oarr, size) == B_FALSE); break; } #ifdef _IPQOS_CONF_DEBUG default: { /* shouldn't get here as all types should be covered */ assert(1); } #endif } /* switch */ if (diff != 0) { IPQOSCDBG1(DIFF, "parameter %s different\n", nme); *pdiff = 1; (void) fclose(tfp); return (IPQOS_CONF_SUCCESS); } nvp = nvlist_next_nvpair(new, nvp); } /* now compare all the stuff in the second list with the first */ if (first_pass) { tmp = old; old = new; new = tmp; first_pass = 0; goto start; } (void) fclose(tfp); *pdiff = 0; return (IPQOS_CONF_SUCCESS); } /* ************************** difference application *********************** */ /* * causes all items marked as requiring change in actions and old_actions * to have the change applied. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int applydiff( ipqos_conf_action_t *actions, ipqos_conf_action_t *old_actions) { int res; IPQOSCDBG0(L1, "In applydiff:\n"); /* add each item marked as new */ res = add_items(actions, B_FALSE); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* modify items marked for modification */ res = modify_items(actions); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* delete items marked for deletion */ res = remove_items(old_actions, B_FALSE); if (res != IPQOS_CONF_SUCCESS) { return (res); } return (IPQOS_CONF_SUCCESS); } static int add_items( ipqos_conf_action_t *actions, boolean_t rem_undo) { int res; ipqos_conf_action_t *act; IPQOSCDBG1(L1, "In add_items, rem_undo: %u\n", rem_undo); /* * we need to create ipgpc action before any others as some actions * such as ftpcl which make calls to it depend on it being there on * their creation. */ act = actionexist(IPGPC_CLASSIFY, actions); if (act && (rem_undo == B_FALSE && act->new == B_TRUE || rem_undo == B_TRUE && act->deleted == B_TRUE)) { res = add_action(act); if (res != IPQOS_CONF_SUCCESS) { return (res); } } /* * loop though action list and add any actions marked as * new/modified action and apply any additions there, then return. */ for (act = actions; act; act = act->next) { res = add_item(act, rem_undo); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } } return (IPQOS_CONF_SUCCESS); } /* * * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int add_item( ipqos_conf_action_t *actions, boolean_t rem_undo) { ipqos_conf_action_t *act = actions; int res; ipqos_conf_class_t *cls; ipqos_conf_act_ref_t *pact; IPQOSCDBG2(L1, "In add_item: action: %s, rem_undo: %u\n", actions->name, rem_undo); /* if already visited return immediately */ if (act->visited == ADD_VISITED) { IPQOSCDBG0(L1, "Early exit due to visited\n"); return (IPQOS_CONF_SUCCESS); } act->visited = ADD_VISITED; /* recurse to last action in tree */ for (cls = act->classes; cls; cls = cls->next) { /* if not virtual action */ if (cls->alist->action) { res = add_item(cls->alist->action, rem_undo); if (res != IPQOS_CONF_SUCCESS) { return (res); } } } for (pact = act->params->actions; pact; pact = pact->next) { /* if not virtual */ if (pact->action) { res = add_item(pact->action, rem_undo); if (res != IPQOS_CONF_SUCCESS) { return (res); } } } /* if action marked as new and not ipgpc, create */ if (((rem_undo == B_FALSE && act->new == B_TRUE) || (rem_undo == B_TRUE && act->deleted == B_TRUE)) && strcmp(act->name, IPGPC_CLASSIFY) != 0) { res = add_action(act); if (res != IPQOS_CONF_SUCCESS) { return (res); } } /* add any classes and filters marked as new */ if (add_classes(act->classes, act->name, act->module_version, rem_undo) != IPQOS_CONF_SUCCESS || add_filters(act->filters, act->name, act->module_version, rem_undo) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* * Uses the contents of acts params nvlist and adds an originator * element set to ipqosconf and the stats parameter. This list * is then used as the parameter to a call to ipp_action_create to create * this action in the kernel. * RETURNS: IPQOS_CONF_ERR on err, else IPQOS_CONF_SUCCESS. */ static int add_action(ipqos_conf_action_t *act) { int res; nvlist_t **nvl; IPQOSCDBG2(APPLY, "add_action: action: %s, module: %s\n", act->name, act->module); nvl = &act->params->nvlist; /* alloc params nvlist if not already one */ if (*nvl == NULL) { res = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); return (IPQOS_CONF_ERR); } } /* * add module version */ if (nvlist_add_uint32(*nvl, IPP_MODULE_VERSION, (uint32_t)act->module_version) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); return (IPQOS_CONF_ERR); } /* add action stats */ if (nvlist_add_uint32(*nvl, IPP_ACTION_STATS_ENABLE, (uint32_t)act->params->stats_enable) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32: action stats"); return (IPQOS_CONF_ERR); } /* add ipqosconf originator id */ if (add_orig_ipqosconf(*nvl) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* call into lib to create action */ res = ipp_action_create(act->module, act->name, nvl, 0); if (res != 0) { IPQOSCDBG2(APPLY, "Create action %s, module %s failed\n", act->name, act->module); /* invalid params */ if (errno == EINVAL) { ipqos_msg(MT_ERROR, gettext("Invalid Parameters for action %s.\n"), act->name); } else if (errno == ENOENT) { ipqos_msg(MT_ERROR, gettext("Missing required parameter for action " "%s.\n"), act->name); } else { /* unexpected error */ ipqos_msg(MT_ERROR, gettext("Failed to create action " "%s: %s.\n"), act->name, strerror(errno)); } return (IPQOS_CONF_ERR); } /* mark action as created */ act->cr_mod = B_TRUE; return (IPQOS_CONF_SUCCESS); } /* * for each of the filters in parameter filters if rem_undo is false and * the filter is marked as new or if rem_undo is true and the filter is * marked as deleted then add the filter to the kernel action named by action * and if successful mark as created. * RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS. */ static int add_filters( ipqos_conf_filter_t *filters, char *action, int module_version, boolean_t rem_undo) { ipqos_conf_filter_t *flt; IPQOSCDBG0(L1, "In add_filters\n"); /* loop through filters in filters param */ for (flt = filters; flt; flt = flt->next) { /* * skip filter if in normal mode and not new filter or * if doing rollback and filter wasn't previously deleted. */ if ((rem_undo == B_FALSE && flt->new == B_FALSE) || (rem_undo == B_TRUE && flt->deleted == B_FALSE)) { continue; } /* add filter to action */ if (add_filter(action, flt, module_version) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* mark as created */ flt->cr_mod = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * for each of the classes in parameter classes if rem_undo is false and * the class is marked as new or if rem_undo is true and the class is * marked as deleted then add the class to the kernel action named by action * and if successful mark as created. * RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS. */ int add_classes( ipqos_conf_class_t *classes, char *action, int module_version, boolean_t rem_undo) { int res; ipqos_conf_class_t *cls; IPQOSCDBG0(L1, "In add_classes\n"); /* for each class */ for (cls = classes; cls; cls = cls->next) { /* * skip class if in normal mode and not new class or * if doing rollback and class wasn't deleted. */ if ((rem_undo == B_FALSE && cls->new == B_FALSE) || (rem_undo == B_TRUE && cls->deleted == B_FALSE)) { continue; } /* add class to action */ res = add_class(action, cls->name, module_version, cls->stats_enable, cls->alist->name); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* mark class as created */ cls->cr_mod = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * For each of the actions in actions remove the action if marked as * such or remove any objects within marked as such. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int remove_items( ipqos_conf_action_t *actions, boolean_t add_undo) { int res; ipqos_conf_action_t *act; IPQOSCDBG1(L0, "In remove_items, add_undo: %u\n", add_undo); /* * loop through actions removing any actions, or action contents * that are marked as such. */ for (act = actions; act; act = act->next) { res = remove_item(act, add_undo); if (res != IPQOS_CONF_SUCCESS) { return (res); } } return (IPQOS_CONF_SUCCESS); } /* * Deletes this action if marked for deletion or any of it's contents marked * for deletion. If the action is marked for deletion any actions referencing * this action are destroyed first if marked or have their contents destroyed * if marked. This is recursive. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int remove_item( ipqos_conf_action_t *act, boolean_t add_undo) { ipqos_conf_class_t *cls; ipqos_conf_filter_t *flt; ipqos_conf_act_ref_t *dep; int res; IPQOSCDBG3(L1, "In remove_item: action: %s, add_undo: %u, mod: %u\n", act->name, add_undo, act->modified); /* return immmediately if previously visited in remove phase */ if (act->visited == REM_VISITED) { IPQOSCDBG0(L1, "Exit due to REM_VISITED set\n"); return (IPQOS_CONF_SUCCESS); } act->visited = REM_VISITED; /* if this action is to be deleted */ if (add_undo == B_FALSE && act->todel == B_TRUE || add_undo == B_TRUE && act->new == B_TRUE && act->cr_mod == B_TRUE) { /* modify parent actions first */ for (dep = act->dependencies; dep; dep = dep->next) { res = remove_item(dep->action, add_undo); if (res != IPQOS_CONF_SUCCESS) { return (res); } } /* delete this action */ IPQOSCDBG1(APPLY, "deleting action %s\n", act->name); res = ipp_action_destroy(act->name, 0); if (res != 0) { IPQOSCDBG1(APPLY, "failed to destroy action %s\n", act->name); return (IPQOS_CONF_ERR); } /* flag as deleted */ act->deleted = B_TRUE; /* if modified action */ } else if (act->modified == B_TRUE) { /* loop through removing any filters marked for del */ for (flt = act->filters; flt; flt = flt->next) { if ((add_undo == B_FALSE && flt->todel == B_TRUE) || (add_undo == B_TRUE && flt->new == B_TRUE && flt->cr_mod == B_TRUE)) { /* do deletion */ res = remove_filter(act->name, flt->name, flt->instance, act->module_version); if (res != IPQOS_CONF_SUCCESS) { IPQOSCDBG2(APPLY, "failed to destroy " "filter %s, inst: %d\n", flt->name, flt->instance); return (IPQOS_CONF_ERR); } /* flag deleted */ flt->deleted = B_TRUE; } } /* remove any classes marked for del */ for (cls = act->classes; cls; cls = cls->next) { if ((add_undo == B_FALSE && cls->todel == B_TRUE) || (add_undo == B_TRUE && cls->new == B_TRUE && cls->cr_mod == B_TRUE)) { /* do deletion */ res = remove_class(act->name, cls->name, act->module_version, 0); if (res != IPQOS_CONF_SUCCESS) { IPQOSCDBG1(APPLY, "failed to destroy " "class %s\n", cls->name); return (IPQOS_CONF_ERR); } /* flag deleted */ cls->deleted = B_TRUE; } } /* mark action as having been modified */ act->cr_mod = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * for each of the actions in parameter actions apply any objects marked as * modified as a modification to the kernel action represented. * RETURNS: IPQOS_CONF_ERR on err, else IPQOS_CONF_SUCCESS. */ static int modify_items(ipqos_conf_action_t *actions) { ipqos_conf_action_t *act; int res; ipqos_conf_filter_t *flt; ipqos_conf_class_t *cls; IPQOSCDBG0(L1, "In modify_items\n"); /* loop through actions in parameter actions */ for (act = actions; act; act = act->next) { /* skip unchanged actions */ if (act->modified == B_FALSE) { continue; } /* apply any parameter mods */ if (act->params->modified) { res = modify_params(act->name, &act->params->nvlist, act->module_version, act->params->stats_enable); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } act->params->cr_mod = B_TRUE; } /* apply any class mods */ for (cls = act->classes; cls; cls = cls->next) { if (cls->modified) { res = modify_class(act->name, cls->name, act->module_version, cls->stats_enable, cls->alist->name, 0); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* mark modification done */ cls->cr_mod = B_TRUE; } } /* apply any filter mods */ for (flt = act->filters; flt; flt = flt->next) { if (flt->modified) { res = modify_filter(act->name, flt, act->module_version); if (res != 0) { return (IPQOS_CONF_ERR); } /* mark modification done */ flt->cr_mod = B_TRUE; } } /* mark action modified */ act->cr_mod = B_TRUE; } return (IPQOS_CONF_SUCCESS); } /* * For each of the objects of each of the actions in nactions that are * marked as having been modified the object modification is done in * reverse using the same named object from oactions. * RETURNS: IPQOS_CONF_ERR on error, IPQOS_CONF_SUCCESS otherwise. */ static int undo_modifys( ipqos_conf_action_t *oactions, ipqos_conf_action_t *nactions) { ipqos_conf_filter_t *flt; ipqos_conf_class_t *cls; ipqos_conf_action_t *act; ipqos_conf_action_t *oldact; ipqos_conf_filter_t *oldflt; ipqos_conf_class_t *oldcls; int res; IPQOSCDBG0(L1, "In undo_modifys:\n"); /* loop throught new actions */ for (act = nactions; act; act = act->next) { oldact = actionexist(act->name, oactions); /* * if the action was new then it will be removed and * any permamanent items that were marked for modify * will dissappear, so ignore action. */ if (oldact == NULL) { continue; } /* if parameters were modified switch them back */ if (act->params->modified == B_TRUE && act->params->cr_mod == B_TRUE) { res = modify_params(act->name, &oldact->params->nvlist, act->module_version, act->params->stats_enable); if (res != IPQOS_CONF_SUCCESS) { return (res); } } /* for each filter in action if filter modified switch back */ for (flt = act->filters; flt; flt = flt->next) { if (flt->modified == B_TRUE && flt->cr_mod == B_TRUE) { oldflt = filterexist(flt->name, -1, oldact->filters); res = modify_filter(act->name, oldflt, act->module_version); if (res != IPQOS_CONF_SUCCESS) { return (res); } } } /* for each class in action if class modified switch back */ for (cls = act->classes; cls; cls = cls->next) { if (cls->modified == B_TRUE && cls->cr_mod == B_TRUE) { oldcls = classexist(cls->name, oldact->classes); if (oldcls->alist) { res = modify_class(act->name, cls->name, act->module_version, oldcls->stats_enable, oldcls->alist->name, 0); } if (res != IPQOS_CONF_SUCCESS) { return (res); } } } } /* * Go through the old actions modifying perm filters and classes * whose action was deleted. * */ for (act = oactions; act != NULL; act = act->next) { if (act->deleted == B_FALSE) { continue; } for (flt = act->filters; flt != NULL; flt = flt->next) { if (flt->originator == IPP_CONFIG_PERMANENT) { res = modify_filter(act->name, flt, act->module_version); if (res != IPQOS_CONF_SUCCESS) { return (res); } } } for (cls = act->classes; cls != NULL; cls = cls->next) { if (cls->originator == IPP_CONFIG_PERMANENT) { res = modify_class(act->name, cls->name, act->module_version, cls->stats_enable, cls->alist->name, 0); if (res != IPQOS_CONF_SUCCESS) { return (res); } } } } return (IPQOS_CONF_SUCCESS); } /* * causes all changes marked as being done in actions and old_actions * to be undone. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int rollback( ipqos_conf_action_t *actions, ipqos_conf_action_t *old_actions) { int res; IPQOSCDBG0(RBK, "In rollback:\n"); /* re-add items that were deleted */ res = add_items(old_actions, B_TRUE); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* change modified items back how they were */ res = undo_modifys(old_actions, actions); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* remove new items that were added */ res = remove_items(actions, B_TRUE); if (res != IPQOS_CONF_SUCCESS) { return (res); } return (IPQOS_CONF_SUCCESS); } /* ******************************* print config **************************** */ /* * Prints the username of the user with uid 'uid' to 'fp' if the uid belongs * to a known user on the system, otherwise just print 'uid'. */ static void printuser( FILE *fp, uid_t uid) { struct passwd *pwd; IPQOSCDBG0(L0, "In printuser\n"); pwd = getpwuid(uid); if (pwd != NULL) { (void) fprintf(fp, "%s\n", pwd->pw_name); } else { (void) fprintf(fp, "%u\n", (int)uid); } } /* * print either a single value of start to fp (if start equals end), else * print start'-'end if start is the smaller of the two values, otherwise * print end'-'start. */ static void printrange( FILE *fp, uint32_t start, uint32_t end) { uint32_t tmp; if (start > end) { tmp = start; start = end; end = tmp; } (void) fprintf(fp, "%u", start); if (end != start) (void) fprintf(fp, "-%u", end); } /* * print the contents of the array arr to fp in the form: * {0-6:1;7-12:2;13:3.....} or {0-6:GREEN;7-12:YELLOW:...} * dependant upon whether this is an integer or enumerated array resectively * (if enum_nvs isn't set to NULL this is assumed to be an enumerated array); * where 0-6 is the range of indexes with value 1 (or GREEN), 7-12 the range * with value 2 (or YELLOW), and so forth. size is the array size and llimit * and ulimit are the lower and upper limits of the array values printed * respectively. For enumerated arrays enum_nvs carries the list of name * and value pairs and ulimit and llimit parameters are ignored and instead * determined from the enum_nvs list. */ static void print_int_array( FILE *fp, int arr[], uint32_t size, int llimit, int ulimit, str_val_nd_t *enum_nvs, int tab_inserts) { int x, y; uint32_t first, last; boolean_t first_entry; /* first 'ranges:value' to be printed ? */ boolean_t first_range; /* first range for a value to be printed ? */ boolean_t found_range; /* did we find a range for this value ? */ IPQOSCDBG4(L0, "In print_int_array: size: %u, llimit: %u, ulimit: %u, " "enum_nvs: %x \n", size, llimit, ulimit, enum_nvs); /* * if an enumeration retrieve value range. */ if (enum_nvs != NULL) get_str_val_value_range(enum_nvs, &llimit, &ulimit); /* * print opening curl. */ (void) fprintf(fp, "%c\n", CURL_BEGIN); PRINT_TABS(fp, tab_inserts + 1); first_entry = B_TRUE; /* * for each value in range. */ for (x = llimit; x <= ulimit; x++) { found_range = B_FALSE; first_range = B_TRUE; y = 0; /* * scan array and print ranges of indexes with value x. */ while (y < size) { /* * get first occurence of value for this range. */ while ((arr[y] != x) && (y < size)) y++; if (y == size) { break; } else { found_range = B_TRUE; } first = y; /* * get last occurence of value for this range. */ while ((arr[y] == x) && (y < size)) y++; last = y - 1; /* * print entry delimiter (semi-colon)? It must be * the first range for this value and this mustn't * be the first 'ranges:value' entry. */ if (!first_entry && first_range) { (void) fprintf(fp, ";\n"); PRINT_TABS(fp, tab_inserts + 1); } else { first_entry = B_FALSE; } /* * print comma (range delimeter) only if there was * a previous range for this value. */ if (!first_range) { (void) fprintf(fp, ","); } else { first_range = B_FALSE; } /* * print range. */ printrange(fp, first, last); } /* * only print a colon and value if we found a range with * this value. */ if (found_range) { (void) fprintf(fp, ":"); /* * print numeric/symbolic value. */ if (enum_nvs) { printenum(fp, x, enum_nvs); } else { (void) fprintf(fp, "%d", x); } } } /* * print closing curl. */ (void) fprintf(fp, "\n"); PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "%c\n", CURL_END); } /* print the protocol name for proto, or if unknown protocol number proto. */ static void printproto( FILE *fp, uint8_t proto) { struct protoent *pent; pent = getprotobynumber(proto); if (pent != NULL) { (void) fprintf(fp, "%s\n", pent->p_name); } else { (void) fprintf(fp, "%u\n", proto); } } /* * prints the name associated with interface with index ifindex to fp. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int printifname( FILE *fp, int ifindex) { int s; struct lifconf lc; struct lifnum ln; struct lifreq *lr; char *buf; int len; char *cp; int ret; int x; int idx; /* open socket */ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { ipqos_msg(MT_ENOSTR, gettext("opening AF_INET socket")); return (IPQOS_CONF_ERR); } /* get number of lifreq structs that need to be alloc'd for */ ln.lifn_family = AF_UNSPEC; ln.lifn_flags = 0; ret = ioctl(s, SIOCGLIFNUM, &ln); if (ret < 0) { ipqos_msg(MT_ENOSTR, "SIOCLIFNUM ioctl"); (void) close(s); return (IPQOS_CONF_ERR); } /* allocate buffer for SIOGLIFCONF ioctl */ len = ln.lifn_count * sizeof (struct lifreq); buf = malloc(len); if (buf == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); (void) close(s); return (IPQOS_CONF_ERR); } /* setup lifconf params for ioctl */ lc.lifc_family = AF_UNSPEC; lc.lifc_flags = 0; lc.lifc_len = len; lc.lifc_buf = buf; /* do SIOCGLIFCONF ioctl */ ret = ioctl(s, SIOCGLIFCONF, &lc); if (ret < 0) { ipqos_msg(MT_ENOSTR, "SIGLIFCONF"); (void) close(s); free(buf); return (IPQOS_CONF_ERR); } (void) close(s); /* * for each interface name given in the returned lifreq list get * it's index and compare with ifindex param. Break if equal. */ for (x = ln.lifn_count, lr = lc.lifc_req; x > 0; x--, lr++) { ret = readifindex(lr->lifr_name, &idx); if (ret != IPQOS_CONF_SUCCESS) { free(buf); return (IPQOS_CONF_ERR); } if (idx == ifindex) { break; } } free(buf); if (x == 0) { IPQOSCDBG1(L1, "Failed to find if index %u in returned " "if list.\n", ifindex); return (IPQOS_CONF_ERR); } /* truncate any logical suffix */ if ((cp = strchr(lr->lifr_name, '@')) != NULL) { *cp = '\0'; } /* print interface name */ (void) fprintf(fp, "%s\n", lr->lifr_name); return (IPQOS_CONF_SUCCESS); } /* * print to fp the enumeration clause evaluating to the value val using the * names/values given in enum_nvs. */ static void printenum( FILE *fp, uint32_t val, str_val_nd_t *enum_nvs) { boolean_t isfirstval = B_TRUE; str_val_nd_t *name_val = enum_nvs; /* for each value in enum_nvs if same bit set in val print name */ while (name_val) { if ((name_val->sv.value & val) == name_val->sv.value) { if (isfirstval == B_TRUE) { (void) fprintf(fp, "%s", name_val->sv.string); isfirstval = B_FALSE; } else { (void) fprintf(fp, ", %s", name_val->sv.string); } } name_val = name_val->next; } } /* prints the service name of port, or if unknown the number to fp. */ static void printport( FILE *fp, uint16_t port) { struct servent *sent; sent = getservbyport(port, NULL); if (sent != NULL) { (void) fprintf(fp, "%s\n", sent->s_name); } else { (void) fprintf(fp, "%u\n", ntohs(port)); } } /* * prints tp fp the name and value of all user specifiable parameters in the * nvlist. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int printnvlist( FILE *fp, char *module, nvlist_t *nvl, int printall, /* are we want ip addresses printing if node name */ ipqos_conf_filter_t *flt, /* used to determine if node name set */ int tab_inserts, place_t place) { FILE *tfp; nvpair_t *nvp; char *name; ipqos_nvtype_t type; str_val_nd_t *enum_nvs; int ret; char dfltst[IPQOS_VALST_MAXLEN+1]; char *param; int openerr; int res; IPQOSCDBG0(L1, "In printnvlist\n"); /* open stream to types file */ tfp = validmod(module, &openerr); if (tfp == NULL) { if (openerr) { ipqos_msg(MT_ENOSTR, "fopen"); } return (IPQOS_CONF_ERR); } /* go through list getting param name and type and printing it */ nvp = nvlist_next_nvpair(nvl, NULL); while (nvp) { /* get nvpair name */ name = nvpair_name(nvp); IPQOSCDBG1(L0, "processing element %s.\n", name); /* skip ipgpc params that are not explicitly user settable */ if (strcmp(name, IPGPC_FILTER_TYPE) == 0 || strcmp(name, IPGPC_SADDR_MASK) == 0 || strcmp(name, IPGPC_DADDR_MASK) == 0 || strcmp(name, IPGPC_SPORT_MASK) == 0 || strcmp(name, IPGPC_DPORT_MASK) == 0) { nvp = nvlist_next_nvpair(nvl, nvp); continue; } param = SHORT_NAME(name); /* * get parameter type from types file. */ place = PL_ANY; ret = readtype(tfp, module, param, &type, &enum_nvs, dfltst, B_TRUE, &place); if (ret != IPQOS_CONF_SUCCESS) { return (ret); } /* * for map entries we don't print the map value, only * the index value it was derived from. */ if (place == PL_MAP) { nvp = nvlist_next_nvpair(nvl, nvp); continue; } /* * the ifindex is converted to the name and printed out * so print the parameter name as ifname. */ if (strcmp(name, IPGPC_IF_INDEX) == 0) { PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "%s ", IPQOS_IFNAME_STR); /* * we may not print the address due to us instead printing * the node name in printfilter, therefore we leave the * printing of the parameter in the addresses switch case code. */ } else if ((strcmp(name, IPGPC_SADDR) != 0 && strcmp(name, IPGPC_DADDR) != 0)) { PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "%s ", param); } switch (type) { case IPQOS_DATA_TYPE_IFINDEX: { uint32_t ifidx; (void) nvpair_value_uint32(nvp, &ifidx); (void) printifname(fp, ifidx); break; } case IPQOS_DATA_TYPE_BOOLEAN: { boolean_t bl; (void) nvpair_value_uint32(nvp, (uint32_t *)&bl); (void) fprintf(fp, "%s\n", bl == B_TRUE ? "true" : "false"); break; } case IPQOS_DATA_TYPE_ACTION: { char *strval; (void) nvpair_value_string(nvp, &strval); print_action_nm(fp, strval); break; } case IPQOS_DATA_TYPE_STRING: { char *strval; (void) nvpair_value_string(nvp, &strval); (void) fprintf(fp, "%s\n", quote_ws_string(strval)); break; } case IPQOS_DATA_TYPE_ADDRESS: { uint_t tmp; in6_addr_t *addr; char addrstr[INET6_ADDRSTRLEN]; uchar_t ftype; int af; in6_addr_t *mask; /* * skip addresses that have node names for * non printall listings. */ if (printall == 0 && (strcmp(nvpair_name(nvp), IPGPC_SADDR) == 0 && flt->src_nd_name || strcmp(nvpair_name(nvp), IPGPC_DADDR) == 0 && flt->dst_nd_name)) { break; } /* we skipped this above */ PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "%s ", param); (void) nvpair_value_uint32_array(nvp, (uint32_t **)&addr, &tmp); /* get filter type */ (void) nvlist_lookup_byte(nvl, IPGPC_FILTER_TYPE, &ftype); if (ftype == IPGPC_V4_FLTR) { af = AF_INET; addr = (in6_addr_t *) &V4_PART_OF_V6((*addr)); } else { af = AF_INET6; } /* get mask */ if (strcmp(nvpair_name(nvp), IPGPC_SADDR) == 0) { ret = nvlist_lookup_uint32_array(nvl, IPGPC_SADDR_MASK, (uint32_t **)&mask, &tmp); } else { ret = nvlist_lookup_uint32_array(nvl, IPGPC_DADDR_MASK, (uint32_t **)&mask, &tmp); } /* print address/mask to fp */ (void) fprintf(fp, "%s/%u\n", inet_ntop(af, addr, addrstr, INET6_ADDRSTRLEN), masktocidr(af, mask)); break; } case IPQOS_DATA_TYPE_ENUM: { uint32_t val; (void) nvpair_value_uint32(nvp, &val); /* * print list of tokens resulting in val */ (void) fprintf(fp, "{ "); printenum(fp, val, enum_nvs); (void) fprintf(fp, " }\n"); break; } case IPQOS_DATA_TYPE_PORT: { uint16_t port; (void) nvpair_value_uint16(nvp, &port); printport(fp, port); break; } case IPQOS_DATA_TYPE_PROTO: { uint8_t proto; (void) nvpair_value_byte(nvp, &proto); printproto(fp, proto); break; } case IPQOS_DATA_TYPE_M_INDEX: case IPQOS_DATA_TYPE_UINT8: { uchar_t u8; (void) nvpair_value_byte(nvp, &u8); (void) fprintf(fp, "%u\n", u8); break; } case IPQOS_DATA_TYPE_UINT16: { uint16_t u16; (void) nvpair_value_uint16(nvp, &u16); (void) fprintf(fp, "%u\n", u16); break; } case IPQOS_DATA_TYPE_INT16: { int16_t i16; (void) nvpair_value_int16(nvp, &i16); (void) fprintf(fp, "%d\n", i16); break; } case IPQOS_DATA_TYPE_UINT32: { uint32_t u32; (void) nvpair_value_uint32(nvp, &u32); (void) fprintf(fp, "%u\n", u32); break; } case IPQOS_DATA_TYPE_INT32: { int i32; (void) nvpair_value_int32(nvp, &i32); (void) fprintf(fp, "%d\n", i32); break; } case IPQOS_DATA_TYPE_INT_ARRAY: { str_val_nd_t *arr_enum_nvs = NULL; uint32_t size; int llimit, ulimit; int *arr; (void) nvpair_value_int32_array(nvp, &arr, &size); /* * read array info from types file. */ res = read_int_array_info(dfltst, &arr_enum_nvs, &size, &llimit, &ulimit, module); /* * print array with numbers, or symbols * if enumerated. */ if (res == IPQOS_CONF_SUCCESS) { print_int_array(fp, arr, size, llimit, ulimit, arr_enum_nvs, tab_inserts); if (arr_enum_nvs != NULL) { free_str_val_entrys( arr_enum_nvs); } } break; } case IPQOS_DATA_TYPE_USER: { uid_t uid; (void) nvpair_value_int32(nvp, (int *)&uid); printuser(fp, uid); break; } #ifdef _IPQOS_CONF_DEBUG default: { /* * we should have catered for all used data * types that readtype returns. */ assert(1); } #endif } nvp = nvlist_next_nvpair(nvl, nvp); } (void) fclose(tfp); return (IPQOS_CONF_SUCCESS); } /* * print a parameter clause for the parmeters given in params to fp. * If printall is set, then the originator of the parameter object is printed. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int printparams( FILE *fp, char *module, ipqos_conf_params_t *params, int printall, int tab_inserts) { int res; /* print opening clause */ PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, IPQOS_CONF_PARAMS_STR " {\n"); /* print originator name if printall flag set */ if (printall) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(stdout, "Originator %s\n", quote_ws_string(get_originator_nm(params->originator))); } /* print global stats */ PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_GLOBAL_STATS_STR " %s\n", params->stats_enable == B_TRUE ? "true" : "false"); /* print module specific parameters */ res = printnvlist(fp, module, params->nvlist, printall, NULL, tab_inserts + 1, PL_PARAMS); if (res != IPQOS_CONF_SUCCESS) { return (res); } PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "}\n"); return (IPQOS_CONF_SUCCESS); } /* * print the interpreted name of the action_nm parameter if it is a special * action, else action_nm verbatim to fp parameter. */ static void print_action_nm(FILE *fp, char *action_nm) { if (strcmp(action_nm, IPP_ANAME_CONT) == 0) { (void) fprintf(fp, IPQOS_CONF_CONT_STR "\n"); } else if (strcmp(action_nm, IPP_ANAME_DEFER) == 0) { (void) fprintf(fp, IPQOS_CONF_DEFER_STR "\n"); } else if (strcmp(action_nm, IPP_ANAME_DROP) == 0) { (void) fprintf(fp, IPQOS_CONF_DROP_STR "\n"); } else { (void) fprintf(fp, "%s\n", quote_ws_string(action_nm)); } } /* * print a class clause for class to fp. If printall is set the originator * is printed. */ static void printclass( FILE *fp, ipqos_conf_class_t *class, int printall, int tab_inserts) { /* print opening clause */ PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, IPQOS_CONF_CLASS_STR " {\n"); /* if printall flag print originator name */ if (printall) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(stdout, "Originator %s\n", get_originator_nm(class->originator)); } /* print name, next action and stats enable */ PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_NAME_STR " %s\n", quote_ws_string(class->name)); PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_NEXT_ACTION_STR " "); print_action_nm(fp, class->alist->name); PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_STATS_ENABLE_STR " %s\n", class->stats_enable == B_TRUE ? "true" : "false"); PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "}\n"); } /* * Returns a ptr to the originator name associated with origid. If unknown * id returns ptr to "unknown". * RETURNS: ptr to originator name, or if id not known "unknown". */ static char * get_originator_nm(uint32_t origid) { int x; /* scan originators table for origid */ for (x = 0; originators[x].value != -1 && originators[x].value != origid; x++) {} /* if we've reached end of array due to unknown type return "unknown" */ if (originators[x].value == -1) { return ("unknown"); } return (originators[x].string); } /* * print a filter clause for filter pointed to by filter out to fp. If printall * is set then the originator is printed, for filters with node names instance * numbers are printed, and the filter pointer isn't advanced to point at the * last instance of the printed filter. * RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS. */ static int printfilter( FILE *fp, char *module, ipqos_conf_filter_t **filter, int printall, int tab_inserts) { int res; /* print opening clause */ PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, IPQOS_CONF_FILTER_STR " {\n"); /* print originator if printall flag set */ if (printall) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(stdout, "Originator %s\n", quote_ws_string(get_originator_nm((*filter)->originator))); } /* print name and class */ PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_NAME_STR " %s\n", quote_ws_string((*filter)->name)); PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_CLASS_STR " %s\n", quote_ws_string((*filter)->class_name)); /* print the instance if printall and potential mhomed addresses */ if (printall && ((*filter)->src_nd_name || (*filter)->dst_nd_name)) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, "Instance %u\n", (*filter)->instance); } /* print node names if any */ if ((*filter)->src_nd_name) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, "%s %s\n", strchr(IPGPC_SADDR, '.') + 1, (*filter)->src_nd_name); } if ((*filter)->dst_nd_name) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, "%s %s\n", strchr(IPGPC_DADDR, '.') + 1, (*filter)->dst_nd_name); } /* print ip_version enumeration if set */ if ((*filter)->ip_versions != 0) { PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_IP_VERSION_STR " {"); if (VERSION_IS_V4(*filter)) { (void) fprintf(fp, " V4"); } if (VERSION_IS_V6(*filter)) { (void) fprintf(fp, " V6"); } (void) fprintf(fp, " }\n"); } /* print other module specific parameters parameters */ res = printnvlist(fp, module, (*filter)->nvlist, printall, *filter, tab_inserts + 1, PL_FILTER); if (res != IPQOS_CONF_SUCCESS) { return (res); } PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "}\n"); /* * if not printall advance filter parameter to last instance of this * filter. */ if (!printall) { for (;;) { if ((*filter)->next == NULL || strcmp((*filter)->name, (*filter)->next->name) != 0) { break; } *filter = (*filter)->next; } } return (IPQOS_CONF_SUCCESS); } /* * Returns a pointer to str if no whitespace is present, else it returns * a pointer to a string with the contents of str enclose in double quotes. * This returned strings contents may change in subsequent calls so a copy * should be made of it if the caller wishes to retain it. */ static char * quote_ws_string(const char *str) { static char *buf = NULL; const char *cp; /* we don't modify the contents of str so const */ IPQOSCDBG0(L0, "In quote_ws_string\n"); /* * Just return str if no whitespace. */ for (cp = str; (*cp != '\0') && !isspace(*cp); cp++) ; if (*cp == '\0') return ((char *)str); if (buf == NULL) { /* * if first run just allocate buffer of * strlen(str) + 2 quote characters + NULL terminator. */ buf = malloc(strlen(str) + 3); } else if ((strlen(str) + 2) > strlen(buf)) { /* * Not first run, so check if we have a big enough buffer * and if not reallocate the buffer to a sufficient size. */ buf = realloc(buf, strlen(str) + 3); } if (buf == NULL) return (""); /* * copy string into buffer with quotes. */ (void) strcpy(buf, "\""); (void) strcat(buf, str); (void) strcat(buf, "\""); return (buf); } /* * print an action clause for action to fp. If the printall flag is set * then all filters and classes (regardless of their originator) and * their originators are displayed. * RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS. */ static int printaction( FILE *fp, ipqos_conf_action_t *action, int printall, int tab_inserts) { ipqos_conf_filter_t *flt; ipqos_conf_class_t *cls; int res; /* print opening clause, module and name */ PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, IPQOS_CONF_ACTION_STR " {\n"); PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, IPQOS_CONF_MODULE_STR " %s\n", quote_ws_string(action->module)); PRINT_TABS(fp, tab_inserts + 1); (void) fprintf(fp, "name %s\n", quote_ws_string(action->name)); /* print params clause */ (void) fprintf(fp, "\n"); res = printparams(fp, action->module, action->params, printall, tab_inserts + 1); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* * print classes clause for each class if printall is set, else * just ipqosconf created or permanent classes. */ for (cls = action->classes; cls != NULL; cls = cls->next) { if (printall || cls->originator == IPP_CONFIG_IPQOSCONF || cls->originator == IPP_CONFIG_PERMANENT) { (void) fprintf(fp, "\n"); printclass(fp, cls, printall, tab_inserts + 1); } } /* * print filter clause for each filter if printall is set, else * just ipqosconf created or permanent filters. */ for (flt = action->filters; flt != NULL; flt = flt->next) { if (printall || flt->originator == IPP_CONFIG_IPQOSCONF || flt->originator == IPP_CONFIG_PERMANENT) { (void) fprintf(fp, "\n"); res = printfilter(fp, action->module, &flt, printall, tab_inserts + 1); if (res != IPQOS_CONF_SUCCESS) { return (res); } } } PRINT_TABS(fp, tab_inserts); (void) fprintf(fp, "}\n"); return (IPQOS_CONF_SUCCESS); } /* *************************************************************** */ static void list_end( ipqos_list_el_t **listp, ipqos_list_el_t ***lendpp) { *lendpp = listp; while (**lendpp != NULL) { *lendpp = &(**lendpp)->next; } } static void add_to_list( ipqos_list_el_t **listp, ipqos_list_el_t *el) { el->next = *listp; *listp = el; } /* * given mask calculates the number of bits it spans. The mask must be * continuous. * RETURNS: number of bits spanned. */ static int masktocidr( int af, in6_addr_t *mask) { int zeros = 0; int byte; int cidr; /* * loop through from lowest byte to highest byte counting the * number of zero bits till hitting a one bit. */ for (byte = 15; byte >= 0; byte--) { /* * zero byte, so add 8 to zeros. */ if (mask->s6_addr[byte] == 0) { zeros += 8; /* * non-zero byte, add zero count to zeros. */ } else { zeros += (ffs((int)mask->s6_addr[byte]) - 1); break; } } /* * translate zero bits to 32 or 128 bit mask based on af. */ if (af == AF_INET) { cidr = 32 - zeros; } else { cidr = 128 - zeros; } return (cidr); } /* * Sets the first prefix_len bits in the v4 or v6 address (based upon af) * contained in the v6 address referenced by addr to 1. */ static void setmask(int prefix_len, in6_addr_t *addr, int af) { int i; int shift; int maskstartbit = 128 - prefix_len; int end_u32; IPQOSCDBG2(L1, "In setmask, prefix_len: %u, af: %s\n", prefix_len, af == AF_INET ? "AF_INET" : "AF_INET6"); /* zero addr */ bzero(addr, sizeof (in6_addr_t)); /* set which 32bits in *addr are relevant to this af */ if (af == AF_INET) { end_u32 = 3; maskstartbit = 32 - prefix_len; /* AF_INET6 */ } else { end_u32 = 0; } /* * go through each of the 32bit quantities in 128 bit in6_addr_t * and set appropriate bits according to prefix_len. */ for (i = 3; i >= end_u32; i--) { /* does the prefix apply to this 32bits? */ if (maskstartbit < ((4 - i) * 32)) { /* is this 32bits fully masked? */ if (maskstartbit <= ((3 - i) * 32)) { shift = 0; } else { shift = maskstartbit % 32; } addr->_S6_un._S6_u32[i] = (uint32_t)~0; addr->_S6_un._S6_u32[i] = addr->_S6_un._S6_u32[i] >> shift; addr->_S6_un._S6_u32[i] = addr->_S6_un._S6_u32[i] << shift; } /* translate to NBO */ addr->_S6_un._S6_u32[i] = htonl(addr->_S6_un._S6_u32[i]); } } /* * search nvlist for an element with the name specified and return a ptr * to it if found. * RETURNS: pointer to nvpair named name if found, else NULL. */ static nvpair_t * find_nvpair(nvlist_t *nvl, char *name) { nvpair_t *nvp; nvpair_t *match = NULL; char *nvp_name; IPQOSCDBG0(L1, "In find_nvpair\n"); nvp = nvlist_next_nvpair(nvl, NULL); while (nvp) { nvp_name = nvpair_name(nvp); if (strcmp(name, nvp_name) == 0) { match = nvp; } nvp = nvlist_next_nvpair(nvl, nvp); } return (match); } /* * returns a string containing module_name '.' name. * RETURNS: IPQOS_CONF_ERR if error, else IPQOS_CONF_SUCCESS. */ static char * prepend_module_name( char *name, char *module) { char *ret; IPQOSCDBG0(L2, "In prepend_module_name\n"); ret = malloc(strlen(module) + strlen(".") + strlen(name) + 1); if (ret == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (NULL); } (void) strcpy(ret, module); (void) strcat(ret, "."); (void) strcat(ret, name); return (ret); } #if 0 /* * check if element with matching s1 and s2 string is in table table. * RETURNS: 1 if found else 0. */ static int in_str_str_table( str_str_t *table, char *s1, char *s2) { str_str_t *ss = table; /* loop through table till matched or end */ while (ss->s1[0] != '\0' && (strcmp(ss->s1, s1) != 0 || strcmp(ss->s2, s2) != 0)) { ss++; } if (ss->s1[0] != '\0') { return (1); } return (0); } #endif /* 0 */ /* * check whether name is a valid action/class/filter name. * RETURNS: IPQOS_CONF_ERR if invalid name else IPQOS_CONF_SUCCESS. */ static int valid_name(char *name) { IPQOSCDBG1(L1, "In valid_name: name: %s\n", name); /* first char can't be '!' */ if (name[0] == '!') { ipqos_msg(MT_ERROR, gettext("Name not allowed to start with " "'!', line %u.\n"), lineno); return (IPQOS_CONF_ERR); } /* can't exceed IPQOS_CONF_NAME_LEN size */ if (strlen(name) >= IPQOS_CONF_NAME_LEN) { ipqos_msg(MT_ERROR, gettext("Name exceeds maximum name length " "line %u.\n"), lineno); return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* ********************* string value manip fns ************************** */ /* * searches through the str_val_nd_t list of string value pairs finding * the minimum and maximum values for value and places them in the * integers pointed at by min and max. */ static void get_str_val_value_range( str_val_nd_t *svnp, int *min, int *max) { if (svnp != NULL) { *min = *max = svnp->sv.value; svnp = svnp->next; } while (svnp != NULL) { if (svnp->sv.value > *max) *max = svnp->sv.value; if (svnp->sv.value < *min) *min = svnp->sv.value; svnp = svnp->next; } } /* * add an entry with string string and value val to sv_entrys. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int add_str_val_entry( str_val_nd_t **sv_entrys, char *string, uint32_t val) { str_val_nd_t *sv_entry; IPQOSCDBG2(L1, "In add_str_val_entry: string: %s, val: %u\n", string, val); /* alloc new node */ sv_entry = malloc(sizeof (str_val_nd_t)); if (sv_entry == NULL) { return (IPQOS_CONF_ERR); } /* populate node */ sv_entry->sv.string = malloc(strlen(string) + 1); if (sv_entry->sv.string == NULL) { free(sv_entry); ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } else { (void) strcpy(sv_entry->sv.string, string); } sv_entry->sv.value = val; /* place at start of sv_entrys list */ sv_entry->next = *sv_entrys; *sv_entrys = sv_entry; return (IPQOS_CONF_SUCCESS); } /* frees all the elements of sv_entrys. */ static void free_str_val_entrys( str_val_nd_t *sv_entrys) { str_val_nd_t *sve = sv_entrys; str_val_nd_t *tmp; IPQOSCDBG0(L1, "In free_str_val_entrys\n"); while (sve) { free(sve->sv.string); tmp = sve->next; free(sve); sve = tmp; } } /* * finds the value associated with string and assigns it to value ref'd by * val. * RETURNS: IPQOS_CONF_ERR if string not found, else IPQOS_CONF_SUCCESS. */ static int str_val_list_lookup( str_val_nd_t *svs, char *string, uint32_t *val) { str_val_nd_t *sv = svs; IPQOSCDBG1(L1, "In str_val_list_lookup: %s\n", string); /* loop through list and exit when found or list end */ while (sv != NULL) { if (strcmp(sv->sv.string, string) == 0) { break; } sv = sv->next; } /* ret error if not found */ if (sv == NULL) { return (IPQOS_CONF_ERR); } *val = sv->sv.value; IPQOSCDBG1(L1, "svll: Value returned is %u\n", *val); return (IPQOS_CONF_SUCCESS); } /* ************************ conf file read fns ***************************** */ /* * Reads a uid or username from string 'str' and assigns either the uid * or associated uid respectively to storage pointed at by 'uid'. The * function determines whether to read a uid by checking whether the first * character of 'str' is numeric, in which case it reads a uid; otherwise it * assumes a username. * RETURNS: IPQOS_CONF_ERR if a NULL string pointer is passed, the read uid * doesn't have an entry on the system, or the read username doesn't have an * entry on the system. */ static int readuser( char *str, uid_t *uid) { struct passwd *pwd; char *lo; IPQOSCDBG1(L0, "In readuser, str: %s\n", str); if (str == NULL) return (IPQOS_CONF_ERR); /* * Check if this appears to be a uid, and if so check that a * corresponding user exists. */ if (isdigit((int)str[0])) { /* * Read a 32bit integer and check in doing so that * we have consumed the whole string. */ if (readint32(str, (int *)uid, &lo) != IPQOS_CONF_SUCCESS || *lo != '\0') return (IPQOS_CONF_ERR); if (getpwuid(*uid) == NULL) return (IPQOS_CONF_ERR); } else { /* This must be a username, so lookup the uid. */ pwd = getpwnam(str); if (pwd == NULL) { return (IPQOS_CONF_ERR); } else { *uid = pwd->pw_uid; } } return (IPQOS_CONF_SUCCESS); } /* * Reads a range from range_st, either of form 'a-b' or simply 'a'. * In the former case lower and upper have their values set to a * and b respectively; in the later lower and upper have both * their values set to a. * RETURNS: IPQOS_CONF_ERR if there's a parse error, else IPQOS_CONF_SUCCESS. */ static int readrange( char *range_st, int *lower, int *upper) { char *cp; char *end, *end2; IPQOSCDBG1(L0, "In readrange: string: %s\n", range_st); /* * get range boundarys. */ cp = strchr(range_st, '-'); if (cp != NULL) { /* we have a range */ *cp++ = '\0'; *lower = (int)strtol(range_st, &end, 10); *upper = (int)strtol(cp, &end2, 10); SKIPWS(end); SKIPWS(end2); if ((range_st == end) || (*end != '\0') || (cp == end) || (*end2 != '\0')) { IPQOSCDBG0(L0, "Failed reading a-b\n"); return (IPQOS_CONF_ERR); } } else { /* single value */ *lower = *upper = (int)strtol(range_st, &end, 10); SKIPWS(end); if ((range_st == end) || (*end != '\0')) { IPQOSCDBG0(L0, "Failed reading a\n"); return (IPQOS_CONF_ERR); } } return (IPQOS_CONF_SUCCESS); } /* * Reads the values of an integer array from fp whose format is: * '{'RANGE[,RANGE[..]]:VALUE[;RANGE:VALUE[..]]'}', creates an array of size * arr_size, applies the values to it and points arrp at this array. * RANGE is one set of array indexes over which this value is to * be applied, and VALUE either an integer within the range * llimit - ulimit, or if enum_nvs isn't NULL, an enumeration value * found in the list enum_nvs. Those values which aren't explicity set * will be set to -1. * * RETURNS: IPQOS_CONF_ERR on resource or parse error, else IPQOS_CONF_SUCCESS. */ static int read_int_array( FILE *fp, char *first_token, int **arrp, uint32_t arr_size, int llimit, int ulimit, str_val_nd_t *enum_nvs) { char buf[5 * IPQOS_CONF_LINEBUF_SZ]; char *token; char *range; char *ranges; char *svalue; int value; int res; char *entry; char *tmp; char *end; int lower, upper; int x; uint32_t startln; IPQOSCDBG4(L0, "In read_int_array: size: %u, lower: %u, upper: %u, " "first_token: %s\n", arr_size, llimit, ulimit, first_token); /* * read beginning curl. */ if (first_token[0] != CURL_BEGIN) { ipqos_msg(MT_ERROR, gettext("\'{\' missing at line " "%u.\n"), lineno); return (IPQOS_CONF_ERR); } /* * allocate and initialise array for holding read values. */ *arrp = malloc(arr_size * sizeof (int)); if (*arrp == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } (void) memset(*arrp, -1, arr_size * sizeof (int)); /* * read whole array declaration string into buffer. * this is because readtoken doesn't interpret our * delimeter values specially and may return them * within another string. */ startln = lineno; /* store starting lineno for error reports */ buf[0] = '\0'; res = readtoken(fp, &token); while ((res != IPQOS_CONF_CURL_END) && (res != IPQOS_CONF_ERR) && (res != IPQOS_CONF_EOF)) { (void) strlcat(buf, token, sizeof (buf)); free(token); res = readtoken(fp, &token); } if (res != IPQOS_CONF_CURL_END) { goto array_err; } IPQOSCDBG1(L0, "array declaration buffer contains: %s\n", buf); /* * loop reading "ranges ':' value;" till end of buffer. */ entry = strtok(buf, ";"); while (entry != NULL) { svalue = strchr(entry, ':'); if (svalue == NULL) { /* missing value string */ IPQOSCDBG0(L0, "Missing value string\n"); goto array_err; } *svalue++ = '\0'; ranges = entry; /* * get value of number or enumerated symbol. */ if (enum_nvs) { /* * get rid of surrounding whitespace so as not to * confuse read_enum_value. */ SKIPWS(svalue); tmp = svalue; while (*tmp != '\0') { if (isspace(*tmp)) { *tmp = '\0'; break; } else { tmp++; } } /* * read enumeration value. */ res = read_enum_value(NULL, svalue, enum_nvs, (uint32_t *)&value); if (res != IPQOS_CONF_SUCCESS) goto array_err; } else { value = (int)strtol(svalue, &end, 10); SKIPWS(end); if ((svalue == end) || (*end != '\0')) { IPQOSCDBG0(L0, "Invalid value\n"); goto array_err; } IPQOSCDBG1(L0, "value: %u\n", value); /* * check value within valid range. */ if ((value < llimit) || (value > ulimit)) { IPQOSCDBG0(L0, "value out of range\n"); goto array_err; } } /* * loop reading ranges for this value. */ range = strtok_r(ranges, ",", &tmp); while (range != NULL) { res = readrange(range, &lower, &upper); if (res != IPQOS_CONF_SUCCESS) goto array_err; IPQOSCDBG2(L0, "range: %u - %u\n", lower, upper); if (upper < lower) { uint32_t u = lower; lower = upper; upper = u; } /* * check range valid for array size. */ if ((lower < 0) || (upper > arr_size)) { IPQOSCDBG0(L0, "Range out of array " "dimensions\n"); goto array_err; } /* * add this value to array indexes within range. */ for (x = lower; x <= upper; x++) (*arrp)[x] = value; /* * get next range. */ range = strtok_r(NULL, ",", &tmp); } entry = strtok(NULL, ";"); } return (IPQOS_CONF_SUCCESS); array_err: ipqos_msg(MT_ERROR, gettext("Array declaration line %u is invalid.\n"), startln); free(*arrp); return (IPQOS_CONF_ERR); } static int readllong(char *str, long long *llp, char **lo) { *llp = strtoll(str, lo, 0); if (*lo == str) { return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } static int readuint8(char *str, uint8_t *ui8, char **lo) { long long tmp; if (readllong(str, &tmp, lo) != 0) { return (IPQOS_CONF_ERR); } if (tmp > UCHAR_MAX || tmp < 0) { return (IPQOS_CONF_ERR); } *ui8 = (uint8_t)tmp; return (IPQOS_CONF_SUCCESS); } static int readuint16(char *str, uint16_t *ui16, char **lo) { long long tmp; if (readllong(str, &tmp, lo) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } if (tmp > USHRT_MAX || tmp < 0) { return (IPQOS_CONF_ERR); } *ui16 = (uint16_t)tmp; return (IPQOS_CONF_SUCCESS); } static int readint16(char *str, int16_t *i16, char **lo) { long long tmp; if (readllong(str, &tmp, lo) != 0) { return (IPQOS_CONF_ERR); } if (tmp > SHRT_MAX || tmp < SHRT_MIN) { return (IPQOS_CONF_ERR); } *i16 = (int16_t)tmp; return (IPQOS_CONF_SUCCESS); } static int readint32(char *str, int *i32, char **lo) { long long tmp; if (readllong(str, &tmp, lo) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } if (tmp > INT_MAX || tmp < INT_MIN) { return (IPQOS_CONF_ERR); } *i32 = tmp; return (IPQOS_CONF_SUCCESS); } static int readuint32(char *str, uint32_t *ui32, char **lo) { long long tmp; if (readllong(str, &tmp, lo) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } if (tmp > UINT_MAX || tmp < 0) { return (IPQOS_CONF_ERR); } *ui32 = (uint32_t)tmp; return (IPQOS_CONF_SUCCESS); } /* * retrieves the index associated with the interface named ifname and assigns * it to the int pointed to by ifindex. * RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS. */ static int readifindex( char *ifname, int *ifindex) { int s; struct lifreq lifrq; /* open socket */ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { ipqos_msg(MT_ENOSTR, gettext("opening AF_INET socket")); return (IPQOS_CONF_ERR); } /* copy ifname into lifreq */ (void) strlcpy(lifrq.lifr_name, ifname, LIFNAMSIZ); /* do SIOGLIFINDEX ioctl */ if (ioctl(s, SIOCGLIFINDEX, (caddr_t)&lifrq) == -1) { (void) close(s); return (IPQOS_CONF_ERR); } /* Warn if a virtual interface is specified */ if ((ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrq) != -1) && (lifrq.lifr_flags & IFF_VIRTUAL)) { ipqos_msg(MT_WARNING, gettext("Invalid interface")); } (void) close(s); *ifindex = lifrq.lifr_index; return (IPQOS_CONF_SUCCESS); } /* * Case insensitively compares the string in str with IPQOS_CONF_TRUE_STR * and IPQOS_CONF_FALSE_STR and sets boolean pointed to by bool accordingly. * RETURNS: if failure to match either IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS. */ static int readbool(char *str, boolean_t *bool) { if (strcasecmp(str, IPQOS_CONF_TRUE_STR) == 0) { *bool = B_TRUE; } else if (strcasecmp(str, IPQOS_CONF_FALSE_STR) == 0) { *bool = B_FALSE; } else { return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* * reads a protocol name/number from proto_str and assigns the number * to the uint8 ref'd by proto. * RETURNS: If not a valid name or protocol number IPQOS_CONF_ERR, else * IPQOS_CONF_SUCCESS. */ static int readproto(char *proto_str, uint8_t *proto) { struct protoent *pent; char *lo; int res; IPQOSCDBG1(L1, "In readproto: string: %s\n", proto_str); /* try name lookup */ pent = getprotobyname(proto_str); if (pent) { *proto = pent->p_proto; /* check valid protocol number */ } else { res = readuint8(proto_str, proto, &lo); if (res != IPQOS_CONF_SUCCESS || proto == 0) { return (IPQOS_CONF_ERR); } } return (IPQOS_CONF_SUCCESS); } /* * reads either a port service, or a port number from port_str and assigns * the associated port number to short ref'd by port. * RETURNS: If invalid name and number IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS. */ static int readport(char *port_str, uint16_t *port) { struct servent *sent; char *tmp; IPQOSCDBG1(L1, "In readport: string: %s\n", port_str); /* try service name lookup */ sent = getservbyname(port_str, NULL); /* failed name lookup so read port number */ if (sent == NULL) { if (readuint16(port_str, port, &tmp) != IPQOS_CONF_SUCCESS || *port == 0) { return (IPQOS_CONF_ERR); } *port = htons(*port); } else { *port = sent->s_port; } return (IPQOS_CONF_SUCCESS); } /* * Reads a curly brace, a string enclosed in double quotes, or a whitespace/ * curly brace delimited string. If a double quote enclosed string the * closing quotes need to be on the same line. * RETURNS: * on reading a CURL_BEGIN token it returns IPQOS_CONF_CURL_BEGIN, * on reading a CURL_END token it returns IPQOS_CONF_CURL_END, * on reading another valid token it returns IPQOS_CONF_SUCCESS. * for each of these token is set to point at the read string. * at EOF it returns IPQOS_CONF_EOF and if errors it returns IPQOS_CONF_ERR. */ static int readtoken( FILE *fp, char **token) { char *st, *tmp; int len; int quoted = 0; char *cmnt; char *bpos; int rembuf; static char *lo; static char *buf = NULL; static int bufsize; /* if first call initialize line buf to default size */ if (buf == NULL) { bufsize = IPQOS_CONF_LINEBUF_SZ; buf = malloc(bufsize); if (buf == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } } /* set buffer postition and size to use whole buffer */ bpos = buf; rembuf = bufsize; /* * loop reading lines until we've read a line with a non-whitespace * char. */ do { /* if no leftover from previous invocation */ if (lo == NULL) { /* * loop reading into buffer doubling if necessary until * we have either read a complete line or reached the * end of file. */ for (;;) { st = fgets(bpos, rembuf, fp); if (st == NULL) { /* if read error */ if (ferror(fp)) { free(buf); buf = NULL; ipqos_msg(MT_ENOSTR, "fgets"); return (IPQOS_CONF_ERR); /* end of file */ } else { free(buf); buf = NULL; *token = NULL; return (IPQOS_CONF_EOF); } } else { /* if read a newline */ if (buf[strlen(buf) - 1] == '\n') { lineno++; break; /* if read the last line */ } else if (feof(fp)) { break; /* * not read a full line so buffer size * is too small, double it and retry. */ } else { bufsize *= 2; tmp = realloc(buf, bufsize); if (tmp == NULL) { ipqos_msg(MT_ENOSTR, "realloc"); free(buf); return (IPQOS_CONF_ERR); } else { buf = tmp; } /* * make parameters to fgets read * into centre of doubled buffer * so we retain what we've * already read. */ bpos = &buf[(bufsize / 2) - 1]; rembuf = (bufsize / 2) + 1; } } } st = buf; /* previous leftover, assign to st */ } else { st = lo; lo = NULL; } /* truncate at comment */ cmnt = strchr(st, '#'); if (cmnt) { *cmnt = '\0'; } /* Skip any whitespace */ while (isspace(*st) && *st != '\0') { st++; } } while (*st == '\0'); /* find end of token */ tmp = st; /* if curl advance 1 char */ if (*tmp == CURL_BEGIN || *tmp == CURL_END) { tmp++; /* if dbl quote read until matching quote */ } else if (*tmp == '"') { quoted++; tmp = ++st; while (*tmp != '"' && *tmp != '\n' && *tmp != '\0') { tmp++; } if (*tmp != '"') { ipqos_msg(MT_ERROR, gettext("Quoted string exceeds " "line, line %u.\n"), lineno); free(buf); return (IPQOS_CONF_ERR); } /* normal token */ } else { /* find first whitespace, curl, newline or string end */ while (!isspace(*tmp) && *tmp != CURL_BEGIN && *tmp != CURL_END && *tmp != '\n' && *tmp != '\0') { tmp++; } } /* copy token to return */ len = tmp - st; *token = malloc(len + 1); if (!*token) { free(buf); ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } bcopy(st, *token, len); (*token)[len] = '\0'; /* if just read quoted string remove quote from remaining string */ if (quoted) { tmp++; } /* if not end of string, store rest for latter parsing */ if (*tmp != '\0' && *tmp != '\n') { lo = tmp; } /* for curl_end and curl_begin return special ret codes */ if ((*token)[1] == '\0') { if (**token == CURL_BEGIN) { return (IPQOS_CONF_CURL_BEGIN); } else if (**token == CURL_END) { return (IPQOS_CONF_CURL_END); } } return (IPQOS_CONF_SUCCESS); } /* * Reads an enumeration bitmask definition from line. The format is: * { NAME=VAL, NAME2=VAL2 }. The resulting names and values are returned. * RETURNS: NULL on error, else ptr to name/values. */ static str_val_nd_t * read_enum_nvs(char *line, char *module_name) { str_val_nd_t *enum_vals = NULL; char *cp; char *start; char *name = NULL; int len; uint32_t val; int ret; int readc; IPQOSCDBG1(L1, "In read_enum_nvs, line: %s\n", line); /* read opening brace */ cp = strchr(line, CURL_BEGIN); if (cp == NULL) { IPQOSCDBG0(L1, "missing curl begin\n"); goto fail; } else { start = cp + 1; } /* * loop reading 'name = value' entrys seperated by comma until * reach closing brace. */ for (;;) { SKIPWS(start); if (*start == '\0') { IPQOSCDBG0(L1, "missing closing bracket\n"); goto fail; } /* * read name - read until whitespace, '=', closing curl, * or string end. */ for (cp = start; !isspace(*cp) && *cp != '=' && *cp != CURL_END && *cp != '\0'; cp++) {} if (*cp == '\0') { IPQOSCDBG0(L1, "Unexpected line end in enum def'n\n"); goto fail; /* finished definition, exit loop */ } else if (*cp == CURL_END) { break; } /* store name */ len = cp - start; name = malloc(len + 1); if (name == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); goto fail; } bcopy(start, name, len); name[len] = '\0'; IPQOSCDBG1(L0, "Stored name: %s\n", name); /* read assignment */ start = strchr(cp, '='); if (start == NULL) { IPQOSCDBG0(L1, "Missing = in enum def'n\n"); goto fail; } /* read value */ ret = sscanf(++start, "%x%n", &val, &readc); if (ret != 1) { IPQOSCDBG1(L1, "sscanf of value failed, string: %s\n", cp); goto fail; } /* add name value to set */ ret = add_str_val_entry(&enum_vals, name, val); if (ret != IPQOS_CONF_SUCCESS) { IPQOSCDBG0(L1, "Failed to add str_val entry\n"); goto fail; } free(name); name = NULL; /* try reading comma */ cp = strchr(start, ','); if (cp != NULL) { start = cp + 1; /* no comma, advance to char past value last read */ } else { start += readc; } } return (enum_vals); fail: free_str_val_entrys(enum_vals); if (name != NULL) free(name); /* if a parse error */ if (errno == 0) { ipqos_msg(MT_ERROR, gettext("Types file for module %s is " "corrupt.\n"), module_name); } return (NULL); } /* * Given mapped_list with is a comma seperated list of map names, and value, * which is used to index into these maps, the function creates x new entries * in nvpp, where x is the number of map names specified. Each of these * entries has the value from the map in the position indexed by value and * with name module.${MAP_NAME}. The maps are contained in the modules config * file and have the form: * map map1 uint32 1,23,32,45,3 * As you can see the map values are uint32, and along with uint8 are the * only supported types at the moment. * * RETURNS: IPQOS_CONF_ERR if one of the maps specified in mapped_list * doesn't exist, if value is not a valid map position for a map, or if * there's a resource failure. otherwise IPQOS_CONF_SUCCESS is returned. */ static int read_mapped_values( FILE *tfp, nvlist_t **nvlp, char *module, char *mapped_list, int value) { char *map_name, *lastparam, *tmpname; int res; ipqos_nvtype_t type; char dfltst[IPQOS_VALST_MAXLEN+1] = ""; str_val_nd_t *enum_nvs; place_t place; IPQOSCDBG0(L1, "In read_mapped_values\n"); map_name = (char *)strtok_r(mapped_list, ",", &lastparam); while (map_name != NULL) { char *tokval, *lastval; int index = 0; /* * get map info from types file. */ place = PL_MAP; res = readtype(tfp, module, map_name, &type, &enum_nvs, dfltst, B_FALSE, &place); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* * Just keep browsing the list till we get to the element * with the index from the value parameter or the end. */ tokval = (char *)strtok_r(dfltst, ",", &lastval); for (;;) { if (tokval == NULL) { ipqos_msg(MT_ERROR, gettext("Invalid value, %u, line %u.\n"), value, lineno); return (IPQOS_CONF_ERR); } if (index++ == value) { break; } tokval = (char *)strtok_r(NULL, ",", &lastval); } /* * create fully qualified parameter name for map value. */ tmpname = prepend_module_name(map_name, module); if (tmpname == NULL) { return (IPQOS_CONF_ERR); } /* * add map value with fqn to parameter nvlist. */ IPQOSCDBG2(L0, "Adding map %s, value %u to nvlist\n", tmpname, atoi(tokval)); switch (type) { case IPQOS_DATA_TYPE_UINT8: { res = nvlist_add_byte(*nvlp, tmpname, (uint8_t)atoi(tokval)); if (res != 0) { free(tmpname); ipqos_msg(MT_ENOSTR, "nvlist_add_uint8"); return (IPQOS_CONF_ERR); } break; } case IPQOS_DATA_TYPE_UINT32: { res = nvlist_add_uint32(*nvlp, tmpname, (uint32_t)atoi(tokval)); if (res != 0) { free(tmpname); ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); return (IPQOS_CONF_ERR); } break; } default: { ipqos_msg(MT_ERROR, gettext("Types file for module %s is " "corrupt.\n"), module); IPQOSCDBG1(L0, "Unsupported map type for " "parameter %s given in types file.\n", map_name); return (IPQOS_CONF_ERR); } } free(tmpname); map_name = (char *)strtok_r(NULL, ",", &lastparam); } return (IPQOS_CONF_SUCCESS); } /* * Parses the string info_str into it's components. Its format is: * SIZE','[ENUM_DEF | RANGE], where SIZE is the size of the array, * ENUM_DEF is the definition of the enumeration for this array, * and RANGE is the set of values this array can accept. In * the event this array has an enumeration definition enum_nvs is * set to point at a str_val_nd_t structure which stores the names * and values associated with this enumeration. Otherwise, if this * is not an enumerated array, lower and upper are set to the lower * and upper values of RANGE. * RETURNS: IPQOS_CONF_ERR due to unexpected parse errors, else * IPQOS_CONF_SUCCESS. */ static int read_int_array_info( char *info_str, str_val_nd_t **enum_nvs, uint32_t *size, int *lower, int *upper, char *module) { int res; char *end; char *token; char *tmp; IPQOSCDBG1(L0, "In read_array_info: info_str: %s\n", (info_str != NULL) ? info_str : "NULL"); if (info_str == NULL) { IPQOSCDBG0(L0, "Null info string\n"); goto fail; } /* * read size. */ token = strtok(info_str, ","); *size = (uint32_t)strtol(token, &end, 10); SKIPWS(end); if ((end == token) || (*end != '\0')) { IPQOSCDBG0(L0, "Invalid size\n"); goto fail; } IPQOSCDBG1(L0, "read size: %u\n", *size); /* * check we have another string. */ token = strtok(NULL, "\n"); if (token == NULL) { IPQOSCDBG0(L0, "Missing range/enum def\n"); goto fail; } IPQOSCDBG1(L0, "range/enum def: %s\n", token); /* * check if enumeration set or integer set and read enumeration * definition or integer range respectively. */ tmp = strchr(token, CURL_BEGIN); if (tmp == NULL) { /* a numeric range */ res = readrange(token, lower, upper); if (res != IPQOS_CONF_SUCCESS) { IPQOSCDBG0(L0, "Failed reading range\n"); goto fail; } } else { /* an enumeration */ *enum_nvs = read_enum_nvs(token, module); if (*enum_nvs == NULL) { IPQOSCDBG0(L0, "Failed reading enum def\n"); goto fail; } } return (IPQOS_CONF_SUCCESS); fail: ipqos_msg(MT_ERROR, gettext("Types file for module %s is corrupt.\n"), module); return (IPQOS_CONF_ERR); } /* * reads the value of an enumeration parameter from first_token and fp. * first_token is the first token of the value. * The format expected is NAME | { NAME1 [, NAME2 ] [, NAME3 ] }. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int read_enum_value( FILE *fp, char *first_token, str_val_nd_t *enum_vals, uint32_t *val) { uint32_t u32; int ret; char *tk; char *lo = NULL; char *cm; int name_expected = 0; IPQOSCDBG0(L1, "In read_enum_value\n"); /* init param val */ *val = 0; /* first token not curl_begin, so lookup its value */ if (*first_token != CURL_BEGIN) { ret = str_val_list_lookup(enum_vals, first_token, val); if (ret != IPQOS_CONF_SUCCESS) { ipqos_msg(MT_ERROR, gettext("Unrecognized value, %s, line %u.\n"), first_token, lineno); return (ret); } /* curl_begin, so read values till curl_end, dicing at ',' */ } else { name_expected++; for (;;) { /* * no leftover from pervious iteration so read new * token. This leftover happens because readtoken * doesn't interpret comma's as special characters * and thus could return 'val1,val2' as one token. * If this happens the val1 will be used in the * current iteration and what follows saved in lo * for processing by successive iterations. */ if (lo == NULL) { ret = readtoken(fp, &tk); if (ret == IPQOS_CONF_ERR) { return (ret); } else if (ret == IPQOS_CONF_EOF) { ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n")); return (IPQOS_CONF_ERR); } } else { /* previous leftover, so use it */ IPQOSCDBG1(L1, "Using leftover %s.\n", lo); tk = lo; lo = NULL; } if (name_expected) { if (ret == IPQOS_CONF_CURL_END || tk[0] == ',') { ipqos_msg(MT_ERROR, gettext("Malformed value list " "line %u.\n"), lineno); free(tk); return (IPQOS_CONF_ERR); } /* * check if this token contains a ',' and * if so store it and what follows for next * iteration. */ cm = strchr(tk, ','); if (cm != NULL) { lo = malloc(strlen(cm) + 1); if (lo == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); free(tk); return (IPQOS_CONF_ERR); } (void) strcpy(lo, cm); *cm = '\0'; } /* get name value and add to total val */ ret = str_val_list_lookup(enum_vals, tk, &u32); if (ret != IPQOS_CONF_SUCCESS) { ipqos_msg(MT_ERROR, gettext("Unrecognized value, %s, " "line %u.\n"), tk, lineno); free(tk); return (IPQOS_CONF_ERR); } *val = *val | u32; name_expected--; /* comma or curl end accepted */ } else { /* we've reached curl_end so break */ if (ret == IPQOS_CONF_CURL_END) { free(tk); break; /* not curl end and not comma */ } else if (tk[0] != ',') { ipqos_msg(MT_ERROR, gettext("Malformed value list " "line %u.\n"), lineno); free(tk); return (IPQOS_CONF_ERR); } /* * store anything after the comma for next * iteration. */ if (tk[1] != '\0') { lo = malloc(strlen(&tk[1]) + 1); if (lo == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); free(tk); return (IPQOS_CONF_ERR); } (void) strcpy(lo, &tk[1]); } name_expected++; } free(tk); } } IPQOSCDBG1(L1, "value returned is: %u\n", *val); return (IPQOS_CONF_SUCCESS); } /* * read the set of permanent classes/filter from the types file ref'd by tfp * and store them in a string table pointed to by perm_items, * with *nitems getting set to number of items read. perm_filters is set * to 1 if we're searching for permanent filters, else 0 for classes. * RETURNS: IPQOS_CONF_ERR if any errors, else IPQOS_CONF_SUCCESS. */ static int read_perm_items( int perm_filters, FILE *tfp, char *module_name, char ***perm_items, int *nitems) { char lbuf[IPQOS_CONF_TYPE_LINE_LEN]; int cnt = 0; char name[IPQOS_CONF_NAME_LEN+1]; char foo[IPQOS_CONF_NAME_LEN+1]; int res; char **items = NULL; char **tmp; char *marker; IPQOSCDBG0(L1, "In read_perm_items\n"); /* seek to start of types file */ if (fseek(tfp, 0, SEEK_SET) != 0) { ipqos_msg(MT_ENOSTR, "fseek"); return (IPQOS_CONF_ERR); } /* select which marker were looking for */ if (perm_filters) { marker = IPQOS_CONF_PERM_FILTER_MK; } else { marker = IPQOS_CONF_PERM_CLASS_MK; } /* scan file line by line till end */ while (fgets(lbuf, IPQOS_CONF_TYPE_LINE_LEN, tfp) != NULL) { /* * if the line is marked as containing a default item name * read the name, extend the items string array * and store the string off the array. */ if (strncmp(lbuf, marker, strlen(marker)) == 0) { res = sscanf(lbuf, "%" VAL2STR(IPQOS_CONF_NAME_LEN) "s" "%" VAL2STR(IPQOS_CONF_NAME_LEN) "s", foo, name); if (res < 2) { ipqos_msg(MT_ERROR, gettext("Types file for module %s is " "corrupt.\n"), module_name); IPQOSCDBG1(L0, "Missing name with a %s.\n", marker); goto fail; } /* extend items array to accomodate new item */ tmp = realloc(items, (cnt + 1) * sizeof (char *)); if (tmp == NULL) { ipqos_msg(MT_ENOSTR, "realloc"); goto fail; } else { items = tmp; } /* copy and store item name */ items[cnt] = malloc(strlen(name) + 1); if (items[cnt] == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); goto fail; } (void) strcpy(items[cnt], name); cnt++; IPQOSCDBG1(L1, "stored %s in perm items array\n", name); } } *perm_items = items; *nitems = cnt; return (IPQOS_CONF_SUCCESS); fail: for (cnt--; cnt >= 0; cnt--) free(items[cnt]); free(items); return (IPQOS_CONF_ERR); } /* * Searches types file ref'd by tfp for the parameter named name * with the place corresponding with place parameter. The format * of the lines in the file are: * PLACE NAME TYPE [ ENUM_DEF ] [ DEFAULT_STR ] * The ENUM_DEF is an enumeration definition and is only present * for parameters of type enum. DEFAULT_STR is a default value for * this parameter. If present type is set to the appropriate type * enumeration and dfltst filled with DEFAULT_STR if one was set. * Also if the type is enum enum_nvps is made to point at a * set of name value pairs representing ENUM_DEF. * * RETURNS: If any resource errors occur, or a matching parameter * isn't found IPQOS_CONF_ERR is returned, else IPQOS_CONF_SUCCESS. */ static int readtype( FILE *tfp, char *module_name, char *name, ipqos_nvtype_t *type, str_val_nd_t **enum_nvps, char *dfltst, boolean_t allow_ipgpc_priv, place_t *place) { int ac; char lbuf[IPQOS_CONF_TYPE_LINE_LEN]; char param[IPQOS_CONF_PNAME_LEN+1]; char typest[IPQOS_CONF_TYPE_LEN+1]; char place_st[IPQOS_CONF_TYPE_LEN+1]; char *cp; int x; char *ipgpc_nm; int found = 0; IPQOSCDBG1(L1, "In readtype: param: %s\n", name); /* * if allow_ipgpc_priv is true then we allow ipgpc parameters that are * private between ipqosconf and ipgpc. eg. address masks, port masks. */ if (allow_ipgpc_priv && strcmp(module_name, IPGPC_NAME) == 0) { ipgpc_nm = prepend_module_name(name, IPGPC_NAME); if (ipgpc_nm == NULL) { return (IPQOS_CONF_ERR); } if (strcmp(ipgpc_nm, IPGPC_SADDR_MASK) == 0 || strcmp(ipgpc_nm, IPGPC_DADDR_MASK) == 0) { *type = IPQOS_DATA_TYPE_ADDRESS_MASK; return (IPQOS_CONF_SUCCESS); } else if (strcmp(ipgpc_nm, IPGPC_SPORT_MASK) == 0 || strcmp(ipgpc_nm, IPGPC_DPORT_MASK) == 0) { *type = IPQOS_DATA_TYPE_UINT16; return (IPQOS_CONF_SUCCESS); } else if (strcmp(ipgpc_nm, IPGPC_FILTER_TYPE) == 0) { *type = IPQOS_DATA_TYPE_UINT32; return (IPQOS_CONF_SUCCESS); } else if (strcmp(ipgpc_nm, IPGPC_IF_INDEX) == 0) { *type = IPQOS_DATA_TYPE_IFINDEX; return (IPQOS_CONF_SUCCESS); } free(ipgpc_nm); } /* * read upto and including module version line. */ if (read_tfile_ver(tfp, IPQOS_MOD_STR, module_name) == -1) return (IPQOS_CONF_ERR); /* * loop reading lines of the types file until named parameter * found or EOF. */ while (fgets(lbuf, IPQOS_CONF_TYPE_LINE_LEN, tfp) != NULL) { /* * check whether blank or commented line; if so skip */ for (cp = lbuf; isspace(*cp) && *cp != '\0'; cp++) {} if (*cp == '\0' || *cp == '#') { continue; } dfltst[0] = '\0'; /* * read place, param, type and if present default str * from line. */ ac = sscanf(lbuf, "%" VAL2STR(IPQOS_CONF_TYPE_LEN) "s " "%" VAL2STR(IPQOS_CONF_PNAME_LEN) "s " "%" VAL2STR(IPQOS_CONF_TYPE_LEN) "s " "%" VAL2STR(IPQOS_VALST_MAXLEN) "s", place_st, param, typest, dfltst); if (ac < 3) { ipqos_msg(MT_ERROR, gettext("Types file for module %s is corrupt.\n"), module_name); IPQOSCDBG0(L0, "sscanf failed to read 3 strings.\n"); return (IPQOS_CONF_ERR); } /* * if the place and name match no need to look any further. */ if ((*place == PL_ANY) || ((*place == PL_PARAMS) && strcmp(place_st, IPQOS_PLACE_PRM_STR) == 0) || ((*place == PL_FILTER) && strcmp(place_st, IPQOS_PLACE_FILTER_STR) == 0) || ((*place == PL_MAP) && strcmp(place_st, IPQOS_PLACE_MAP_STR) == 0)) { if (strcmp(param, name) == 0) { found++; break; } } } if (found == 0) { ipqos_msg(MT_ERROR, gettext("Invalid parameter, %s, line %u.\n"), name, lineno); return (IPQOS_CONF_ERR); } /* * set the place parameter to the actual place when the PL_ANY flag * was set. */ if (*place == PL_ANY) { if (strcmp(place_st, IPQOS_PLACE_PRM_STR) == 0) { *place = PL_PARAMS; } else if (strcmp(place_st, IPQOS_PLACE_FILTER_STR) == 0) { *place = PL_FILTER; } else if (strcmp(place_st, IPQOS_PLACE_MAP_STR) == 0) { *place = PL_MAP; } } /* * get type enumeration */ for (x = 0; nv_types[x].string[0]; x++) { if (strcmp(nv_types[x].string, typest) == 0) { break; } } /* * check that we have a type corresponding with the one the types * file specifies. */ if (nv_types[x].string[0] == '\0') { ipqos_msg(MT_ERROR, gettext("Types file for module %s is corrupt.\n"), module_name); return (IPQOS_CONF_ERR); } *type = nv_types[x].value; /* * if enumeration type get set of name/vals and any default value */ if (*type == IPQOS_DATA_TYPE_ENUM) { *enum_nvps = read_enum_nvs(lbuf, module_name); if (*enum_nvps == NULL) { return (IPQOS_CONF_ERR); } dfltst[0] = '\0'; cp = strchr(lbuf, CURL_END); (void) sscanf(++cp, "%" VAL2STR(IPQOS_VALST_MAXLEN) "s", dfltst); } IPQOSCDBG2(L1, "read type: %s default: %s\n", nv_types[x].string, *dfltst ? dfltst : "None"); return (IPQOS_CONF_SUCCESS); } /* * Reads a name and a value from file ref'd by cfp into list indirectly * ref'd by nvlp; If this list is NULL it will be created to accomodate * the name/value. The name must be either a special token for * for the place, or be present in the module types file ref'd by tfp. * *type is set to the enumeration of the type of the parameter and * nvp to point at the element with the nvlp ref'd list. * RETURNS: IPQOS_CONF_CURL_END if read CURL_END as name, * IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS. */ static int readnvpair( FILE *cfp, FILE *tfp, nvlist_t **nvlp, nvpair_t **nvp, ipqos_nvtype_t *type, place_t place, char *module_name) { char *name = NULL; char *valst = NULL; int res; char *tmp; str_val_nd_t *enum_nvs = NULL; char dfltst[IPQOS_VALST_MAXLEN+1]; IPQOSCDBG0(L1, "in readnvpair\n"); /* * read nvpair name */ res = readtoken(cfp, &name); /* * if reached eof, curl end or error encountered return to caller */ if (res == IPQOS_CONF_EOF) { ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n")); return (IPQOS_CONF_ERR); } else if (res == IPQOS_CONF_ERR) { return (res); } else if (res == IPQOS_CONF_CURL_END) { free(name); return (res); } /* * read nvpair value */ res = readtoken(cfp, &valst); /* * check we've read a valid value */ if (res != IPQOS_CONF_SUCCESS && res != IPQOS_CONF_CURL_BEGIN) { if (res == IPQOS_CONF_EOF) { ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n")); } else if (res == IPQOS_CONF_CURL_END) { ipqos_msg(MT_ERROR, gettext("Missing parameter value line %u.\n"), lineno); free(valst); } /* we do nothing special for IPQOS_CONF_ERR */ free(name); return (IPQOS_CONF_ERR); } /* * check for generic parameters. */ if ((place == PL_CLASS) && strcmp(name, IPQOS_CONF_NEXT_ACTION_STR) == 0) { *type = IPQOS_DATA_TYPE_ACTION; } else if (place == PL_PARAMS && strcmp(name, IPQOS_CONF_GLOBAL_STATS_STR) == 0 || place == PL_CLASS && strcmp(name, IPQOS_CONF_STATS_ENABLE_STR) == 0) { *type = IPQOS_DATA_TYPE_BOOLEAN; } else if (tfp == NULL || ((place != PL_PARAMS) && strcmp(name, IPQOS_CONF_NAME_STR) == 0) || (place == PL_FILTER) && (strcmp(name, IPQOS_CONF_CLASS_STR) == 0) || (place == PL_ACTION) && (strcmp(name, IPQOS_CONF_MODULE_STR) == 0)) { *type = IPQOS_DATA_TYPE_STRING; } else { /* if not generic parameter */ /* * get type from types file */ if (readtype(tfp, module_name, name, type, &enum_nvs, dfltst, B_FALSE, &place) != IPQOS_CONF_SUCCESS) { free(name); free(valst); return (IPQOS_CONF_ERR); } /* * get full module prefix parameter name */ tmp = name; if ((name = prepend_module_name(name, module_name)) == NULL) { name = tmp; goto fail; } free(tmp); } IPQOSCDBG3(L1, "NVP, name: %s, str_value: %s, type: %s\n", name, valst, nv_types[*type].string); /* * create nvlist if not present already */ if (*nvlp == NULL) { res = nvlist_alloc(nvlp, NV_UNIQUE_NAME, 0); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_alloc"); free(name); free(valst); return (IPQOS_CONF_ERR); } } /* * check we haven't already read this parameter */ if (find_nvpair(*nvlp, name)) { ipqos_msg(MT_ERROR, gettext("Duplicate parameter line %u.\n"), lineno); goto fail; } /* * convert value string to appropriate type and add to nvlist */ switch (*type) { case IPQOS_DATA_TYPE_IFNAME: { uint32_t ifidx; res = readifindex(valst, (int *)&ifidx); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_uint32(*nvlp, IPGPC_IF_INDEX, ifidx); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } (void) nvlist_remove_all(*nvlp, name); /* * change name to point at the name of the * new ifindex nvlist entry as name is used * later in the function. */ free(name); name = malloc(strlen(IPGPC_IF_INDEX) + 1); if (name == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); goto fail; } (void) strcpy(name, IPGPC_IF_INDEX); } break; } case IPQOS_DATA_TYPE_PROTO: { uint8_t proto; res = readproto(valst, &proto); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_byte(*nvlp, name, proto); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } } break; } case IPQOS_DATA_TYPE_PORT: { uint16_t port; res = readport(valst, &port); if (res == IPQOS_CONF_SUCCESS) { /* add port */ res = nvlist_add_uint16(*nvlp, name, port); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint16"); goto fail; } /* add appropriate all ones port mask */ if (strcmp(name, IPGPC_DPORT) == 0) { res = nvlist_add_uint16(*nvlp, IPGPC_DPORT_MASK, ~0); } else if (strcmp(name, IPGPC_SPORT) == 0) { res = nvlist_add_uint16(*nvlp, IPGPC_SPORT_MASK, ~0); } if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint16"); goto fail; } } break; } case IPQOS_DATA_TYPE_ADDRESS: case IPQOS_DATA_TYPE_ACTION: case IPQOS_DATA_TYPE_STRING: res = nvlist_add_string(*nvlp, name, valst); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } break; case IPQOS_DATA_TYPE_BOOLEAN: { boolean_t b; res = readbool(valst, &b); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_uint32(*nvlp, name, (uint32_t)b); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } } break; } case IPQOS_DATA_TYPE_UINT8: { uint8_t u8; res = readuint8(valst, &u8, &tmp); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_byte(*nvlp, name, u8); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } } break; } case IPQOS_DATA_TYPE_INT16: { int16_t i16; res = readint16(valst, &i16, &tmp); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_int16(*nvlp, name, i16); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int16"); goto fail; } } break; } case IPQOS_DATA_TYPE_UINT16: { uint16_t u16; res = readuint16(valst, &u16, &tmp); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_uint16(*nvlp, name, u16); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int16"); goto fail; } } break; } case IPQOS_DATA_TYPE_INT32: { int i32; res = readint32(valst, &i32, &tmp); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_int32(*nvlp, name, i32); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int32"); goto fail; } } break; } case IPQOS_DATA_TYPE_UINT32: { uint32_t u32; res = readuint32(valst, &u32, &tmp); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_uint32(*nvlp, name, u32); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } } break; } case IPQOS_DATA_TYPE_ENUM: { uint32_t val; res = read_enum_value(cfp, valst, enum_nvs, &val); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_uint32(*nvlp, name, val); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32"); goto fail; } } else { goto fail; } break; } /* * For now the dfltst contains a comma separated list of the * type we need this parameter to be mapped to. * read_mapped_values will fill in all the mapped parameters * and their values in the nvlist. */ case IPQOS_DATA_TYPE_M_INDEX: { uint8_t u8; res = readuint8(valst, &u8, &tmp); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_byte(*nvlp, name, u8); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint8"); goto fail; } } else { *type = IPQOS_DATA_TYPE_UINT8; break; } res = read_mapped_values(tfp, nvlp, module_name, dfltst, u8); if (res != IPQOS_CONF_SUCCESS) { goto fail; } break; } case IPQOS_DATA_TYPE_INT_ARRAY: { str_val_nd_t *arr_enum_nvs = NULL; uint32_t size; int llimit = 0, ulimit = 0; int *arr; /* * read array info from types file. */ res = read_int_array_info(dfltst, &arr_enum_nvs, &size, &llimit, &ulimit, module_name); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * read array contents from config file and construct * array with them. */ res = read_int_array(cfp, valst, &arr, size, llimit, ulimit, arr_enum_nvs); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * add array to nvlist. */ res = nvlist_add_int32_array(*nvlp, name, arr, size); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int32"); goto fail; } /* * free uneeded resources. */ free(arr); if (arr_enum_nvs) free_str_val_entrys(arr_enum_nvs); break; } case IPQOS_DATA_TYPE_USER: { uid_t uid; res = readuser(valst, &uid); if (res == IPQOS_CONF_SUCCESS) { res = nvlist_add_int32(*nvlp, name, (int)uid); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_int32"); goto fail; } } break; } #ifdef _IPQOS_CONF_DEBUG default: { /* * we shouldn't have a type that doesn't have a switch * entry. */ assert(1); } #endif } if (res != 0) { ipqos_msg(MT_ERROR, gettext("Invalid %s, line %u.\n"), nv_types[*type].string, lineno); goto fail; } /* set the nvp parameter to point at the newly added nvlist entry */ *nvp = find_nvpair(*nvlp, name); free(name); free(valst); if (enum_nvs) free_str_val_entrys(enum_nvs); return (IPQOS_CONF_SUCCESS); fail: if (name != NULL) free(name); if (valst != NULL) free(valst); if (enum_nvs != NULL) free_str_val_entrys(enum_nvs); return (IPQOS_CONF_ERR); } /* * read a parameter clause from cfp into *params. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int readparams( FILE *cfp, FILE *tfp, char *module_name, ipqos_conf_params_t *params) { int res; nvpair_t *nvp; ipqos_nvtype_t type; boolean_t bl; char *nm; char *action; char tmp[IPQOS_CONF_PNAME_LEN]; int read_stats = 0; IPQOSCDBG0(L0, "in readparams\n"); /* read beginning curl */ res = read_curl_begin(cfp); if (res != IPQOS_CONF_SUCCESS) { return (res); } /* * loop reading nvpairs, adding to params nvlist until encounter * CURL_END. */ for (;;) { /* read nvpair */ res = readnvpair(cfp, tfp, ¶ms->nvlist, &nvp, &type, PL_PARAMS, module_name); if (res == IPQOS_CONF_ERR) { goto fail; /* we have finished reading params */ } else if (res == IPQOS_CONF_CURL_END) { break; } /* * read global stats - place into params struct and remove * from nvlist. */ if (strcmp(nvpair_name(nvp), IPQOS_CONF_GLOBAL_STATS_STR) == 0) { /* check we haven't read stats before */ if (read_stats) { ipqos_msg(MT_ERROR, gettext("Duplicate parameter line %u.\n"), lineno); goto fail; } read_stats++; (void) nvpair_value_uint32(nvp, (uint32_t *)&bl); params->stats_enable = bl; (void) nvlist_remove_all(params->nvlist, IPQOS_CONF_GLOBAL_STATS_STR); /* * read action type parameter - add it to list of action refs. * also, if it's one of continue or drop virtual actions * change the action name to their special ipp names in * the action ref list and the nvlist. */ } else if (type == IPQOS_DATA_TYPE_ACTION) { /* get name and value from nvlist */ nm = nvpair_name(nvp); (void) nvpair_value_string(nvp, &action); /* if virtual action names change to ipp name */ if ((strcmp(action, IPQOS_CONF_CONT_STR) == 0) || strcmp(action, IPQOS_CONF_DROP_STR) == 0) { /* * we copy nm to a seperate buffer as nv_pair * name above gave us a ptr to internal * memory which causes strange behaviour * when we re-value that nvlist element. */ (void) strlcpy(tmp, nm, sizeof (tmp)); nm = tmp; /* modify nvlist entry and change action */ if (strcmp(action, IPQOS_CONF_CONT_STR) == 0) { action = IPP_ANAME_CONT; res = nvlist_add_string(params->nvlist, nm, action); } else { action = IPP_ANAME_DROP; res = nvlist_add_string(params->nvlist, nm, action); } if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_string"); goto fail; } } /* add action reference to params */ res = add_aref(¶ms->actions, nm, action); } } return (IPQOS_CONF_SUCCESS); fail: if (params->nvlist) { nvlist_free(params->nvlist); params->nvlist = NULL; } if (params->actions) { free_arefs(params->actions); params->actions = NULL; } return (IPQOS_CONF_ERR); } /* ************************* class manip fns ****************************** */ /* * make dst point at a dupicate class struct with duplicate elements to src. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int dup_class( ipqos_conf_class_t *src, ipqos_conf_class_t **dst) { ipqos_conf_class_t *cls; int res; IPQOSCDBG1(DIFF, "In dup_class: class: %s\n", src->name); cls = alloc_class(); if (cls == NULL) { return (IPQOS_CONF_ERR); } /* struct copy */ *cls = *src; /* we're not interested in the nvlist for a class */ cls->nvlist = NULL; /* copy first action reference */ cls->alist = NULL; res = add_aref(&cls->alist, src->alist->field, src->alist->name); if (res != IPQOS_CONF_SUCCESS) { free(cls); return (res); } *dst = cls; return (IPQOS_CONF_SUCCESS); } /* * create a zero'd class struct and return a ptr to it. * RETURNS: ptr to struct on success, NULL otherwise. */ static ipqos_conf_class_t * alloc_class() { ipqos_conf_class_t *class; class = malloc(sizeof (ipqos_conf_class_t)); if (class) { bzero(class, sizeof (ipqos_conf_class_t)); } else { ipqos_msg(MT_ENOSTR, "malloc"); } return (class); } /* frees up all memory occupied by a filter struct and its contents. */ static void free_class(ipqos_conf_class_t *cls) { if (cls == NULL) return; /* free its nvlist if present */ nvlist_free(cls->nvlist); /* free its action refs if present */ if (cls->alist) free_arefs(cls->alist); /* finally free class itself */ free(cls); } /* * Checks whether there is a class called class_nm in classes list. * RETURNS: ptr to first matched class, else if not matched NULL. */ static ipqos_conf_class_t * classexist( char *class_nm, ipqos_conf_class_t *classes) { ipqos_conf_class_t *cls; IPQOSCDBG1(L1, "In classexist: name: %s\n", class_nm); for (cls = classes; cls; cls = cls->next) { if (strcmp(class_nm, cls->name) == 0) { break; } } return (cls); } /* ************************** filter manip fns **************************** */ /* * Checks whether there is a filter called filter_nm with instance number * instance in filters list created by us or permanent. Instance value -1 * is a wildcard. * RETURNS: ptr to first matched filter, else if not matched NULL. */ static ipqos_conf_filter_t * filterexist( char *filter_nm, int instance, ipqos_conf_filter_t *filters) { IPQOSCDBG2(L1, "In filterexist: name :%s, inst: %d\n", filter_nm, instance); while (filters) { if (strcmp(filters->name, filter_nm) == 0 && (instance == -1 || filters->instance == instance) && (filters->originator == IPP_CONFIG_IPQOSCONF || filters->originator == IPP_CONFIG_PERMANENT)) { break; } filters = filters->next; } return (filters); } /* * allocate and zero a filter structure. * RETURNS: NULL on error, else ptr to filter struct. */ static ipqos_conf_filter_t * alloc_filter() { ipqos_conf_filter_t *flt; flt = malloc(sizeof (ipqos_conf_filter_t)); if (flt) { bzero(flt, sizeof (ipqos_conf_filter_t)); flt->instance = -1; } else { ipqos_msg(MT_ENOSTR, "malloc"); } return (flt); } /* free flt and all it's contents. */ static void free_filter(ipqos_conf_filter_t *flt) { IPQOSCDBG2(L1, "In free_filter: filter: %s, inst: %d\n", flt->name, flt->instance); if (flt == NULL) return; if (flt->src_nd_name) free(flt->src_nd_name); if (flt->dst_nd_name) free(flt->dst_nd_name); if (flt->nvlist) { nvlist_free(flt->nvlist); } free(flt); } /* * makes a copy of ofilter and its contents and points nfilter at it. It * also adds an instance number to the filter and if either saddr or * daddr are non-null that address to the filters nvlist along with * an all 1s address mask and the af. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int dup_filter( ipqos_conf_filter_t *ofilter, ipqos_conf_filter_t **nfilter, int af, int inv6, /* if saddr or daddr set and v4 filter are they in v6 addr */ void *saddr, void *daddr, int inst) { ipqos_conf_filter_t *nf; int res; in6_addr_t v6addr; in6_addr_t all_1s_v6; IPQOSCDBG4(MHME, "In dup_filter: name: %s, af: %u, inv6: %u, ins: %d\n", ofilter->name, af, inv6, inst); /* show src address and dst address if present */ #ifdef _IPQOS_CONF_DEBUG if (ipqosconf_dbg_flgs & MHME) { char st[100]; if (saddr) { (void) fprintf(stderr, "saddr: %s\n", inet_ntop(inv6 ? AF_INET6 : AF_INET, saddr, st, 100)); } if (daddr) { (void) fprintf(stderr, "daddr: %s\n", inet_ntop(inv6 ? AF_INET6 : AF_INET, daddr, st, 100)); } } #endif /* _IPQOS_CONF_DEBUG */ /* init local v6 address to 0 */ (void) bzero(&v6addr, sizeof (in6_addr_t)); /* create an all 1s address for use as mask */ (void) memset(&all_1s_v6, ~0, sizeof (in6_addr_t)); /* create a new filter */ nf = alloc_filter(); if (nf == NULL) { return (IPQOS_CONF_ERR); } /* struct copy old filter to new */ *nf = *ofilter; /* copy src filters nvlist if there is one to copy */ if (ofilter->nvlist) { res = nvlist_dup(ofilter->nvlist, &nf->nvlist, 0); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_dup"); goto fail; } } /* copy src and dst node names if present */ if (ofilter->src_nd_name) { nf->src_nd_name = malloc(strlen(ofilter->src_nd_name) + 1); if (nf->src_nd_name == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); goto fail; } (void) strcpy(nf->src_nd_name, ofilter->src_nd_name); } if (ofilter->dst_nd_name) { nf->dst_nd_name = malloc(strlen(ofilter->dst_nd_name) + 1); if (nf->dst_nd_name == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); goto fail; } (void) strcpy(nf->dst_nd_name, ofilter->dst_nd_name); } /* add filter addresses type */ res = nvlist_add_byte(nf->nvlist, IPGPC_FILTER_TYPE, af == AF_INET ? IPGPC_V4_FLTR : IPGPC_V6_FLTR); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } IPQOSCDBG1(MHME, "adding address type %s in dup filter\n", af == AF_INET ? "AF_INET" : "AF_INET6"); /* add saddr if present */ if (saddr) { if (af == AF_INET && !inv6) { V4_PART_OF_V6(v6addr) = *(uint32_t *)saddr; saddr = &v6addr; } /* add address and all 1's mask */ if (nvlist_add_uint32_array(nf->nvlist, IPGPC_SADDR, (uint32_t *)saddr, 4) != 0 || nvlist_add_uint32_array(nf->nvlist, IPGPC_SADDR_MASK, (uint32_t *)&all_1s_v6, 4) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32_array"); goto fail; } } /* add daddr if present */ if (daddr) { if (af == AF_INET && !inv6) { V4_PART_OF_V6(v6addr) = *(uint32_t *)daddr; daddr = &v6addr; } /* add address and all 1's mask */ if (nvlist_add_uint32_array(nf->nvlist, IPGPC_DADDR, (uint32_t *)daddr, 4) != 0 || nvlist_add_uint32_array(nf->nvlist, IPGPC_DADDR_MASK, (uint32_t *)&all_1s_v6, 4) != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32_array"); goto fail; } } /* add filter instance */ nf->instance = inst; *nfilter = nf; return (IPQOS_CONF_SUCCESS); fail: free_filter(nf); return (IPQOS_CONF_ERR); } /* ************************* action manip fns ********************** */ /* * create and zero action structure and a params structure hung off of it. * RETURNS: ptr to allocated action on success, else NULL. */ static ipqos_conf_action_t * alloc_action() { ipqos_conf_action_t *action; action = (ipqos_conf_action_t *)malloc(sizeof (ipqos_conf_action_t)); if (action == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (action); } bzero(action, sizeof (ipqos_conf_action_t)); action->params = (ipqos_conf_params_t *) malloc(sizeof (ipqos_conf_params_t)); if (action->params == NULL) { free(action); return (NULL); } bzero(action->params, sizeof (ipqos_conf_params_t)); action->params->stats_enable = B_FALSE; return (action); } /* * free all the memory used in all the actions in actions list. */ static void free_actions( ipqos_conf_action_t *actions) { ipqos_conf_action_t *act = actions; ipqos_conf_action_t *next; ipqos_conf_filter_t *flt, *nf; ipqos_conf_class_t *cls, *nc; while (act != NULL) { /* free parameters */ if (act->params != NULL) { free_arefs(act->params->actions); if (act->params->nvlist != NULL) { nvlist_free(act->params->nvlist); } free(act->params); } /* free action nvlist */ if (act->nvlist != NULL) free(act->nvlist); /* free filters */ flt = act->filters; while (flt != NULL) { nf = flt->next; free_filter(flt); flt = nf; } /* free classes */ cls = act->classes; while (cls != NULL) { nc = cls->next; free_class(cls); cls = nc; } /* free permanent classes table */ cleanup_string_table(act->perm_classes, act->num_perm_classes); /* free filters to retry */ flt = act->retry_filters; while (flt != NULL) { nf = flt->next; free_filter(flt); flt = nf; } /* free dependency pointers */ free_arefs(act->dependencies); next = act->next; free(act); act = next; } } /* * Checks whether there is an action called action_name in actions list. * RETURNS: ptr to first matched action, else if not matched NULL. * */ static ipqos_conf_action_t * actionexist( char *action_name, ipqos_conf_action_t *actions) { IPQOSCDBG1(L1, "In actionexist: name: %s\n", action_name); while (actions) { if (strcmp(action_name, actions->name) == 0) { break; } actions = actions->next; } return (actions); } /* **************************** act ref manip fns ******************** */ /* * add an action reference element with parameter field and action * action_name to arefs. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int add_aref( ipqos_conf_act_ref_t **arefs, char *field, char *action_name) { ipqos_conf_act_ref_t *aref; IPQOSCDBG1(L1, "add_aref: action: %s.\n", action_name); /* allocate zero'd aref */ aref = malloc(sizeof (ipqos_conf_act_ref_t)); if (aref == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } (void) bzero(aref, sizeof (ipqos_conf_act_ref_t)); /* copy parameter name if present */ if (field) (void) strlcpy(aref->field, field, IPQOS_CONF_PNAME_LEN); /* copy action name */ (void) strlcpy(aref->name, action_name, IPQOS_CONF_NAME_LEN); /* place at head of list */ aref->next = *arefs; *arefs = aref; return (IPQOS_CONF_SUCCESS); } /* * free all the memory used by the action references in arefs. */ static void free_arefs( ipqos_conf_act_ref_t *arefs) { ipqos_conf_act_ref_t *aref = arefs; ipqos_conf_act_ref_t *next; while (aref) { nvlist_free(aref->nvlist); next = aref->next; free(aref); aref = next; } } /* *************************************************************** */ /* * checks whether aname is a valid action name. * RETURNS: IPQOS_CONF_ERR if invalid, else IPQOS_CONF_SUCCESS. */ static int valid_aname(char *aname) { /* * dissallow the use of the name of a virtual action, either * the ipqosconf name, or the longer ipp names. */ if (strcmp(aname, IPQOS_CONF_CONT_STR) == 0 || strcmp(aname, IPQOS_CONF_DEFER_STR) == 0 || strcmp(aname, IPQOS_CONF_DROP_STR) == 0 || virtual_action(aname)) { ipqos_msg(MT_ERROR, gettext("Invalid action name line %u.\n"), lineno); return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* * Opens a stream to the types file for module module_name (assuming * that the file path is TYPES_FILE_DIR/module_name.types). if * a file open failure occurs, *openerr is set to 1. * RETURNS: NULL on error, else stream ptr to module types file. */ static FILE * validmod( char *module_name, int *openerr) { FILE *fp; char *path; IPQOSCDBG1(L1, "In validmod: module_name: %s\n", module_name); *openerr = 0; /* create modules type file path */ path = malloc(strlen(TYPES_FILE_DIR) + strlen(module_name) + strlen(".types") + 1); if (path == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (NULL); } (void) strcpy(path, TYPES_FILE_DIR); (void) strcat(path, module_name); (void) strcat(path, ".types"); IPQOSCDBG1(L1, "opening file %s\n", path); /* open stream to types file */ fp = fopen(path, "r"); if (fp == NULL) { (*openerr)++; } free(path); return (fp); } /* * read a class clause from cfp into a class struct and point class at this. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int readclass( FILE *cfp, char *module_name, ipqos_conf_class_t **class, char **perm_classes, int num_perm_classes) { int nm, act; int res; nvpair_t *nvp; ipqos_nvtype_t type; char *name; char *action; int stats; IPQOSCDBG0(L0, "in readclass\n"); /* create and zero class struct */ *class = alloc_class(); if (!*class) { return (IPQOS_CONF_ERR); } (*class)->originator = IPP_CONFIG_IPQOSCONF; /* get starting line for error reporting */ (*class)->lineno = lineno; /* read curl_begin */ res = read_curl_begin(cfp); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* loop reading parameters till read curl_end */ stats = nm = act = 0; for (;;) { /* read nvpair */ res = readnvpair(cfp, NULL, &(*class)->nvlist, &nvp, &type, PL_CLASS, module_name); if (res == IPQOS_CONF_ERR) { goto fail; /* reached end of class clause */ } else if (res == IPQOS_CONF_CURL_END) { break; } /* * catch name and action nv pairs and stats if present * and place values in class structure. */ /* name */ if (nm == 0 && strcmp(nvpair_name(nvp), IPQOS_CONF_NAME_STR) == 0) { (void) nvpair_value_string(nvp, &name); if (valid_name(name) != IPQOS_CONF_SUCCESS) { goto fail; } (void) strcpy((*class)->name, name); nm++; /* next action */ } else if (act == 0 && strcmp(nvpair_name(nvp), IPQOS_CONF_NEXT_ACTION_STR) == 0) { (void) nvpair_value_string(nvp, &action); /* * if next action string continue string set action to * IPP_ANAME_CONT, else if drop string IPP_ANAME_DROP */ if (strcmp(action, IPQOS_CONF_CONT_STR) == 0) { action = IPP_ANAME_CONT; } else if (strcmp(action, IPQOS_CONF_DROP_STR) == 0) { action = IPP_ANAME_DROP; } /* add an action reference to action list */ res = add_aref(&(*class)->alist, IPQOS_CONF_NEXT_ACTION_STR, action); if (res != IPQOS_CONF_SUCCESS) { goto fail; } act++; /* class stats enable */ } else if (stats == 0 && strcmp(nvpair_name(nvp), IPQOS_CONF_STATS_ENABLE_STR) == 0) { boolean_t bl; (void) nvpair_value_uint32(nvp, (uint32_t *)&bl); (*class)->stats_enable = bl; stats++; /* no other / duplicate parameters allowed */ } else { ipqos_msg(MT_ERROR, gettext("Unexpected parameter line %u.\n"), lineno); goto fail; } } if (nm == 0 || act == 0) { ipqos_msg(MT_ERROR, gettext("Missing class name/next action before line %u.\n"), lineno); goto fail; } /* change class originator field to permanent if permanent class */ if (in_string_table(perm_classes, num_perm_classes, (*class)->name)) { IPQOSCDBG1(L0, "Setting class %s as permanent.\n", (*class)->name); (*class)->originator = IPP_CONFIG_PERMANENT; } return (IPQOS_CONF_SUCCESS); fail: if (*class) free_class(*class); return (IPQOS_CONF_ERR); } /* * This function assumes either src_nd_name or dst_node_nm are set in filter. * * Creates one of more copies of filter according to the ip versions * requested (or assumed) and the resolution of the src and dst address * node names if spec'd. If both node names are spec'd then a filter is * created for each pair of addresses (one from each node name) that is * compatible with the chosen address family, otherwise a filter copy is * created for just each address of the single node name that is * compatible. * If filter->ip_versions has been set that is used to determine the * af's we will create filters for, else if a numeric address was * added the family of that will be used, otherwise we fall back * to both v4 and v6 addresses. * * Any name lookup failures that occur are checked to see whether the failure * was a soft or hard failure and the nlerr field of filter set accordingly * before the error is returned. * * RETURNS: IPQOS_CONF_ERR on any error, else IPQOS_CONF_SUCCESS. */ static int domultihome( ipqos_conf_filter_t *filter, ipqos_conf_filter_t **flist, boolean_t last_retry) { uint32_t ftype; int v4 = 1, v6 = 1; /* default lookup family is v4 and v6 */ int saf, daf; struct hostent *shp = NULL; struct hostent *dhp = NULL; in6_addr_t daddr, saddr; int idx = 0; ipqos_conf_filter_t *nfilter; int res; int ernum; int in32b = 0; char **sp, **dp; IPQOSCDBG3(MHME, "In domultihome: filter: %s, src_node: %s, " "dst_node: %s\n", filter->name, (filter->src_nd_name ? filter->src_nd_name : "NULL"), (filter->dst_nd_name ? filter->dst_nd_name : "NULL")); /* check if we've read an ip_version request to get the versions */ if (filter->ip_versions != 0) { v4 = VERSION_IS_V4(filter); v6 = VERSION_IS_V6(filter); /* otherwise check if we've read a numeric address and get versions */ } else if (nvlist_lookup_uint32(filter->nvlist, IPGPC_FILTER_TYPE, &ftype) == 0) { if (ftype == IPGPC_V4_FLTR) { v6--; } else { v4--; } } /* read saddrs if src node name */ if (filter->src_nd_name) { /* v4 only address */ if (v4 && !v6) { in32b++; shp = getipnodebyname(filter->src_nd_name, AF_INET, AI_ADDRCONFIG, &ernum); /* v6 only */ } else if (v6 && !v4) { shp = getipnodebyname(filter->src_nd_name, AF_INET6, AI_DEFAULT, &ernum); /* v4 and v6 */ } else if (v6 && v4) { shp = getipnodebyname(filter->src_nd_name, AF_INET6, AI_DEFAULT|AI_ALL, &ernum); } #ifdef TESTING_RETRY if (!last_retry) { filter->nlerr = IPQOS_LOOKUP_RETRY; goto fail; } #endif /* * if lookup error determine whether it was a soft or hard * failure and mark as such in filter. */ if (shp == NULL) { if (ernum != TRY_AGAIN) { ipqos_msg(MT_ERROR, gettext("Failed to " "resolve src host name for filter at " "line %u, ignoring filter.\n"), filter->lineno); filter->nlerr = IPQOS_LOOKUP_FAIL; } else { if (last_retry) { ipqos_msg(MT_ERROR, gettext("Failed " "to resolve src host name for " "filter at line %u, ignoring " "filter.\n"), filter->lineno); } filter->nlerr = IPQOS_LOOKUP_RETRY; } goto fail; } } /* read daddrs if dst node name */ if (filter->dst_nd_name) { /* v4 only address */ if (v4 && !v6) { in32b++; dhp = getipnodebyname(filter->dst_nd_name, AF_INET, AI_ADDRCONFIG, &ernum); /* v6 only */ } else if (v6 && !v4) { dhp = getipnodebyname(filter->dst_nd_name, AF_INET6, AI_DEFAULT, &ernum); /* v6 and v4 addresses */ } else { dhp = getipnodebyname(filter->dst_nd_name, AF_INET6, AI_DEFAULT|AI_ALL, &ernum); } if (dhp == NULL) { if (ernum != TRY_AGAIN) { ipqos_msg(MT_ERROR, gettext("Failed to " "resolve dst host name for filter at " "line %u, ignoring filter.\n"), filter->lineno); filter->nlerr = IPQOS_LOOKUP_FAIL; } else { if (last_retry) { ipqos_msg(MT_ERROR, gettext("Failed " "to resolve dst host name for " "filter at line %u, ignoring " "filter.\n"), filter->lineno); } filter->nlerr = IPQOS_LOOKUP_RETRY; } goto fail; } } /* * if src and dst node name, create set of filters; one for each * src and dst address of matching types. */ if (filter->src_nd_name && filter->dst_nd_name) { for (sp = shp->h_addr_list; *sp != NULL; sp++) { (void) bcopy(*sp, &saddr, shp->h_length); /* get saddr family */ if (in32b || IN6_IS_ADDR_V4MAPPED(&saddr)) { saf = AF_INET; } else { saf = AF_INET6; } for (dp = dhp->h_addr_list; *dp != NULL; dp++) { (void) bcopy(*dp, &daddr, dhp->h_length); /* get daddr family */ if (in32b || IN6_IS_ADDR_V4MAPPED(&daddr)) { daf = AF_INET; } else { daf = AF_INET6; } /* * if saddr and daddr same af duplicate * filter adding addresses and new instance * number and add to flist filter list. */ if (daf == saf) { res = dup_filter(filter, &nfilter, saf, !in32b, &saddr, &daddr, ++idx); if (res != IPQOS_CONF_SUCCESS) { goto fail; } ADD_TO_LIST(flist, nfilter); } } } /* if src name only create set of filters, one for each node address */ } else if (filter->src_nd_name) { for (sp = shp->h_addr_list; *sp != NULL; sp++) { (void) bcopy(*sp, &saddr, shp->h_length); /* get af */ if (in32b || IN6_IS_ADDR_V4MAPPED(&saddr)) { saf = AF_INET; } else { saf = AF_INET6; } /* * dup filter adding saddr and new instance num and * add to flist filter list. */ res = dup_filter(filter, &nfilter, saf, !in32b, &saddr, NULL, ++idx); if (res != IPQOS_CONF_SUCCESS) { goto fail; } ADD_TO_LIST(flist, nfilter); } /* if dname only create set of filters, one for each node address */ } else { for (dp = dhp->h_addr_list; *dp != NULL; dp++) { (void) bcopy(*dp, &daddr, dhp->h_length); /* get af */ if (in32b || IN6_IS_ADDR_V4MAPPED(&daddr)) { daf = AF_INET; } else { daf = AF_INET6; } /* * dup filter adding daddr and new instance num and * add to flist filter list. */ res = dup_filter(filter, &nfilter, daf, !in32b, NULL, &daddr, ++idx); if (res != IPQOS_CONF_SUCCESS) { goto fail; } ADD_TO_LIST(flist, nfilter); } } if (shp) freehostent(shp); if (dhp) freehostent(dhp); return (IPQOS_CONF_SUCCESS); fail: /* * should really clean up any filters that we have created, * however, free_actions called from readaction will cleam them up. */ if (shp) freehostent(shp); if (dhp) freehostent(dhp); return (IPQOS_CONF_ERR); } /* * read a filter clause from cfp into a filter struct and point filter * at this. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int readfilter( FILE *cfp, FILE *tfp, char *module_name, ipqos_conf_filter_t **filter, char **perm_filters, int num_perm_filters) { int res; int nm, cls, ipv; in6_addr_t mask; char *addr_str; char *sl = NULL; in6_addr_t addr; int sa; struct hostent *hp; int err_num; int v4 = 0, v6 = 0; uchar_t mlen; char *tmp; nvpair_t *nvp; ipqos_nvtype_t type; char *name; char *class; uchar_t b; in6_addr_t v6addr; IPQOSCDBG0(L0, "in readfilter\n"); /* create and zero filter struct */ *filter = alloc_filter(); if (*filter == NULL) { return (IPQOS_CONF_ERR); } (*filter)->originator = IPP_CONFIG_IPQOSCONF; /* get starting line for error reporting */ (*filter)->lineno = lineno; /* read beginning curl */ res = read_curl_begin(cfp); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * loop reading nvpairs onto nvlist until encounter CURL_END */ ipv = nm = cls = 0; for (;;) { /* read nvpair */ res = readnvpair(cfp, tfp, &(*filter)->nvlist, &nvp, &type, PL_FILTER, module_name); if (res == IPQOS_CONF_ERR) { goto fail; /* reached the end of filter definition */ } else if (res == IPQOS_CONF_CURL_END) { break; } /* * catch name and class and place value into filter * structure. */ /* read filter name */ if (strcmp(nvpair_name(nvp), IPQOS_CONF_NAME_STR) == 0) { if (nm != 0) { ipqos_msg(MT_ERROR, gettext("Duplicate parameter line %u.\n"), lineno); goto fail; } (void) nvpair_value_string(nvp, &name); if (valid_name(name) != IPQOS_CONF_SUCCESS) { goto fail; } (void) strcpy((*filter)->name, name); (void) nvlist_remove_all((*filter)->nvlist, IPQOS_CONF_NAME_STR); nm++; /* read class name */ } else if (strcmp(nvpair_name(nvp), IPQOS_CONF_CLASS_STR) == 0) { if (cls != 0) { ipqos_msg(MT_ERROR, gettext("Duplicate parameter line %u.\n"), lineno); goto fail; } if (nvpair_value_string(nvp, &class) != 0) { ipqos_msg(MT_ENOSTR, "nvpair_value_string"); break; } if (valid_name(class) != IPQOS_CONF_SUCCESS) { goto fail; } (void) strcpy((*filter)->class_name, class); (void) nvlist_remove_all((*filter)->nvlist, IPQOS_CONF_CLASS_STR); cls++; /* * if a src or dst ip node name/address. For those that * are determined to be addresses we convert them from * strings here and add to the filter nvlist; for node names * we add the name to the filter struct for readaction to * process. */ } else if (strcmp(nvpair_name(nvp), IPGPC_SADDR) == 0 || strcmp(nvpair_name(nvp), IPGPC_DADDR) == 0) { sa = 0; if (strcmp(nvpair_name(nvp), IPGPC_SADDR) == 0) { sa++; } (void) nvpair_value_string(nvp, &addr_str); /* * get the address mask if present. * make a copy so that the nvlist element that * it is part of doesn't dissapear and causes probs. */ sl = strchr(addr_str, '/'); if (sl) { *sl = '\0'; tmp = malloc(strlen(++sl) + 1); if (tmp == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); goto fail; } (void) strcpy(tmp, sl); sl = tmp; } /* if a numeric address */ if (inet_pton(AF_INET, addr_str, &addr) == 1 || inet_pton(AF_INET6, addr_str, &addr) == 1) { /* get address */ hp = getipnodebyname(addr_str, AF_INET6, AI_DEFAULT, &err_num); if (hp == NULL) { ipqos_msg(MT_ENOSTR, "getipnodebyname"); goto fail; } (void) bcopy(hp->h_addr_list[0], &v6addr, hp->h_length); freehostent(hp); /* determine address type */ v4 = IN6_IS_ADDR_V4MAPPED(&v6addr); if (!v4) { v6++; } /* * check any previous addresses have same * version. */ if (nvlist_lookup_byte((*filter)->nvlist, IPGPC_FILTER_TYPE, &b) == 0) { if (v4 && b != IPGPC_V4_FLTR || v6 && b != IPGPC_V6_FLTR) { ipqos_msg(MT_ERROR, gettext("Incompatible " "address version line " "%u.\n"), lineno); goto fail; } } /* * check that if ip_version spec'd it * corresponds. */ if ((*filter)->ip_versions != 0) { if (v4 && !VERSION_IS_V4(*filter) || v6 && !VERSION_IS_V6(*filter)) { ipqos_msg(MT_ERROR, gettext("Incompatible " "address version line %u" ".\n"), lineno); goto fail; } } /* add the address type */ res = nvlist_add_byte( (*filter)->nvlist, IPGPC_FILTER_TYPE, v4 ? IPGPC_V4_FLTR : IPGPC_V6_FLTR); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_byte"); goto fail; } /* add address to list */ res = nvlist_add_uint32_array((*filter)->nvlist, sa ? IPGPC_SADDR : IPGPC_DADDR, (uint32_t *)&v6addr, 4); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32_array"); goto fail; } /* * add mask entry in list. */ if (sl) { /* have CIDR mask */ char *lo; res = readuint8(sl, &mlen, &lo); if (res != IPQOS_CONF_SUCCESS || v4 && mlen > 32 || !v4 && mlen > 128 || mlen == 0) { ipqos_msg(MT_ERROR, gettext("Invalid CIDR " "mask line %u.\n"), lineno); goto fail; } setmask(mlen, &mask, v4 ? AF_INET : AF_INET6); free(sl); } else { /* no CIDR mask spec'd - use all 1s */ (void) memset(&mask, ~0, sizeof (in6_addr_t)); } res = nvlist_add_uint32_array((*filter)->nvlist, sa ? IPGPC_SADDR_MASK : IPGPC_DADDR_MASK, (uint32_t *)&mask, 4); if (res != 0) { ipqos_msg(MT_ENOSTR, "nvlist_add_uint32_arr"); goto fail; } /* inet_pton returns fail - we assume a node name */ } else { /* * doesn't make sense to have a mask * with a node name. */ if (sl) { ipqos_msg(MT_ERROR, gettext("Address masks aren't " "allowed for host names line " "%u.\n"), lineno); goto fail; } /* * store node name in filter struct for * later resolution. */ if (sa) { (*filter)->src_nd_name = malloc(strlen(addr_str) + 1); (void) strcpy((*filter)->src_nd_name, addr_str); } else { (*filter)->dst_nd_name = malloc(strlen(addr_str) + 1); (void) strcpy((*filter)->dst_nd_name, addr_str); } } /* ip_version enumeration */ } else if (strcmp(nvpair_name(nvp), IPQOS_CONF_IP_VERSION) == 0) { /* check we haven't read ip_version before */ if (ipv) { ipqos_msg(MT_ERROR, gettext("Duplicate parameter line %u.\n"), lineno); goto fail; } ipv++; /* get bitmask value */ (void) nvpair_value_uint32(nvp, &(*filter)->ip_versions); /* * check that if either ip address is spec'd it * corresponds. */ if (v4 && !VERSION_IS_V4(*filter) || v6 && !VERSION_IS_V6(*filter)) { ipqos_msg(MT_ERROR, gettext("Incompatible " "address version line %u.\n"), lineno); goto fail; } /* remove ip_version from nvlist */ (void) nvlist_remove_all((*filter)->nvlist, IPQOS_CONF_IP_VERSION); } } if (nm == 0 || cls == 0) { ipqos_msg(MT_ERROR, gettext("Missing filter/class name " "before line %u.\n"), lineno); goto fail; } if (in_string_table(perm_filters, num_perm_filters, (*filter)->name)) { IPQOSCDBG1(L0, "Setting filter %s as permanent.\n", (*filter)->name); (*filter)->originator = IPP_CONFIG_PERMANENT; } return (IPQOS_CONF_SUCCESS); fail: if (*filter) free_filter(*filter); if (hp) freehostent(hp); if (sl) free(sl); return (IPQOS_CONF_ERR); } /* * reads the curl begin token from cfp stream. * RETURNS: IPQOS_CONF_ERR if not read successfully, else IPQOS_CONF_SUCCES. */ static int read_curl_begin(FILE *cfp) { int res; char *st; res = readtoken(cfp, &st); if (res != IPQOS_CONF_CURL_BEGIN) { if (res == IPQOS_CONF_EOF) { ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n")); /* if CURL_END or something else */ } else if (res != IPQOS_CONF_ERR) { free(st); ipqos_msg(MT_ERROR, gettext("\'{\' missing at line " "%u.\n"), lineno); } return (IPQOS_CONF_ERR); } free(st); return (IPQOS_CONF_SUCCESS); } /* * This function parses the parameter string version into a version of the * form "%u.%u" (as a sscanf format string). It then encodes this into an * int and returns this encoding. * RETURNS: -1 if an invalid string, else the integer encoding. */ static int ver_str_to_int( char *version) { uint32_t major, minor; int ver; if (sscanf(version, "%u.%u", &major, &minor) != 2) { IPQOSCDBG0(L0, "Failed to process version number string\n"); return (-1); } ver = (int)((major * 10000) + minor); return (ver); } /* * This function scans through the stream fp line by line looking for * a line beginning with version_tag and returns a integer encoding of * the version following it. * * RETURNS: If the version definition isn't found or the version is not * a valid version (%u.%u) then -1 is returned, else an integer encoding * of the read version. */ static int read_tfile_ver( FILE *fp, char *version_tag, char *module_name) { char lbuf[IPQOS_CONF_LINEBUF_SZ]; char buf[IPQOS_CONF_LINEBUF_SZ+1]; char buf2[IPQOS_CONF_LINEBUF_SZ+1]; int found = 0; int version; /* * reset to file start */ if (fseek(fp, 0, SEEK_SET) != 0) { ipqos_msg(MT_ENOSTR, "fseek"); return (-1); } /* * loop reading lines till found the one beginning with version_tag. */ while (fgets(lbuf, IPQOS_CONF_LINEBUF_SZ, fp) != NULL) { if ((sscanf(lbuf, "%" VAL2STR(IPQOS_CONF_LINEBUF_SZ) "s" "%" VAL2STR(IPQOS_CONF_LINEBUF_SZ) "s", buf, buf2) == 2) && (strcmp(buf, version_tag) == 0)) { found++; break; } } if (found == 0) { ipqos_msg(MT_ERROR, gettext("Types file for module %s is " "corrupt.\n"), module_name); IPQOSCDBG1(L1, "Couldn't find %s in types file\n", version_tag); return (-1); } /* * convert version string into int. */ if ((version = ver_str_to_int(buf2)) == -1) { ipqos_msg(MT_ERROR, gettext("Types file for module %s is " "corrupt.\n"), module_name); return (-1); } return (version); } /* * read action clause and params/classes/filters clauses within and * store in and hang off an action structure, and point action at it. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int readaction( FILE *cfp, ipqos_conf_action_t **action) { char *st; FILE *tfp = NULL; int nm, md; int readprms = 0; int res; char *strval; char *name; nvpair_t *nvp; ipqos_nvtype_t type; ipqos_conf_filter_t *filter; ipqos_conf_class_t *class; int oe; char **perm_filters; int num_perm_filters; int tf_fmt_ver; IPQOSCDBG0(L0, "in readaction\n"); res = readtoken(cfp, &st); if (res == IPQOS_CONF_ERR || res == IPQOS_CONF_EOF) { return (res); } else if (strcmp(st, IPQOS_CONF_ACTION_STR) != 0) { ipqos_msg(MT_ERROR, gettext("Missing %s token line " "%u.\n"), IPQOS_CONF_ACTION_STR, lineno); free(st); return (IPQOS_CONF_ERR); } free(st); /* create action structure */ *action = alloc_action(); if (*action == NULL) { return (IPQOS_CONF_ERR); } (*action)->params->originator = IPP_CONFIG_IPQOSCONF; /* get starting line for error reporting */ (*action)->lineno = lineno; /* read beginning curl */ res = read_curl_begin(cfp); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* loop till read both action name and module */ nm = md = 0; do { /* read nvpair */ res = readnvpair(cfp, NULL, &(*action)->nvlist, &nvp, &type, PL_ACTION, NULL); if (res == IPQOS_CONF_ERR) { goto fail; /* read curl_end */ } else if (res == IPQOS_CONF_CURL_END) { if (nm == 0 || md == 0) { ipqos_msg(MT_ERROR, gettext("Missing action name/ module " "before line %u.\n"), lineno); goto fail; } } /* store name and module in action structure */ name = nvpair_name(nvp); /* read action name */ if (nm == 0 && strcmp(name, IPQOS_CONF_NAME_STR) == 0) { (void) nvpair_value_string(nvp, &strval); /* check name is valid */ if (valid_name(strval) != IPQOS_CONF_SUCCESS || valid_aname(strval) != IPQOS_CONF_SUCCESS) { goto fail; } /* store and remove from list */ (void) strcpy((*action)->name, strval); /* remove name from nvlist */ (void) nvlist_remove_all((*action)->nvlist, IPQOS_CONF_NAME_STR); nm++; /* read module name */ } else if (md == 0 && strcmp(name, IPQOS_CONF_MODULE_STR) == 0) { /* * check that module has a type file and get * open stream to it. */ (void) nvpair_value_string(nvp, &strval); if ((tfp = validmod(strval, &oe)) == NULL) { if (oe) { if (errno == ENOENT) { ipqos_msg(MT_ERROR, gettext("Invalid " "module name line %u.\n"), lineno); } else { ipqos_msg(MT_ENOSTR, "fopen"); } } goto fail; } /* * move module name to action struct */ (void) strlcpy((*action)->module, strval, IPQOS_CONF_NAME_LEN); (void) nvlist_remove_all((*action)->nvlist, IPQOS_CONF_MODULE_STR); md++; /* duplicate/other parameter */ } else { ipqos_msg(MT_ERROR, gettext("Unexpected parameter line %u.\n"), lineno); goto fail; } } while (nm == 0 || md == 0); /* * check that if the ipgpc action it is named correctly */ if ((strcmp((*action)->module, IPGPC_NAME) == 0) && (strcmp((*action)->name, IPGPC_CLASSIFY) != 0)) { ipqos_msg(MT_ERROR, gettext("%s action has incorrect name line %u.\n"), IPGPC_NAME, (*action)->lineno); goto fail; } /* get list of permanent classes */ res = read_perm_items(0, tfp, (*action)->module, &(*action)->perm_classes, &(*action)->num_perm_classes); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* get list of permanent filters */ res = read_perm_items(1, tfp, (*action)->module, &perm_filters, &num_perm_filters); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * get types file format version and check its supported. */ if ((tf_fmt_ver = read_tfile_ver(tfp, IPQOS_FMT_STR, (*action)->module)) == -1) goto fail; if (IPP_MAJOR_MODULE_VER(tf_fmt_ver) > 1 || IPP_MINOR_MODULE_VER(tf_fmt_ver) > 0) { ipqos_msg(MT_ERROR, gettext("Types file for module %s is " "incompatible.\n"), (*action)->module); IPQOSCDBG0(L1, "Unsupported fmt major/minor version\n"); goto fail; } /* * get module version */ if (((*action)->module_version = read_tfile_ver(tfp, IPQOS_MOD_STR, (*action)->module)) == -1) goto fail; /* read filter/class/params blocks until CURL_END */ for (;;) { /* read token */ res = readtoken(cfp, &st); if (res == IPQOS_CONF_ERR) { goto fail; } else if (res == IPQOS_CONF_EOF) { ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n")); goto fail; /* read CURL_END - end of action definition */ } else if (res == IPQOS_CONF_CURL_END) { free(st); break; } /* * read in either a filter/class or parameter block. */ /* read filter */ if (strcmp(st, IPQOS_CONF_FILTER_STR) == 0) { free(st); res = readfilter(cfp, tfp, (*action)->module, &filter, perm_filters, num_perm_filters); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * if we read a host name for either src or dst addr * resolve the hostnames and create the appropriate * number of filters. */ if (filter->src_nd_name || filter->dst_nd_name) { res = domultihome(filter, &(*action)->filters, B_FALSE); /* * if a lookup fails and the filters * marked as retry we add it to a list * for another attempt later, otherwise * it is thrown away. */ if (res != IPQOS_CONF_SUCCESS) { /* if not name lookup problem */ if (filter->nlerr == 0) { free_filter(filter); goto fail; /* name lookup problem */ /* * if intermitent lookup failure * add to list of filters to * retry later. */ } else if (filter->nlerr == IPQOS_LOOKUP_RETRY) { filter->nlerr = 0; ADD_TO_LIST( &(*action)->retry_filters, filter); /* * for non-existing names * ignore the filter. */ } else { free_filter(filter); } /* creation of new filters successful */ } else { free_filter(filter); } /* non-node name filter */ } else { ADD_TO_LIST(&(*action)->filters, filter); } /* read class */ } else if (strcmp(st, IPQOS_CONF_CLASS_STR) == 0) { free(st); res = readclass(cfp, (*action)->module, &class, (*action)->perm_classes, (*action)->num_perm_classes); if (res != IPQOS_CONF_SUCCESS) { goto fail; } ADD_TO_LIST(&(*action)->classes, class); /* read params */ } else if (strcmp(st, IPQOS_CONF_PARAMS_STR) == 0) { free(st); if (readprms) { ipqos_msg(MT_ERROR, gettext("Second parameter clause not " "supported line %u.\n"), lineno); goto fail; } res = readparams(cfp, tfp, (*action)->module, (*action)->params); if (res != IPQOS_CONF_SUCCESS) { goto fail; } readprms++; /* something unexpected */ } else { free(st); ipqos_msg(MT_ERROR, gettext("Params/filter/class clause expected " "line %u.\n"), lineno); goto fail; } } (void) fclose(tfp); return (IPQOS_CONF_SUCCESS); fail: if (tfp) (void) fclose(tfp); if (*action) { free_actions(*action); *action = NULL; } return (IPQOS_CONF_ERR); } /* * check that each of the actions in actions is uniquely named. If one isn't * set *name to point at the name of the duplicate action. * RETURNS: IPQOS_CONF_ERR if a non-unique action, else IPQOS_CONF_SUCCESS. */ static int actions_unique(ipqos_conf_action_t *actions, char **name) { IPQOSCDBG0(L1, "In actions_unique.\n"); while (actions) { if (actionexist(actions->name, actions->next)) { *name = actions->name; return (IPQOS_CONF_ERR); } actions = actions->next; } return (IPQOS_CONF_SUCCESS); } /* * checks whether the action parameter is involved in an action cycle. * RETURNS: 1 if involved in a cycle, 0 otherwise. */ static int in_cycle( ipqos_conf_action_t *action) { ipqos_conf_act_ref_t *aref; ipqos_conf_class_t *c; IPQOSCDBG1(L0, "in_cycle: visiting action %s\n", action->name); /* have we visited this action before? */ if (action->visited == INCYCLE_VISITED) { action->visited = 0; return (1); } action->visited = INCYCLE_VISITED; /* * recurse down the child actions of this action through the * classes next action and parameter actions. */ for (aref = action->params->actions; aref != NULL; aref = aref->next) { /* skip virtual actions - they can't be in a cycle */ if (virtual_action(aref->name)) { continue; } if (in_cycle(aref->action)) { action->visited = 0; return (1); } } for (c = action->classes; c != NULL; c = c->next) { aref = c->alist; if (virtual_action(aref->name)) { continue; } if (in_cycle(aref->action)) { action->visited = 0; return (1); } } IPQOSCDBG0(L0, "in_cycle: return\n"); action->visited = 0; return (0); } /* * checks that the configuration in actions is a valid whole, that * all actions are unique, all filters and classes are unique within * their action, that classes referenced by filters exist and actions * referenced by classes and params exist. Also checks that there are no root * actions but ipgpc and that no actions are involved in cycles. As * a consequence of checking that the actions exist two way pointers * are created between the dependee and dependant actions. * * In the case the the userconf flag is zero only this link creation is * set as we trust the kernel to return a valid configuration. * * RETURNS: IPQOS_CONF_ERR if config isn't valid, else IPQOS_CONF_SUCCESS. * */ static int validconf( ipqos_conf_action_t *actions, int userconf) /* are we checking a conf file ? */ { char *name; ipqos_conf_action_t *act; int res; ipqos_conf_action_t *dact; ipqos_conf_filter_t *flt; ipqos_conf_class_t *cls; ipqos_conf_params_t *params; ipqos_conf_act_ref_t *aref; IPQOSCDBG0(L0, "In validconf\n"); /* check actions are unique */ if (userconf && actions_unique(actions, &name) != IPQOS_CONF_SUCCESS) { ipqos_msg(MT_ERROR, gettext("Duplicate named action %s.\n"), name); return (IPQOS_CONF_ERR); } for (act = actions; act; act = act->next) { /* * check filters (for user land configs only). * check they are unique in this action and their class exists. */ if (userconf) { for (flt = act->filters; flt; flt = flt->next) { /* check unique name */ if (filterexist(flt->name, flt->instance, flt->next)) { ipqos_msg(MT_ERROR, gettext("Duplicate named filter " "%s in action %s.\n"), flt->name, act->name); return (IPQOS_CONF_ERR); } /* * check existence of class and error if * class doesn't exist and not a perm class */ if (!classexist(flt->class_name, act->classes)) { if (!in_string_table(act->perm_classes, act->num_perm_classes, flt->class_name)) { ipqos_msg(MT_ERROR, gettext("Undefined " "class in filter %s, " "action %s.\n"), flt->name, act->name); return (IPQOS_CONF_ERR); } } } } /* check classes */ for (cls = act->classes; cls; cls = cls->next) { /* check if class name unique (userland only) */ if (userconf && classexist(cls->name, cls->next)) { ipqos_msg(MT_ERROR, gettext("Duplicate named class %s in " "action %s.\n"), cls->name, act->name); return (IPQOS_CONF_ERR); } /* * virtual actions always exist so don't check for next * action. */ if (virtual_action(cls->alist->name)) { continue; } /* * check existance of next action and create link to * it. */ if ((cls->alist->action = actionexist(cls->alist->name, actions)) == NULL) { ipqos_msg(MT_ERROR, gettext("Undefined action in class %s, " "action %s.\n"), cls->name, act->name); return (IPQOS_CONF_ERR); } /* create backwards link - used for deletions */ dact = cls->alist->action; res = add_aref(&dact->dependencies, NULL, act->name); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } dact->dependencies->action = act; } /* check actions exist for action type parameters */ params = act->params; for (aref = params->actions; aref; aref = aref->next) { /* skip virtuals */ if (virtual_action(aref->name)) { continue; } /* * check existance of action in this ref * and if present create a ptr to it. */ aref->action = actionexist(aref->name, actions); if (aref->action == NULL) { ipqos_msg(MT_ERROR, gettext("Undefined action in parameter " "%s, action %s.\n"), SHORT_NAME(aref->field), act->name); return (IPQOS_CONF_ERR); } /* create backwards link */ dact = aref->action; res = add_aref(&dact->dependencies, NULL, act->name); if (res != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } dact->dependencies->action = act; } } /* for kernel retrieved configs we don't do the following checks. */ if (!userconf) { return (IPQOS_CONF_SUCCESS); } /* check for cycles in config and orphaned actions other than ipgpc */ for (act = actions; act; act = act->next) { /* check if involved in cycle */ if (in_cycle(act)) { ipqos_msg(MT_ERROR, gettext("Action %s involved in cycle.\n"), act->name); return (IPQOS_CONF_ERR); } /* check that this action has a parent (except ipgpc) */ if (act->dependencies == NULL && strcmp(act->name, IPGPC_CLASSIFY) != 0) { ipqos_msg(MT_ERROR, gettext("Action %s isn't " "referenced by any other actions.\n"), act->name); return (IPQOS_CONF_ERR); } } return (IPQOS_CONF_SUCCESS); } /* * Read the version from the config file with stream cfp with * the tag version_tag. The tag-value pair should be the first tokens * encountered. * * RETURNS: -1 if a missing or invalid version or a read error, * else an integer encoding of the version. */ static int read_cfile_ver( FILE *cfp, char *version_tag) { char *sp = NULL; int res; int version; IPQOSCDBG0(L1, "In read_cfile_ver:\n"); /* * read version tag string. */ res = readtoken(cfp, &sp); if (res != IPQOS_CONF_SUCCESS) { goto fail; } else if (strcasecmp(sp, version_tag) != 0) { goto fail; } free(sp); sp = NULL; /* * read version number string. */ res = readtoken(cfp, &sp); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * encode version into int. */ if ((version = ver_str_to_int(sp)) == -1) { goto fail; } free(sp); return (version); fail: ipqos_msg(MT_ERROR, gettext("Missing/Invalid config file %s.\n"), version_tag); if (sp != NULL) free(sp); return (-1); } /* * read the set of actions definitions from the stream cfp and store * them in a list pointed to by conf. * RETURNS: IPQOS_CONF_ERR if any errors, else IPQOS_CONF_SUCCESS. */ static int readconf( FILE *cfp, ipqos_conf_action_t **conf) { int res; ipqos_conf_action_t *action; boolean_t ipgpc_action = B_FALSE; int fmt_ver; IPQOSCDBG0(L0, "In readconf\n"); *conf = NULL; /* * get config file format version. */ fmt_ver = read_cfile_ver(cfp, IPQOS_FMT_VERSION_STR); if (fmt_ver == -1) { return (IPQOS_CONF_ERR); } else { /* * check version is valid */ if ((IPP_MAJOR_MODULE_VER(fmt_ver) > 1) || (IPP_MINOR_MODULE_VER(fmt_ver) > 0)) { ipqos_msg(MT_ERROR, gettext("Unsupported config file " "format version.\n")); return (IPQOS_CONF_ERR); } } /* loop reading actions adding to conf till EOF */ for (;;) { action = NULL; /* readaction */ res = readaction(cfp, &action); if (res == IPQOS_CONF_ERR) { goto fail; } /* reached eof, finish */ if (res == IPQOS_CONF_EOF) { break; } ADD_TO_LIST(conf, action); /* check if we just read an ipgpc action */ if (strcmp(action->name, IPGPC_CLASSIFY) == 0) ipgpc_action = B_TRUE; } /* check that there is one or more actions and that one is ipgpc */ if (ipgpc_action == B_FALSE) { ipqos_msg(MT_ERROR, gettext("No %s action defined.\n"), IPGPC_NAME); goto fail; } return (IPQOS_CONF_SUCCESS); fail: free_actions(*conf); *conf = NULL; return (IPQOS_CONF_ERR); } /* ************************ kernel config retrieval ************************ */ /* * read the current configuration from the kernel and make *conf a ptr to it. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int readkconf(ipqos_conf_action_t **conf) { int res; char **modnames = NULL; int nmods; char **actnames = NULL; int nacts; int x, y; FILE *tfp; int openerr; ipqos_actinfo_prm_t ai_prm; IPQOSCDBG0(L0, "In readkconf\n"); /* initialise conf to NULL */ *conf = NULL; /* get list of modules currently loaded */ res = ipp_list_mods(&modnames, &nmods); if (res != 0) { ipqos_msg(MT_ENOSTR, "ipp_list_mods"); return (IPQOS_CONF_ERR); } /* * iterate through all loaded modules retrieving their list of actions * and then retrieving the configuration of each of these * and attatching it to conf. */ for (x = 0; x < nmods; x++) { /* skip actions of modules that we can't open types file of */ if ((tfp = validmod(modnames[x], &openerr)) == NULL) { /* mem error */ if (!openerr) { goto fail; /* * fopen fail - if we failed because the file didn't * exist we assume this is an unknown module and * ignore this module, otherwise error. */ } else { if (errno == ENOENT) { continue; } else { ipqos_msg(MT_ENOSTR, "fopen"); goto fail; } } } (void) fclose(tfp); /* get action list for this module */ res = ipp_mod_list_actions(modnames[x], &actnames, &nacts); if (res != 0) { ipqos_msg(MT_ENOSTR, "ipp_mod_list_actions"); goto fail; } /* read config of each action of this module */ for (y = 0; y < nacts; y++) { ai_prm.action = alloc_action(); if (ai_prm.action == NULL) { goto fail; } /* copy action name into action struct */ (void) strlcpy(ai_prm.action->name, actnames[y], IPQOS_CONF_NAME_LEN); /* copy module name into action struct */ (void) strlcpy(ai_prm.action->module, modnames[x], IPQOS_CONF_NAME_LEN); /* get action info */ res = ipp_action_info(actnames[y], (int (*)(nvlist_t *, void *))parse_kaction, (void *)&ai_prm, 0); if (res != 0) { /* was this an ipp error */ if (ai_prm.intl_ret == IPQOS_CONF_SUCCESS) { ipqos_msg(MT_ENOSTR, "ipp_action_info"); } goto fail; } ADD_TO_LIST(conf, ai_prm.action); } cleanup_string_table(actnames, nacts); } cleanup_string_table(modnames, nmods); return (IPQOS_CONF_SUCCESS); fail: free_actions(*conf); *conf = NULL; cleanup_string_table(modnames, nmods); cleanup_string_table(actnames, nacts); return (IPQOS_CONF_ERR); } /* * This is passed as a parameter to ipp_action_info() in readkaction and * is called back one for each configuration element within the action * specified. This results in filters and classes being created and chained * off of action, and action having its params set. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS. */ static int parse_kaction( nvlist_t *nvl, ipqos_actinfo_prm_t *ai_prm) { int ret; uint8_t cfgtype; ipqos_conf_filter_t *filter = NULL; ipqos_conf_class_t *class = NULL; ipqos_conf_action_t *action = ai_prm->action; IPQOSCDBG1(KRET, "In parse_kaction: action_name: %s\n", action->name); /* get config type */ (void) nvlist_lookup_byte(nvl, IPP_CONFIG_TYPE, &cfgtype); (void) nvlist_remove_all(nvl, IPP_CONFIG_TYPE); switch (cfgtype) { case CLASSIFIER_ADD_FILTER: { /* * parse the passed filter nvlist * and add result to action's filter list. */ filter = alloc_filter(); if (filter == NULL) { ai_prm->intl_ret = IPQOS_CONF_ERR; return (IPQOS_CONF_ERR); } ret = parse_kfilter(filter, nvl); if (ret != IPQOS_CONF_SUCCESS) { free_filter(filter); ai_prm->intl_ret = IPQOS_CONF_ERR; return (ret); } ADD_TO_LIST(&action->filters, filter); break; } case CLASSIFIER_ADD_CLASS: case CLASSIFIER_MODIFY_CLASS: { /* * parse the passed class nvlist * and add result to action's class list. */ class = alloc_class(); if (class == NULL) { ai_prm->intl_ret = IPQOS_CONF_ERR; return (IPQOS_CONF_ERR); } ret = parse_kclass(class, nvl); if (ret != IPQOS_CONF_SUCCESS) { free_class(class); ai_prm->intl_ret = IPQOS_CONF_ERR; return (ret); } ADD_TO_LIST(&action->classes, class); break; } case IPP_SET: { /* * we don't alloc a params struct as it is created * as part of an action. */ /* parse the passed params nvlist */ ret = parse_kparams(action->module, action->params, nvl); if (ret != IPQOS_CONF_SUCCESS) { ai_prm->intl_ret = IPQOS_CONF_ERR; return (ret); } } } ai_prm->intl_ret = IPQOS_CONF_SUCCESS; return (IPQOS_CONF_SUCCESS); } /* * parses a params nvlist returned from the kernel. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ int parse_kparams( char *module, ipqos_conf_params_t *params, nvlist_t *nvl) { int ret; ipqos_nvtype_t type; str_val_nd_t *tmp; char *act; uint32_t u32; nvpair_t *nvp; FILE *tfp; char dfltst[IPQOS_VALST_MAXLEN]; char *param; nvlist_t *nvlcp; int openerr; place_t place; IPQOSCDBG0(KRET, "In parse_kparams:\n"); /* get stream to module types file */ tfp = validmod(module, &openerr); if (tfp == NULL) { if (openerr) { ipqos_msg(MT_ENOSTR, "fopen"); } return (IPQOS_CONF_ERR); } /* make copy of passed in nvlist as it is freed by the caller */ ret = nvlist_dup(nvl, &nvlcp, 0); if (ret != 0) { return (IPQOS_CONF_ERR); } /* * get config originator and remove from nvlist. If no owner we * assume ownership. */ ret = nvlist_lookup_uint32(nvlcp, IPP_CONFIG_ORIGINATOR, &u32); if (ret == 0) { params->originator = u32; (void) nvlist_remove_all(nvlcp, IPP_CONFIG_ORIGINATOR); } else { params->originator = IPP_CONFIG_IPQOSCONF; } /* get action stats and remove from nvlist */ ret = nvlist_lookup_uint32(nvlcp, IPP_ACTION_STATS_ENABLE, &u32); if (ret == 0) { params->stats_enable = *(boolean_t *)&u32; (void) nvlist_remove_all(nvlcp, IPP_ACTION_STATS_ENABLE); } /* * loop throught nvlist elements and for those that are actions create * action ref entrys for them. */ nvp = nvlist_next_nvpair(nvlcp, NULL); while (nvp != NULL) { param = SHORT_NAME(nvpair_name(nvp)); place = PL_ANY; ret = readtype(tfp, module, param, &type, &tmp, dfltst, B_FALSE, &place); if (ret != IPQOS_CONF_SUCCESS) { goto fail; } if ((place == PL_PARAMS) && /* avoid map entries */ (type == IPQOS_DATA_TYPE_ACTION)) { (void) nvpair_value_string(nvp, &act); ret = add_aref(¶ms->actions, nvpair_name(nvp), act); if (ret != IPQOS_CONF_SUCCESS) { goto fail; } } nvp = nvlist_next_nvpair(nvlcp, nvp); } /* assign copied nvlist to params struct */ params->nvlist = nvlcp; (void) fclose(tfp); return (IPQOS_CONF_SUCCESS); fail: (void) fclose(tfp); free_arefs(params->actions); params->actions = NULL; nvlist_free(nvlcp); return (IPQOS_CONF_ERR); } /* * parses a classes nvlist returned from the kernel. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int parse_kclass( ipqos_conf_class_t *class, nvlist_t *nvl) { int ret; uint32_t u32; char *str; IPQOSCDBG0(KRET, "In parse_kclass:\n"); /* lookup object originator */ ret = nvlist_lookup_uint32(nvl, IPP_CONFIG_ORIGINATOR, &u32); if (ret == 0) { class->originator = u32; } else { class->originator = IPP_CONFIG_IPQOSCONF; } /* lookup name */ (void) nvlist_lookup_string(nvl, CLASSIFIER_CLASS_NAME, &str); (void) strlcpy(class->name, str, IPQOS_CONF_NAME_LEN); IPQOSCDBG1(KRET, "reading class %s\n", class->name); /* lookup next action */ (void) nvlist_lookup_string(nvl, CLASSIFIER_NEXT_ACTION, &str); ret = add_aref(&class->alist, NULL, str); if (ret != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* lookup stats enable */ ret = nvlist_lookup_uint32(nvl, CLASSIFIER_CLASS_STATS_ENABLE, &u32); if (ret == 0) { class->stats_enable = *(boolean_t *)&u32; } return (IPQOS_CONF_SUCCESS); } /* * parses a filters nvlist returned from the kernel. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int parse_kfilter( ipqos_conf_filter_t *filter, nvlist_t *nvl) { int ret; char *str; uint32_t u32; nvlist_t *nvlcp; char *end; IPQOSCDBG0(KRET, "In parse_kfilter:\n"); /* make copy of passed in nvlist as it is freed by the caller */ ret = nvlist_dup(nvl, &nvlcp, 0); if (ret != 0) { return (IPQOS_CONF_ERR); } /* lookup originator */ ret = nvlist_lookup_uint32(nvlcp, IPP_CONFIG_ORIGINATOR, &u32); if (ret == 0) { filter->originator = u32; (void) nvlist_remove_all(nvlcp, IPP_CONFIG_ORIGINATOR); } else { filter->originator = IPP_CONFIG_IPQOSCONF; } /* lookup filter name */ (void) nvlist_lookup_string(nvlcp, CLASSIFIER_FILTER_NAME, &str); (void) strlcpy(filter->name, str, IPQOS_CONF_NAME_LEN); (void) nvlist_remove_all(nvlcp, CLASSIFIER_FILTER_NAME); /* lookup class name */ (void) nvlist_lookup_string(nvlcp, CLASSIFIER_CLASS_NAME, &str); (void) strlcpy(filter->class_name, str, IPQOS_CONF_NAME_LEN); (void) nvlist_remove_all(nvlcp, CLASSIFIER_CLASS_NAME); /* lookup src and dst host names if present */ if (nvlist_lookup_string(nvlcp, IPGPC_SADDR_HOSTNAME, &str) == 0) { filter->src_nd_name = malloc(strlen(str) + 1); if (filter->src_nd_name) { (void) strcpy(filter->src_nd_name, str); (void) nvlist_remove_all(nvlcp, IPGPC_SADDR_HOSTNAME); } else { ipqos_msg(MT_ENOSTR, "malloc"); nvlist_free(nvlcp); return (IPQOS_CONF_ERR); } } if (nvlist_lookup_string(nvlcp, IPGPC_DADDR_HOSTNAME, &str) == 0) { filter->dst_nd_name = malloc(strlen(str) + 1); if (filter->dst_nd_name) { (void) strcpy(filter->dst_nd_name, str); (void) nvlist_remove_all(nvlcp, IPGPC_DADDR_HOSTNAME); } else { ipqos_msg(MT_ENOSTR, "malloc"); nvlist_free(nvlcp); return (IPQOS_CONF_ERR); } } /* lookup ip_version if present */ if (nvlist_lookup_string(nvlcp, IPGPC_FILTER_PRIVATE, &str) == 0) { filter->ip_versions = (uint32_t)strtol(str, &end, 0); if (end != str) { (void) nvlist_remove_all(nvlcp, IPGPC_FILTER_PRIVATE); } else { ipqos_msg(MT_ERROR, gettext("Corrupted ip_version returned from " "kernel.\n")); nvlist_free(nvlcp); return (IPQOS_CONF_ERR); } } /* lookup filter instance if present */ ret = nvlist_lookup_int32(nvlcp, IPGPC_FILTER_INSTANCE, &filter->instance); if (ret != 0) { filter->instance = -1; } else { (void) nvlist_remove_all(nvlcp, IPGPC_FILTER_INSTANCE); } /* attach new trimmed nvlist to filter */ filter->nvlist = nvlcp; return (IPQOS_CONF_SUCCESS); } /* * determines whether action_name is a virtual action name. * RETURNS: if virtual action 1, else 0. */ static int virtual_action(char *action_name) { if (strcmp(action_name, IPP_ANAME_CONT) == 0 || strcmp(action_name, IPP_ANAME_DEFER) == 0 || strcmp(action_name, IPP_ANAME_DROP) == 0) { return (1); } return (0); } /* * remove all the actions within the kernel. If there is a failure * modified is set to represent whether the attempt to flush modified * the configuration in any way. * RETURNS: IPQOS_CONF_ERR if the ipp_* functions return any errors, * else IPQOS_CONF_SUCCESS. */ static int flush( boolean_t *modified) { int res; char **modnames = NULL; int nmods; char **actnames = NULL; int nacts; int x, y; IPQOSCDBG0(L0, "In flush\n"); *modified = B_FALSE; /* * get list of modules currently loaded. */ res = ipp_list_mods(&modnames, &nmods); if (res != 0) { ipqos_msg(MT_ENOSTR, "ipp_list_mods"); return (IPQOS_CONF_ERR); } /* * iterate through all the modules listing their actions and * deleting all of them. */ for (x = 0; x < nmods; x++) { IPQOSCDBG1(APPLY, "Getting actions of module %s.\n", modnames[x]); res = ipp_mod_list_actions(modnames[x], &actnames, &nacts); if (res != 0) { ipqos_msg(MT_ENOSTR, "ipp_mod_list_actions"); cleanup_string_table(modnames, nmods); return (IPQOS_CONF_ERR); } for (y = 0; y < nacts; y++) { IPQOSCDBG1(APPLY, "deleting action %s\n", actnames[y]); res = ipp_action_destroy(actnames[y], IPP_DESTROY_REF); /* * if fails for reason other than action doesn't * exist or action has dependency. */ if (res != 0 && errno != ENOENT && errno != EBUSY) { ipqos_msg(MT_ENOSTR, "ipp_action_destroy"); cleanup_string_table(modnames, nmods); cleanup_string_table(actnames, nacts); return (IPQOS_CONF_ERR); } if (res == 0) *modified = B_TRUE; } cleanup_string_table(actnames, nacts); } cleanup_string_table(modnames, nmods); return (IPQOS_CONF_SUCCESS); } /* * Trys to flush the configuration. If it fails and nothing has been modified * and force_flush is false just return an error, otherwise persist trying to * completion. * RETURNS: IPQOS_CONF_ERR if flush attempt failed without modifying anything * and force_flush was set to false, otherwise IPQOS_CONF_SUCCESS. */ static int atomic_flush( boolean_t force_flush) { int x = 0; int res; boolean_t modified = B_FALSE; /* * attempt first flush of config. */ res = flush(&modified); if ((force_flush == B_FALSE) && (res != IPQOS_CONF_SUCCESS) && (modified == B_FALSE)) { return (IPQOS_CONF_ERR); } else if (res == IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_SUCCESS); } /* * failed flush that modified config, or force flush set; loop till * successful flush. */ while (res != IPQOS_CONF_SUCCESS) { if (x == 5) { /* 10 secs since start/last message. */ ipqos_msg(MT_ERROR, gettext("Retrying configuration flush.\n")); x = 0; } (void) sleep(2); x++; res = flush(&modified); } return (IPQOS_CONF_SUCCESS); } /* * Performs a flush of the configuration within a signal blocking region * so that there's minimal chance of it being killed and the flush only * partially completing. * RETURNS: IPQOS_CONF_SUCCESS (for symmetry with the other main functions). */ static int flushconf() { int res; /* * make sure that flush is as atomic as possible. */ if ((res = block_all_signals()) == -1) return (IPQOS_CONF_ERR); res = atomic_flush(B_FALSE); /* * restore signals. */ (void) restore_all_signals(); if (res == IPQOS_CONF_SUCCESS) { ipqos_msg(MT_LOG, gettext("Configuration flushed.\n")); } else { ipqos_msg(MT_ENOSTR, "atomic_flush"); } return (res); } static int in_string_table(char *stable[], int size, char *string) { IPQOSCDBG1(L1, "In in_string_table: search string %s\n", string); for (--size; size >= 0; size--) { if (strcmp(stable[size], string) == 0) { IPQOSCDBG1(L1, "Found %s in string table\n", string); return (1); } } return (0); } /* free the memory occupied by the string table ctable and its contents. */ static void cleanup_string_table(char *ctable[], int size) { int x; if (ctable) { for (x = 0; x < size; x++) { free(ctable[x]); } free(ctable); } } #if 0 /* * makes a copy of a string table and returns a ptr to it. * RETURNS: NULL on error or if size was 0, else ptr to copied table. */ static char ** copy_string_table(char *stable1[], int size) { char **st = NULL; int pos; /* create char ptr array */ st = malloc(size * sizeof (char *)); if (st == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (st); } /* create copy of each string from stable1 in array */ for (pos = size - 1; pos >= 0; pos--) { st[pos] = malloc(strlen(stable1[pos] + 1)); if (st[pos] == NULL) { for (pos++; pos < size; pos++) free(st[pos]); free(st); ipqos_msg(MT_ENOSTR, "malloc"); return (NULL); } (void) strcpy(st[pos], stable1[pos]); } return (st); } #endif /* 0 */ /* * retry lookups on filters that soft failed a previous lookup and * were put on the retry list. * RETURNS: IPQOS_CONF_ERR on any errors, else IPQOS_CONF_SUCCESS. */ static int retry_name_lookups( ipqos_conf_action_t *actions) { ipqos_conf_action_t *act; ipqos_conf_filter_t **new_filters; ipqos_conf_filter_t *flt; IPQOSCDBG0(APPLY, "In retry_name_lookups:\n"); for (act = actions; act != NULL; act = act->next) { /* store start of new resolved filters */ GET_LIST_END(&act->filters, &new_filters); /* * do name resolution on retry list adding resolved filters * to end of actions filters. */ for (flt = act->retry_filters; flt != NULL; flt = flt->next) { if (domultihome(flt, new_filters, B_TRUE) != IPQOS_CONF_SUCCESS) { /* if resource failure */ if (flt->nlerr == 0) { return (IPQOS_CONF_ERR); } } } /* add the newly resolved filters to the kernel action */ for (flt = *new_filters; flt != NULL; flt = flt->next) { if (add_filter(act->name, flt, act->module_version) != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } } } return (IPQOS_CONF_SUCCESS); } /* * write the configuration in conf to the file given in dstpath. This * is done by writing first to a temporary file and then renaming that * file to dstpath. This assures an atomic write. * RETURNS: IPQOS_CONF_ERR on any errors, else IPQOS_CONF_SUCCESS. */ static int writeconf( ipqos_conf_action_t *conf, char *dstpath) { FILE *tmpfp; char *tmppath; char *pathend; ipqos_conf_action_t *act; int res; IPQOSCDBG0(L0, "in writeconf\n"); /* construct tmp file path so we can use rename() */ pathend = strrchr(dstpath, '/'); /* dstpath in current dir */ if (pathend == NULL) { tmppath = malloc(strlen("ipqosconf.tmp") + 1); if (tmppath == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } (void) strcpy(tmppath, "ipqosconf.tmp"); /* dstpath in root dir */ } else if (pathend == dstpath) { tmppath = malloc(strlen("/ipqosconf.tmp") + 1); if (tmppath == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } (void) strcpy(tmppath, "/ipqosconf.tmp"); /* not pwd or root */ } else { *pathend = '\0'; tmppath = malloc(strlen(dstpath) + strlen("/ipqosconf.tmp") + 1); if (tmppath == NULL) { ipqos_msg(MT_ENOSTR, "malloc"); return (IPQOS_CONF_ERR); } (void) strcpy(tmppath, dstpath); (void) strcat(tmppath, "/ipqosconf.tmp"); *pathend = '/'; } /* open tmp file */ tmpfp = fopen(tmppath, "w"); if (tmpfp == NULL) { ipqos_msg(MT_ENOSTR, "fopen"); free(tmppath); return (IPQOS_CONF_ERR); } /* write out format version */ (void) fprintf(tmpfp, "%s %d.%d\n\n", IPQOS_FMT_VERSION_STR, IPQOS_CUR_FMT_MAJOR_VER, IPQOS_CUR_FMT_MINOR_VER); /* * loop through actions in list writing ipqosconf originated * ones out to the tmp file. */ for (act = conf; act != NULL; act = act->next) { if (act->params->originator == IPP_CONFIG_IPQOSCONF) { res = printaction(tmpfp, act, 0, 0); if (res != IPQOS_CONF_SUCCESS) { free(tmppath); (void) fclose(tmpfp); return (res); } } } (void) fclose(tmpfp); /* rename tmp file to dst file */ if (rename(tmppath, dstpath) != 0) { ipqos_msg(MT_ENOSTR, "rename"); free(tmppath); return (IPQOS_CONF_ERR); } free(tmppath); return (IPQOS_CONF_SUCCESS); } /* * read the configuration back from the kernel and then write each of the * actions read to IPQOS_CONF_INIT_PATH. * RETURNS: IPQOS_CONF_ERR if error, else IPQOS_CONF_SUCCESS. */ static int commitconf() { int ret; ipqos_conf_action_t *conf; IPQOSCDBG0(L0, "In commitconf\n"); /* read the configuration from the kernel */ ret = readkconf(&conf); if (ret != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* dissallow a null config to be stored (we can't read one in) */ if (conf == NULL) { ipqos_msg(MT_ERROR, gettext("Can't commit a null configuration.\n")); return (IPQOS_CONF_ERR); } /* make sure if we create file that perms are 644 */ (void) umask(S_IXUSR | S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH); /* write the configuration to the init file */ ret = writeconf(conf, IPQOS_CONF_INIT_PATH); if (ret != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } ipqos_msg(MT_LOG, gettext("Current configuration saved to init file.\n")); return (IPQOS_CONF_SUCCESS); } /* * Called in the event of a failed rollback. It first flushes the * current configuration, then attempts to apply the oconf (the old * one), and if that fails flushes again. * * RETURNS: IPQOS_CONF_ERR if the application of old config fails, * else IPQOS_CONF_SUCCESS. */ static int rollback_recover( ipqos_conf_action_t *oconf) { int res; IPQOSCDBG0(RBK, "In rollback_recover\n"); /* * flush configuration. */ (void) atomic_flush(B_TRUE); /* * mark all elements of old config for application. */ mark_config_new(oconf); /* * attempt to apply old config. */ res = applydiff(oconf, NULL); /* * if failed force flush of config. */ if (res != IPQOS_CONF_SUCCESS) { (void) atomic_flush(B_TRUE); return (IPQOS_CONF_ERR); } return (IPQOS_CONF_SUCCESS); } /* * read and apply the configuration contained if file ifile to the kernel. * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int applyconf(char *ifile) { FILE *ifp; ipqos_conf_action_t *conf = NULL; ipqos_conf_action_t *oconf = NULL; ipqos_conf_action_t *act, *oact; int res; IPQOSCDBG0(L0, "In applyconf:\n"); /* if filename '-' read from stdin */ if (strcmp(ifile, "-") == 0) { ifp = stdin; } else { ifp = fopen(ifile, "r"); if (ifp == NULL) { ipqos_msg(MT_ERROR, gettext("Opening file %s for read: %s.\n"), ifile, strerror(errno)); return (IPQOS_CONF_ERR); } } /* read in new configuration */ res = readconf(ifp, &conf); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* check configuration is valid */ res = validconf(conf, 1); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* read in kernel configuration */ res = readkconf(&oconf); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* * check there are no same named actions in both config file and the * the kernel that are for a different module. The application * system can't handle these as we would try to add the new * action before we deleted the old one and because actions * in the kernel are indexed solely on their name (their module * isn't included) the kernel would return an error. We want * to avoid this error and the resulting rollback. */ for (act = conf; act != NULL; act = act->next) { for (oact = oconf; oact != NULL; oact = oact->next) { /* found action */ if (strcmp(act->name, oact->name) == 0) { /* different module */ if (strcmp(act->module, oact->module) != 0) { ipqos_msg(MT_ERROR, gettext("Action at line %u has " "same name as currently " "installed action, but is for a " "different module.\n"), act->lineno); goto fail; /* same module - stop search */ } else { break; } } } } /* create links between actions for use with deletions etc.. */ res = validconf(oconf, 0); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* diff conf file against kernel */ res = diffconf(oconf, conf); if (res != IPQOS_CONF_SUCCESS) { goto fail; } /* make kernel mods as atomic as possible */ if ((res = block_all_signals()) == -1) { res = IPQOS_CONF_ERR; goto fail; } /* apply difference to kernel */ res = applydiff(conf, oconf); #ifdef _IPQOS_CONF_DEBUG if (force_rback || res != IPQOS_CONF_SUCCESS) { #else if (res != IPQOS_CONF_SUCCESS) { #endif /* _IPQOS_CONF_DEBUG */ res = rollback(conf, oconf); if (res != IPQOS_CONF_SUCCESS) { res = rollback_recover(oconf); if (res != IPQOS_CONF_SUCCESS) { /* system left flushed */ ipqos_msg(MT_ERROR, gettext("Failed to rollback from failed " "configuration, configuration flushed.\n")); res = IPQOS_CONF_RECOVER_ERR; } else { /* old config re-applied */ ipqos_msg(MT_ERROR, gettext("Configuration failed, system " "state unchanged.\n")); res = IPQOS_CONF_ERR; } } else { ipqos_msg(MT_ERROR, gettext("Configuration failed, system " "state unchanged.\n")); res = IPQOS_CONF_ERR; } goto fail; } /* retry any soft name lookup failures */ res = retry_name_lookups(conf); if (res != IPQOS_CONF_SUCCESS) { res = rollback(conf, oconf); if (res != IPQOS_CONF_SUCCESS) { res = rollback_recover(oconf); if (res != IPQOS_CONF_SUCCESS) { /* system left flushed */ ipqos_msg(MT_ERROR, gettext("Failed to rollback from failed " "configuration, configuration flushed.\n")); res = IPQOS_CONF_RECOVER_ERR; } else { /* old config re-applied */ ipqos_msg(MT_ERROR, gettext("Configuration failed, system " "state unchanged.\n")); res = IPQOS_CONF_ERR; } } else { ipqos_msg(MT_ERROR, gettext("Configuration failed, system " "state unchanged.\n")); res = IPQOS_CONF_ERR; } goto fail; } ipqos_msg(MT_LOG, gettext("IPQoS configuration applied.\n")); /* re-enable signals */ (void) restore_all_signals(); (void) fclose(ifp); free_actions(conf); free_actions(oconf); return (IPQOS_CONF_SUCCESS); fail: (void) fclose(ifp); (void) restore_all_signals(); if (conf) free_actions(conf); if (oconf) free_actions(oconf); if (res == IPQOS_CONF_RECOVER_ERR) ipqos_msg(MT_LOG, gettext("Configuration flushed.\n")); return (res); } static sigset_t set, oset; static int block_all_signals() { if (sigfillset(&set) == -1) { ipqos_msg(MT_ENOSTR, "sigfillset"); return (-1); } if (sigprocmask(SIG_SETMASK, &set, &oset) == -1) { ipqos_msg(MT_ENOSTR, "sigprocmask"); return (-1); } return (0); } static int restore_all_signals() { if (sigprocmask(SIG_SETMASK, &oset, NULL) == -1) { ipqos_msg(MT_ENOSTR, "sigprocmask"); return (-1); } return (0); } static int unlock(int fd) { if (lockf(fd, F_ULOCK, 0) == -1) { ipqos_msg(MT_ENOSTR, "lockf"); return (-1); } return (0); } static int lock() { int fd; struct stat sbuf1; struct stat sbuf2; /* * Open the file with O_CREAT|O_EXCL. If it exists already, it * will fail. If it already exists, check whether it looks like * the one we created. */ (void) umask(0077); if ((fd = open(IPQOS_CONF_LOCK_FILE, O_EXCL|O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)) == -1) { if (errno != EEXIST) { /* Some other problem. */ ipqos_msg(MT_ENOSTR, gettext("Cannot open lock file %s"), IPQOS_CONF_LOCK_FILE); return (-1); } /* * open() returned an EEXIST error. We don't fail yet * as it could be a residual from a previous * execution. However, we need to clear errno here. * If we don't and print_cmd_buf() is later invoked * as the result of a parsing error, it * will assume that the current error is EEXIST and * that a corresponding error message has already been * printed, which results in an incomplete error * message. If errno is zero, print_cmd_buf() will * assume that it is called as a result of a * parsing error and will print the appropriate * error message. */ errno = 0; /* * File exists. make sure it is OK. We need to lstat() * as fstat() stats the file pointed to by the symbolic * link. */ if (lstat(IPQOS_CONF_LOCK_FILE, &sbuf1) == -1) { ipqos_msg(MT_ENOSTR, gettext("Cannot lstat lock file %s\n"), IPQOS_CONF_LOCK_FILE); return (-1); } /* * Check whether it is a regular file and not a symbolic * link. Its link count should be 1. The owner should be * root and the file should be empty. */ if (!S_ISREG(sbuf1.st_mode) || sbuf1.st_nlink != 1 || sbuf1.st_uid != 0 || sbuf1.st_size != 0) { ipqos_msg(MT_ERROR, gettext("Bad lock file %s.\n"), IPQOS_CONF_LOCK_FILE); return (-1); } if ((fd = open(IPQOS_CONF_LOCK_FILE, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)) == -1) { ipqos_msg(MT_ENOSTR, gettext("Cannot open lock file %s"), IPQOS_CONF_LOCK_FILE); return (-1); } /* Check whether we opened the file that we lstat()ed. */ if (fstat(fd, &sbuf2) == -1) { ipqos_msg(MT_ENOSTR, gettext("Cannot fstat lock file %s\n"), IPQOS_CONF_LOCK_FILE); return (-1); } if (sbuf1.st_dev != sbuf2.st_dev || sbuf1.st_ino != sbuf2.st_ino) { /* File changed after we did the lstat() above */ ipqos_msg(MT_ERROR, gettext("Bad lock file %s.\n"), IPQOS_CONF_LOCK_FILE); return (-1); } } if (lockf(fd, F_LOCK, 0) == -1) { ipqos_msg(MT_ENOSTR, "lockf"); return (-1); } return (fd); } /* * print the current kernel configuration out to stdout. If viewall * is set this causes more verbose configuration listing including * showing objects we didn't create, each instance of a mhome filter, * etc.. see printaction(). * RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES. */ static int viewconf(int viewall) { ipqos_conf_action_t *conf = NULL; ipqos_conf_action_t *act; int ret; IPQOSCDBG0(L0, "In viewconf\n"); /* get kernel configuration */ ret = readkconf(&conf); if (ret != IPQOS_CONF_SUCCESS) { return (IPQOS_CONF_ERR); } /* write out format version */ if (conf != NULL) { (void) fprintf(stdout, "%s %d.%d\n\n", IPQOS_FMT_VERSION_STR, IPQOS_CUR_FMT_MAJOR_VER, IPQOS_CUR_FMT_MINOR_VER); } /* print each of the actions in the kernel config to stdout */ for (act = conf; act != NULL; act = act->next) { ret = printaction(stdout, act, viewall, 0); if (ret != IPQOS_CONF_SUCCESS) { free_actions(conf); return (ret); } (void) fprintf(stdout, "\n"); } free_actions(conf); return (IPQOS_CONF_SUCCESS); } /* * debug function that reads the config file and prints it out after * interpreting to stdout. */ #ifdef _IPQOS_CONF_DEBUG static int viewcfile(char *cfile) { ipqos_conf_action_t *conf; ipqos_conf_action_t *act; int res; FILE *ifp; int viewall = 1; IPQOSCDBG0(L0, "In viewcfile\n"); ifp = fopen(cfile, "r"); if (ifp == NULL) { ipqos_msg(MT_ERROR, gettext("Opening file %s for read: %s.\n"), cfile, strerror(errno)); return (IPQOS_CONF_ERR); } res = readconf(ifp, &conf); if (res != IPQOS_CONF_SUCCESS) { free(ifp); return (IPQOS_CONF_ERR); } /* print each of the actions in the kernel config to stdout */ for (act = conf; act != NULL; act = act->next) { res = printaction(stdout, act, viewall, 0); if (res != IPQOS_CONF_SUCCESS) { free(ifp); return (res); } (void) fprintf(stdout, "\n"); } (void) fprintf(stdout, "\n"); return (IPQOS_CONF_SUCCESS); } #endif /* _IPQOS_CONF_DEBUG */ static void usage(void) { (void) fprintf(stderr, gettext("usage:\n" "\tipqosconf [-sv] -a file|-\n" "\tipqosconf -c\n" "\tipqosconf -l\n" "\tipqosconf -L\n" "\tipqosconf -f\n")); } int main(int argc, char *argv[]) { int c; char *ifile = NULL; int args; int ret; int cmd; int viewall = 0; int lfp; /* init global flags */ use_syslog = verbose = 0; /* init current line number */ lineno = 0; /* setup internationalisation */ (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); /* setup syslog parameters */ openlog("ipqosconf", 0, LOG_USER); args = 0; /* enable debug options */ #ifdef _IPQOS_CONF_DEBUG #define DBGOPTS "rz:" #else #define DBGOPTS #endif /* _IPQOS_CONF_DEBUG */ while ((c = getopt(argc, argv, "sca:vflL" DBGOPTS)) != EOF) { switch (c) { #ifdef _IPQOS_CONF_DEBUG case 'z': cmd = -1; ifile = optarg; if (*ifile == '\0') { usage(); exit(1); } args++; break; case 'r': force_rback++; break; #endif /* _IPQOS_CONF_DEBUG */ case 'c': cmd = IPQOS_CONF_COMMIT; args++; break; case 'a': cmd = IPQOS_CONF_APPLY; ifile = optarg; if (*ifile == '\0') { usage(); exit(1); } args++; break; case 'f': cmd = IPQOS_CONF_FLUSH; args++; break; case 'l': cmd = IPQOS_CONF_VIEW; args++; break; case 'L': cmd = IPQOS_CONF_VIEW; viewall++; args++; break; case 'v': verbose++; break; case 's': use_syslog++; break; case '?': usage(); return (1); } } /* * dissallow non-option args, > 1 cmd args and syslog/verbose flags set * for anything but apply. */ if (optind != argc || args > 1 || use_syslog && cmd != IPQOS_CONF_APPLY || verbose && cmd != IPQOS_CONF_APPLY) { usage(); exit(1); } /* if no cmd option then show config */ if (args == 0) { cmd = IPQOS_CONF_VIEW; } /* stop concurrent ipqosconf invocations */ lfp = lock(); if (lfp == -1) { exit(1); } switch (cmd) { #ifdef _IPQOS_CONF_DEBUG case -1: ret = viewcfile(ifile); break; #endif /* _IPQOS_CONF_DEBUG */ case IPQOS_CONF_APPLY: ret = applyconf(ifile); break; case IPQOS_CONF_COMMIT: ret = commitconf(); break; case IPQOS_CONF_VIEW: ret = viewconf(viewall); break; case IPQOS_CONF_FLUSH: ret = flushconf(); break; } (void) unlock(lfp); return (ret); }