diff --git a/doc/KEEPALIVED-MIB.txt b/doc/KEEPALIVED-MIB.txt index 38ab6a422..234bcadd4 100644 --- a/doc/KEEPALIVED-MIB.txt +++ b/doc/KEEPALIVED-MIB.txt @@ -22,12 +22,14 @@ IMPORTS FROM SNMPv2-TC; keepalived MODULE-IDENTITY - LAST-UPDATED "202212090001Z" + LAST-UPDATED "202403180001Z" ORGANIZATION "Keepalived" CONTACT-INFO "http://www.keepalived.org" DESCRIPTION "This MIB describes objects used by keepalived, both for VRRP and health checker." + REVISION "202403180001Z" + DESCRIPTION "add separate path and argv[0] for scripts" REVISION "202212090001Z" DESCRIPTION "add VRRPv3 checksum as VRRPv2" REVISION "202109230001Z" @@ -2587,7 +2589,8 @@ VrrpScriptEntry ::= SEQUENCE { vrrpScriptResult INTEGER, vrrpScriptRise Unsigned32, vrrpScriptFall Unsigned32, - vrrpScriptWgtRev Integer32 + vrrpScriptWgtRev Integer32, + vrrpScriptPath DisplayString } vrrpScriptIndex OBJECT-TYPE @@ -2668,6 +2671,14 @@ vrrpScriptWgtRev OBJECT-TYPE "Weight reverse for the script if successful." ::= { vrrpScriptEntry 9 } +vrrpScriptPath OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Path to file to be executed when running the script." + ::= { vrrpScriptEntry 10 } + -- VRRP files -- see vrrp_track.h @@ -3413,7 +3424,9 @@ VirtualServerEntry ::= SEQUENCE { virtualServerTunnelType INTEGER, virtualServerTunnelPort InetPortNumber, virtualServerTunnelCsum INTEGER, - virtualServerName DisplayString + virtualServerName DisplayString, + virtualServerQuorumUpPath DisplayString, + virtualServerQuorumDownPath DisplayString } virtualServerIndex OBJECT-TYPE @@ -4055,6 +4068,22 @@ virtualServerName OBJECT-TYPE "Optional SNMP name of this virtual server." ::= { virtualServerEntry 71 } +virtualServerQuorumUpPath OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Path to command to execute when the quorum is met." + ::= { virtualServerEntry 72 } + +virtualServerQuorumDownPath OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Path to command to execute when the quorum is not met." + ::= { virtualServerEntry 73 } + -- real servers @@ -4130,7 +4159,9 @@ RealServerEntry ::= SEQUENCE { realServerTunnelType INTEGER, realServerTunnelPort InetPortNumber, realServerTunnelCsum INTEGER, - realServerName DisplayString + realServerName DisplayString, + realServerNotifyUpPath DisplayString, + realServerNotifyDownPath DisplayString } realServerIndex OBJECT-TYPE @@ -4619,6 +4650,22 @@ realServerName OBJECT-TYPE "Optional SNMP name of this real server." ::= { realServerEntry 55 } +realServerNotifyUpPath OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Path to command to execute when this server becomes alive." + ::= { realServerEntry 56 } + +realServerNotifyDownPath OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Path to command to execute when this server becomes dead." + ::= { realServerEntry 57 } + lvsSyncDaemon OBJECT IDENTIFIER ::= { check 6 } lvsSyncDaemonEnabled OBJECT-TYPE @@ -5075,7 +5122,8 @@ vrrpScriptGroup OBJECT-GROUP vrrpScriptResult, vrrpScriptRise, vrrpScriptFall, - vrrpScriptWgtRev + vrrpScriptWgtRev, + vrrpScriptPath } STATUS current DESCRIPTION @@ -5249,7 +5297,9 @@ virtualServerGroup OBJECT-GROUP virtualServerTunnelType, virtualServerTunnelPort, virtualServerTunnelCsum, - virtualServerName + virtualServerName, + virtualServerQuorumUpPath, + virtualServerQuorumDownPath } STATUS current DESCRIPTION @@ -5311,7 +5361,9 @@ realServerGroup OBJECT-GROUP realServerTunnelType, realServerTunnelPort, realServerTunnelCsum, - realServerName + realServerName, + realServerNotifyUpPath, + realServerNotifyDownPath } STATUS current DESCRIPTION diff --git a/doc/man/man5/keepalived.conf.5.in b/doc/man/man5/keepalived.conf.5.in index 56eb02f7c..37dcc7461 100644 --- a/doc/man/man5/keepalived.conf.5.in +++ b/doc/man/man5/keepalived.conf.5.in @@ -246,6 +246,10 @@ possibly following any cleanup actions needed. \fBchecker_process_name\fR NAME \fBbfd_process_name\fR NAME + # keepalived by default resolves script path names to remove symlinks. + # To keep symlinks in pathnames, specify use_syslink_paths. + \fBuse_symlink_paths \fR[] + # The startup and shutdown scripts are run once, when keepalived starts # before any child processes are run, and when keepalived stops after # all child processes have terminated, respectively. @@ -2073,12 +2077,12 @@ The syntax for vrrp_instance is : # See description of global vrrp_skip_check_adv_addr, which # sets the default value. Defaults to vrrp_skip_check_adv_addr - \fBskip_check_adv_addr \fR[on|off|true|false|yes|no] + \fBskip_check_adv_addr \fR[] # See description of global vrrp_strict # If strict_mode is not specified, it takes the value of vrrp_strict. # If strict_mode without a parameter is specified, it defaults to on. - \fBstrict_mode \fR[on|off|true|false|yes|no] + \fBstrict_mode \fR[] # Debug level, not implemented yet. # LEVEL is a number in the range 0 to 4 diff --git a/keepalived/check/check_data.c b/keepalived/check/check_data.c index 73ba766bc..74b45aaba 100644 --- a/keepalived/check/check_data.c +++ b/keepalived/check/check_data.c @@ -484,6 +484,17 @@ format_decimal(unsigned long val, int dp) return buf; } +static void +dump_notify_vs_rs_script(FILE *fp, const notify_script_t *script, const char *type, const char *state) +{ + if (script->path) + conf_write(fp, " %s %s notify script = %s, params = %s, uid:gid %u:%u", type, state, + script->path, cmd_str(script), script->uid, script->gid); + else + conf_write(fp, " %s %s notify script = %s, uid:gid %u:%u", type, state, + cmd_str(script), script->uid, script->gid); +} + static void dump_rs(FILE *fp, const real_server_t *rs) { @@ -511,11 +522,9 @@ dump_rs(FILE *fp, const real_server_t *rs) conf_write(fp, " Inhibit on failure is %s", rs->inhibit ? "ON" : "OFF"); if (rs->notify_up) - conf_write(fp, " RS up notify script = %s, uid:gid %u:%u", - cmd_str(rs->notify_up), rs->notify_up->uid, rs->notify_up->gid); + dump_notify_vs_rs_script(fp, rs->notify_up, "RS", "up"); if (rs->notify_down) - conf_write(fp, " RS down notify script = %s, uid:gid %u:%u", - cmd_str(rs->notify_down), rs->notify_down->uid, rs->notify_down->gid); + dump_notify_vs_rs_script(fp, rs->notify_down, "RS", "down"); if (rs->virtualhost) conf_write(fp, " VirtualHost = '%s'", rs->virtualhost); #ifdef _WITH_SNMP_CHECKER_ @@ -730,11 +739,9 @@ dump_vs(FILE *fp, const virtual_server_t *vs) conf_write(fp, " Inhibit on failure is %s", vs->inhibit ? "ON" : "OFF"); conf_write(fp, " quorum = %u, hysteresis = %u", vs->quorum, vs->hysteresis); if (vs->notify_quorum_up) - conf_write(fp, " Quorum up notify script = %s, uid:gid %u:%u", - cmd_str(vs->notify_quorum_up), vs->notify_quorum_up->uid, vs->notify_quorum_up->gid); + dump_notify_vs_rs_script(fp, vs->notify_quorum_up, "Quorum", "up"); if (vs->notify_quorum_down) - conf_write(fp, " Quorum down notify script = %s, uid:gid %u:%u", - cmd_str(vs->notify_quorum_down), vs->notify_quorum_down->uid, vs->notify_quorum_down->gid); + dump_notify_vs_rs_script(fp, vs->notify_quorum_down, "Quorum", "down"); if (vs->ha_suspend) conf_write(fp, " Using HA suspend"); conf_write(fp, " Using smtp notification = %s", vs->smtp_alert ? "yes" : "no"); diff --git a/keepalived/check/check_misc.c b/keepalived/check/check_misc.c index c388e89d8..0503101b4 100644 --- a/keepalived/check/check_misc.c +++ b/keepalived/check/check_misc.c @@ -57,8 +57,7 @@ free_misc_check(checker_t *checker) { misc_checker_t *misck_checker = checker->data; - if (misck_checker->script.args) - FREE(misck_checker->script.args); + notify_free_script(&misck_checker->script); FREE(misck_checker); FREE(checker); } @@ -70,6 +69,8 @@ dump_misc_check(FILE *fp, const checker_t *checker) char time_str[26]; conf_write(fp, " Keepalive method = MISC_CHECK"); + if (misck_checker->script.path) + conf_write(fp, " path = %s", misck_checker->script.path); conf_write(fp, " script = %s", cmd_str(&misck_checker->script)); conf_write(fp, " timeout = %lu", misck_checker->timeout/TIMER_HZ); conf_write(fp, " dynamic = %s", misck_checker->dynamic ? "YES" : "NO"); diff --git a/keepalived/check/check_snmp.c b/keepalived/check/check_snmp.c index b6fd85bff..2a4042658 100644 --- a/keepalived/check/check_snmp.c +++ b/keepalived/check/check_snmp.c @@ -125,6 +125,8 @@ enum check_snmp_virtualserver_magic { #endif #endif CHECK_SNMP_VSNAME, + CHECK_SNMP_VSQUORUMUPPATH, + CHECK_SNMP_VSQUORUMDOWNPATH, }; enum check_snmp_realserver_magic { @@ -188,6 +190,8 @@ enum check_snmp_realserver_magic { #endif #endif CHECK_SNMP_RSNAME, + CHECK_SNMP_RSNOTIFYUPPATH, + CHECK_SNMP_RSNOTIFYDOWNPATH, }; #define STATE_VSGM_FWMARK 1 @@ -756,6 +760,16 @@ check_snmp_virtualserver(struct variable *vp, oid *name, size_t *length, *var_len = strlen(v->snmp_name); ret.cp = v->snmp_name; return ret.p; + case CHECK_SNMP_VSQUORUMUPPATH: + if (!v->notify_quorum_up) break; + ret.cp = v->notify_quorum_up->path ? v->notify_quorum_up->path : v->notify_quorum_up->args[0] ; + *var_len = strlen(ret.cp); + return ret.p; + case CHECK_SNMP_VSQUORUMDOWNPATH: + if (!v->notify_quorum_down) break; + ret.cp = v->notify_quorum_down->path ? v->notify_quorum_down->path : v->notify_quorum_down->args[0]; + *var_len = strlen(ret.cp); + return ret.p; default: return NULL; } @@ -1178,6 +1192,18 @@ check_snmp_realserver(struct variable *vp, oid *name, size_t *length, *var_len = strlen(be->snmp_name); ret.cp = be->snmp_name; return ret.p; + case CHECK_SNMP_RSNOTIFYUPPATH: + if (type == STATE_RS_SORRY) break; + if (!be->notify_up) break; + ret.cp = be->notify_up->path ? be->notify_up->path : be->notify_up->args[0]; + *var_len = strlen(ret.cp); + return ret.p; + case CHECK_SNMP_RSNOTIFYDOWNPATH: + if (type == STATE_RS_SORRY) break; + if (!be->notify_down) break; + ret.cp = be->notify_down->path ? be->notify_down->path : be->notify_down->args[0]; + *var_len = strlen(ret.cp); + return ret.p; default: return NULL; } @@ -1456,6 +1482,10 @@ static struct variable8 check_vars[] = { #endif {CHECK_SNMP_VSNAME, ASN_OCTET_STR, RONLY, check_snmp_virtualserver, 3, {3, 1, 71}}, + {CHECK_SNMP_VSQUORUMUPPATH, ASN_OCTET_STR, RONLY, + check_snmp_virtualserver, 3, {3, 1, 72}}, + {CHECK_SNMP_VSQUORUMDOWNPATH, ASN_OCTET_STR, RONLY, + check_snmp_virtualserver, 3, {3, 1, 73}}, /* realServerTable */ {CHECK_SNMP_RSTYPE, ASN_INTEGER, RONLY, @@ -1572,6 +1602,10 @@ static struct variable8 check_vars[] = { #endif {CHECK_SNMP_RSNAME, ASN_OCTET_STR, RONLY, check_snmp_realserver, 3, {4, 1, 55}}, + {CHECK_SNMP_RSNOTIFYUPPATH, ASN_OCTET_STR, RONLY, + check_snmp_realserver, 3, {4, 1, 56}}, + {CHECK_SNMP_RSNOTIFYDOWNPATH, ASN_OCTET_STR, RONLY, + check_snmp_realserver, 3, {4, 1, 57}}, #ifdef _WITH_VRRP_ /* LVS sync daemon configuration */ diff --git a/keepalived/core/global_data.c b/keepalived/core/global_data.c index c2b271d71..6e26a7659 100644 --- a/keepalived/core/global_data.c +++ b/keepalived/core/global_data.c @@ -367,6 +367,8 @@ init_global_data(data_t * data, data_t *prev_global_data, bool copy_unchangeable } } + set_symlinks(global_data->use_symlinks); + /* Check that there aren't conflicts with the notify FIFOs */ #ifdef _WITH_VRRP_ /* If the global and vrrp notify FIFOs are the same, then data will be @@ -535,6 +537,29 @@ open_dump_file(const char *file_name) return fp; } +static void +write_fifo_details(FILE *fp, const notify_fifo_t *fifo, const char *type) +{ + conf_write(fp, " %s notify fifo = %s, uid:gid %u:%u", type, fifo->name, fifo->uid, fifo->gid); + + if (!fifo->script) + return; + + if (fifo->script->path) + conf_write(fp, " %s notify fifo path = %s, script = %s, uid:gid %u:%u", + type, + fifo->script->path, + cmd_str(fifo->script), + fifo->script->uid, + fifo->script->gid); + else + conf_write(fp, " %s notify fifo script = %s, uid:gid %u:%u", + type, + cmd_str(fifo->script), + fifo->script->uid, + fifo->script->gid); +} + void dump_global_data(FILE *fp, data_t * data) { @@ -578,6 +603,7 @@ dump_global_data(FILE *fp, data_t * data) if (data->bfd_process_name) conf_write(fp, " BFD process name = %s", data->bfd_process_name); #endif + conf_write(fp, " %s symlinks in script paths", data->use_symlinks ? "Keep" : "Replace"); if (data->router_id) conf_write(fp, " Router ID = %s", data->router_id); if (data->smtp_server.ss_family) { @@ -623,6 +649,7 @@ dump_global_data(FILE *fp, data_t * data) if (data->reload_file) conf_write(fp, " Reload_file = %s", data->reload_file); #endif + conf_write(fp, " keep script symlinks = %s", data->use_symlinks ? "true" : "false"); if (data->config_directory) conf_write(fp, " config save directory = %s", data->config_directory); if (data->data_use_instance) @@ -687,33 +714,15 @@ dump_global_data(FILE *fp, data_t * data) conf_write(fp, " LVS flush on stop = %s", data->lvs_flush_on_stop == LVS_FLUSH_FULL ? "full" : data->lvs_flush_on_stop == LVS_FLUSH_VS ? "VS" : "disabled"); #endif - if (data->notify_fifo.name) { - conf_write(fp, " Global notify fifo = %s, uid:gid %u:%u", data->notify_fifo.name, data->notify_fifo.uid, data->notify_fifo.gid); - if (data->notify_fifo.script) - conf_write(fp, " Global notify fifo script = %s, uid:gid %u:%u", - cmd_str(data->notify_fifo.script), - data->notify_fifo.script->uid, - data->notify_fifo.script->gid); - } + if (data->notify_fifo.name) + write_fifo_details(fp, &data->notify_fifo, "Global"); #ifdef _WITH_VRRP_ - if (data->vrrp_notify_fifo.name) { - conf_write(fp, " VRRP notify fifo = %s, uid:gid %u:%u", data->vrrp_notify_fifo.name, data->vrrp_notify_fifo.uid, data->vrrp_notify_fifo.gid); - if (data->vrrp_notify_fifo.script) - conf_write(fp, " VRRP notify fifo script = %s, uid:gid %u:%u", - cmd_str(data->vrrp_notify_fifo.script), - data->vrrp_notify_fifo.script->uid, - data->vrrp_notify_fifo.script->gid); - } + if (data->vrrp_notify_fifo.name) + write_fifo_details(fp, &data->vrrp_notify_fifo, "VRRP"); #endif #ifdef _WITH_LVS_ - if (data->lvs_notify_fifo.name) { - conf_write(fp, " LVS notify fifo = %s, uid:gid %u:%u", data->lvs_notify_fifo.name, data->lvs_notify_fifo.uid, data->lvs_notify_fifo.gid); - if (data->lvs_notify_fifo.script) - conf_write(fp, " LVS notify fifo script = %s, uid:gid %u:%u", - cmd_str(data->lvs_notify_fifo.script), - data->lvs_notify_fifo.script->uid, - data->lvs_notify_fifo.script->gid); - } + if (data->lvs_notify_fifo.name) + write_fifo_details(fp, &data->lvs_notify_fifo, "LVS"); #endif #ifdef _WITH_VRRP_ conf_write(fp, " FIFO write vrrp states on reload = %s", data->fifo_write_vrrp_states_on_reload ? "true" : "false"); diff --git a/keepalived/core/global_parser.c b/keepalived/core/global_parser.c index 4c11f39ba..92b20554a 100644 --- a/keepalived/core/global_parser.c +++ b/keepalived/core/global_parser.c @@ -157,6 +157,21 @@ bfd_process_name_handler(const vector_t *strvec) } #endif static void +use_symlink_path_handler(const vector_t *strvec) +{ + int res = true; + + if (vector_size(strvec) >= 2) { + res = check_true_false(strvec_slot(strvec,1)); + if (res < 0) { + report_config_error(CONFIG_GENERAL_ERROR, "Invalid value '%s' for global use_symlink_path specified", strvec_slot(strvec, 1)); + return; + } + } + + global_data->use_symlinks = res; +} +static void routerid_handler(const vector_t *strvec) { if (vector_size(strvec) < 2) { @@ -1319,12 +1334,32 @@ vrrp_check_unicast_src_handler(__attribute__((unused)) const vector_t *strvec) static void vrrp_check_adv_addr_handler(__attribute__((unused)) const vector_t *strvec) { - global_data->vrrp_skip_check_adv_addr = 1; + int res = true; + + if (vector_size(strvec) >= 2) { + res = check_true_false(strvec_slot(strvec,1)); + if (res < 0) { + report_config_error(CONFIG_GENERAL_ERROR, "Invalid value '%s' for global vrrp_check_adv_addr specified", strvec_slot(strvec, 1)); + return; + } + } + + global_data->vrrp_skip_check_adv_addr = res; } static void vrrp_strict_handler(__attribute__((unused)) const vector_t *strvec) { - global_data->vrrp_strict = 1; + int res = true; + + if (vector_size(strvec) >= 2) { + res = check_true_false(strvec_slot(strvec,1)); + if (res < 0) { + report_config_error(CONFIG_GENERAL_ERROR, "Invalid value '%s' for global vrrp_strict specified", strvec_slot(strvec, 1)); + return; + } + } + + global_data->vrrp_strict = res; } static void vrrp_prio_handler(const vector_t *strvec) @@ -2347,6 +2382,7 @@ init_global_keywords(bool global_active) #ifdef _WITH_BFD_ install_keyword("bfd_process_name", &bfd_process_name_handler); #endif + install_keyword("use_symlink_paths", &use_symlink_path_handler); install_keyword("router_id", &routerid_handler); install_keyword("notification_email_from", &emailfrom_handler); install_keyword("smtp_server", &smtpserver_handler); diff --git a/keepalived/include/global_data.h b/keepalived/include/global_data.h index 80750bd56..3ec119925 100644 --- a/keepalived/include/global_data.h +++ b/keepalived/include/global_data.h @@ -120,6 +120,7 @@ typedef struct _data { unsigned startup_script_timeout; notify_script_t *shutdown_script; unsigned shutdown_script_timeout; + bool use_symlinks; #ifndef _ONE_PROCESS_DEBUG_ const char *reload_check_config; /* log file name for validating new configuration before reloading */ const char *reload_time_file; diff --git a/keepalived/vrrp/vrrp_data.c b/keepalived/vrrp/vrrp_data.c index 4aef19c98..d3757b439 100644 --- a/keepalived/vrrp/vrrp_data.c +++ b/keepalived/vrrp/vrrp_data.c @@ -192,8 +192,12 @@ dump_notify_script(FILE *fp, const notify_script_t *script, const char *type) if (!script) return; - conf_write(fp, " %s state transition script = %s, uid:gid %u:%u" - , type, cmd_str(script), script->uid, script->gid); + if (script->path) + conf_write(fp, " %s state transition script = %s, params = %s, uid:gid %u:%u" + , type, script->path, cmd_str(script), script->uid, script->gid); + else + conf_write(fp, " %s state transition script = %s, uid:gid %u:%u" + , type, cmd_str(script), script->uid, script->gid); } static void @@ -275,7 +279,7 @@ free_vscript(vrrp_script_t *vscript) list_del_init(&vscript->e_list); free_tracking_obj_list(&vscript->tracking_vrrp); FREE_CONST(vscript->sname); - FREE_PTR(vscript->script.args); + notify_free_script(&vscript->script); FREE(vscript); } static void @@ -293,6 +297,8 @@ dump_vscript(FILE *fp, const vrrp_script_t *vscript) conf_write(fp, " VRRP Script = %s", vscript->sname); conf_write(fp, " Command = %s", cmd_str(&vscript->script)); + if (vscript->script.path) + conf_write(fp, " Path = %s", vscript->script.path); conf_write(fp, " Interval = %lu sec", vscript->interval / TIMER_HZ); conf_write(fp, " Timeout = %lu sec", vscript->timeout / TIMER_HZ); conf_write(fp, " Weight = %d%s", vscript->weight, vscript->weight_reverse ? " reverse" : ""); diff --git a/keepalived/vrrp/vrrp_scheduler.c b/keepalived/vrrp/vrrp_scheduler.c index f4b9f17a4..fc40d59de 100644 --- a/keepalived/vrrp/vrrp_scheduler.c +++ b/keepalived/vrrp/vrrp_scheduler.c @@ -1270,7 +1270,8 @@ vrrp_script_child_thread(thread_ref_t thread) vscript->state = SCRIPT_STATE_IDLE; timeout = 0; } else { - log_message(LOG_INFO, "kill -%d of process %s(%d) with new state %u failed with errno %d", sig_num, vscript->script.args[0], pid, vscript->state, errno); + log_message(LOG_INFO, "kill -%d of process %s(%d) with new state %u failed with errno %d", + sig_num, vscript->script.path ? vscript->script.path : vscript->script.args[0], pid, vscript->state, errno); timeout = 1000; } } diff --git a/keepalived/vrrp/vrrp_snmp.c b/keepalived/vrrp/vrrp_snmp.c index 1ed12808d..5e1038af5 100644 --- a/keepalived/vrrp/vrrp_snmp.c +++ b/keepalived/vrrp/vrrp_snmp.c @@ -143,6 +143,7 @@ enum snmp_vrrp_magic { VRRP_SNMP_SCRIPT_RESULT, VRRP_SNMP_SCRIPT_RISE, VRRP_SNMP_SCRIPT_FALL, + VRRP_SNMP_SCRIPT_PATH, VRRP_SNMP_FILE_NAME, VRRP_SNMP_FILE_PATH, VRRP_SNMP_FILE_RESULT, @@ -594,6 +595,10 @@ vrrp_snmp_script(struct variable *vp, oid *name, size_t *length, case VRRP_SNMP_SCRIPT_FALL: long_ret.s = scr->fall; return PTR_CAST(u_char, &long_ret); + case VRRP_SNMP_SCRIPT_PATH: + ret.cp = scr->script.path ? scr->script.path : scr->script.args[0]; + *var_len = strlen(ret.cp); + return ret.p; default: break; } @@ -2903,6 +2908,7 @@ static struct variable8 vrrp_vars[] = { {VRRP_SNMP_SCRIPT_RISE, ASN_UNSIGNED, RONLY, vrrp_snmp_script, 3, {9, 1, 7}}, {VRRP_SNMP_SCRIPT_FALL, ASN_UNSIGNED, RONLY, vrrp_snmp_script, 3, {9, 1, 8}}, {VRRP_SNMP_SCRIPT_WEIGHT_REVERSE, ASN_INTEGER, RONLY, vrrp_snmp_script, 3, {9, 1, 9}}, + {VRRP_SNMP_SCRIPT_PATH, ASN_OCTET_STR, RONLY, vrrp_snmp_script, 3, {9, 1, 10}}, /* vrrpRouteNextHopTable */ {VRRP_SNMP_ROUTE_NEXT_HOP_ADDRESS_TYPE, ASN_INTEGER, RONLY, diff --git a/lib/notify.c b/lib/notify.c index eb785787f..4c98bf1e9 100644 --- a/lib/notify.c +++ b/lib/notify.c @@ -65,10 +65,18 @@ static size_t getpwnam_buf_len; static char *path; static bool path_is_malloced; +static bool use_symlinks; + /* Buffer for expanding notify script commands */ static char cmd_str_buf[MAXBUF]; +void +set_symlinks(bool state) +{ + use_symlinks = state; +} + static bool set_script_env(uid_t uid, gid_t gid) { @@ -203,7 +211,7 @@ system_call_script(thread_master_t *m, thread_func_t func, void * arg, unsigned prctl(PR_SET_PDEATHSIG, SIGTERM); args.args = script->args; /* Note: we are casting away constness, since execve parameter type is wrong */ - execve(script->args[0], args.execve_args, environ); + execve(script->path ? script->path : script->args[0], args.execve_args, environ); /* error */ log_message(LOG_ALERT, "Error exec-ing command '%s', error %d: %m", script->args[0], errno); @@ -426,7 +434,7 @@ replace_cmd_name(notify_script_t *script, const char *new_path) script->args = args.cparams; } -/* The following function is essentially __execve() from glibc */ +/* The following function is essentially __execve_common() from glibc posix/execvpe.c */ static int find_path(notify_script_t *script) { @@ -448,16 +456,12 @@ find_path(notify_script_t *script) return ENOENT; filename_len = strlen(file); - if (filename_len >= PATH_MAX) { - ret_val = ENAMETOOLONG; - goto exit1; - } + if (filename_len >= PATH_MAX) + return ENAMETOOLONG; /* Don't search when it contains a slash. */ - if (strchr (file, '/') != NULL) { - ret_val = 0; - goto exit1; - } + if (strchr (file, '/') != NULL) + return 0; /* Get the path if we haven't already done so, and if that doesn't * exist, use CS_PATH */ @@ -478,18 +482,15 @@ find_path(notify_script_t *script) file_len = strnlen (file, NAME_MAX + 1); path_len = strnlen (path, PATH_MAX - 1) + 1; - if (file_len > NAME_MAX) { - ret_val = ENAMETOOLONG; - goto exit1; - } + if (file_len > NAME_MAX) + return ENAMETOOLONG; if (script->uid != our_uid || script->gid != our_gid) { /* Set file access to the relevant uid/gid */ if (script->gid != our_gid) { if (setegid(script->gid)) { log_message(LOG_INFO, "Unable to set egid to %u (%m)", script->gid); - ret_val = EACCES; - goto exit1; + return EACCES; } } @@ -612,7 +613,6 @@ find_path(notify_script_t *script) FREE(sgid_list); } -exit1: /* We tried every element and none of them worked. */ if (got_eacces) { /* At least one failure was due to permissions, so report that error. */ @@ -688,6 +688,85 @@ check_security(const char *filename, bool using_script_security) return flags; } +static char * +clean_path(const char *old_argv) +{ + char *new_argv = MALLOC(strlen(old_argv) + 1 + 1); // We can add an initial '/' + const char *slash, *next_slash; + char *previous_slash; + const char *ip = old_argv; + char *op = new_argv; + + /* Remove leading ./ and ../ */ + if (!strncmp(ip, "./", 2)) + ip += 1; + else if (!strncmp(ip, "../", 3)) + ip += 2; + + /* Remove any leading /../ and /./ recursively */ + while (!strncmp(ip, "/../", 4) || !strncmp(ip, "/./", 3) || !strncmp(ip, "//", 2)) + ip += ip[1] == '/' ? 1 : ip[2] == '/' ? 2 : 3; + + if (*ip != '/') + *op++ = '/'; + + slash = strchr(ip, '/'); + if (!slash) + strcpy(op, slash); + else if (slash > ip) { + strncpy(op, ip, slash - ip); + op += slash - ip; + } + + for (; slash; slash = next_slash) { + next_slash = strchr(slash + 1, '/'); + + if (!next_slash) { + strcpy(op, slash); + break; + } + + /* We are looking for '//', '/./' or '/../' */ + if (next_slash > slash + 3 || + (slash[1] != '.' && slash[1] != '/') || + (slash[1] == '.' && + (slash[2] != '.' && slash[2] != '/'))) { + strncpy(op, slash, next_slash - slash); + op += next_slash - slash; + continue; + } + + /* We have found one of them */ + + if (next_slash == slash + 1) { + /* // */ + ip++; + continue; + } + + if (next_slash == slash + 2) { + /* /./ */ + ip += 2; + continue; + } + + /* /../ - we must remove previous element */ + previous_slash = strrchr(new_argv, '/'); + if (!previous_slash) + op = new_argv; + else + op = previous_slash; + *op = '\0'; + } + + if (!strcmp(old_argv, new_argv)) { + FREE(new_argv); + return NULL; + } + + return new_argv; +} + unsigned check_script_secure(notify_script_t *script, #ifndef _HAVE_LIBMAGIC_ @@ -696,14 +775,16 @@ check_script_secure(notify_script_t *script, magic_t magic) { unsigned flags; - int ret, ret_real, ret_new; - struct stat file_buf, real_buf; + int ret; + struct stat file_buf; bool need_script_protection = false; - char *new_path; - char *sav_path; + const char *real_path; + const char *new_argv_0; + union { + char *nc; // needed for free() + const char *c; + } sav_path; int sav_errno; - char *real_file_path; - char *orig_file_part, *new_file_part; int sav_death_sig; if (!script) @@ -744,8 +825,7 @@ check_script_secure(notify_script_t *script, } } - /* Remove /./, /../, multiple /'s, and resolve symbolic links */ - new_path = realpath(script->args[0], NULL); + real_path = realpath(script->args[0], NULL); sav_errno = errno; if (script->gid != our_gid || script->uid != our_uid) { @@ -761,66 +841,42 @@ check_script_secure(notify_script_t *script, kill(getpid(), SIGTERM); } - if (!new_path) - { + if (!real_path) { log_message(LOG_INFO, "Script %s cannot be accessed - %s", script->args[0], strerror(sav_errno)); return SC_NOTFOUND; } - /* It is much easier to ensure that new_path is part of + /* It is much easier to ensure that real_path is part of * keepalived's malloc handling. */ - sav_path = new_path; - new_path = STRDUP(new_path); - free(sav_path); /* malloc'd returned by realpath() */ - - real_file_path = NULL; - - orig_file_part = strrchr(script->args[0], '/'); - new_file_part = strrchr(new_path, '/'); - if (strcmp(script->args[0], new_path)) { - /* The path name is different */ - - /* If the file name parts don't match, we need to be careful to - * ensure that we preserve the file name part since some executables - * alter their behaviour based on what they are called */ - if (strcmp(orig_file_part + 1, new_file_part + 1)) { - real_file_path = new_path; - new_path = MALLOC(new_file_part - real_file_path + 1 + strlen(orig_file_part) - 1 + 1); - strncpy(new_path, real_file_path, new_file_part + 1 - real_file_path); - strcpy(new_path + (new_file_part + 1 - real_file_path), orig_file_part + 1); - - /* Now check this is the same file */ - ret_real = stat(real_file_path, &real_buf); - ret_new = stat(new_path, &file_buf); - if (!ret_real && - (ret_new || - real_buf.st_dev != file_buf.st_dev || - real_buf.st_ino != file_buf.st_ino)) { - /* It doesn't resolve to the same file */ - FREE(new_path); - new_path = real_file_path; - real_file_path = NULL; - } - } - - if (strcmp(script->args[0], new_path)) { - /* We need to set up all the args again */ - replace_cmd_name(script, new_path); - } - } - - FREE(new_path); + sav_path.c = real_path; + real_path = STRDUP(real_path); + free(sav_path.nc); /* malloc'd returned by realpath() */ /* Get the permissions for the file itself */ - if (stat(real_file_path ? real_file_path : script->args[0], &file_buf)) { + if (stat(real_path, &file_buf)) { log_message(LOG_INFO, "Unable to access script `%s` - disabling", script->args[0]); - FREE(real_file_path); + FREE_CONST(real_path); return SC_NOTFOUND; } + if (use_symlinks || strcmp(real_path, script->args[0])) { + /* Remove /./, /../, repeated /'s from argv[0] */ + new_argv_0 = clean_path(script->args[0]); + if (new_argv_0) { + /* The path name is different */ + replace_cmd_name(script, new_argv_0); + FREE_CONST_ONLY(new_argv_0); + } + } + + if (strcmp(real_path, script->args[0]) && !use_symlinks) { + script->path = real_path; + real_path = NULL; + } + flags = SC_ISSCRIPT; /* We have the final file. Check if root is executing it, or it is set uid/gid root. */ @@ -837,7 +893,7 @@ check_script_secure(notify_script_t *script, script->flags |= SC_EXECABLE; #ifdef _HAVE_LIBMAGIC_ if (magic && flags & SC_EXECUTABLE) { - const char *magic_desc = magic_file(magic, real_file_path ? real_file_path : script->args[0]); + const char *magic_desc = magic_file(magic, real_path ? real_path : script->path ? script->path : script->args[0]); if (!strstr(magic_desc, " executable") && !strstr(magic_desc, " shared object")) { log_message(LOG_INFO, "Please add a #! shebang to script %s", script->args[0]); @@ -846,19 +902,15 @@ check_script_secure(notify_script_t *script, } #endif - if (!need_script_protection) { - if (real_file_path) - FREE(real_file_path); + if (real_path) + FREE_CONST(real_path); - return flags; - } + if (need_script_protection) { + /* Make sure that all parts of the path(s) are not non-root writable */ + flags |= check_security(script->args[0], script_security); - /* Make sure that all parts of the path are not non-root writable */ - flags |= check_security(script->args[0], script_security); - - if (real_file_path) { - flags |= check_security(real_file_path, script_security); - FREE(real_file_path); + if (script->path) + flags |= check_security(script->path, script_security); } return flags; @@ -1120,6 +1172,13 @@ add_script_param(notify_script_t *script, const char *param) script->args[script->num_args++] = param; } +void +notify_free_script(notify_script_t *script) +{ + FREE_CONST_PTR(script->path); + FREE_CONST(script->args); +} + void notify_resource_release(void) { diff --git a/lib/notify.h b/lib/notify.h index b3761c879..19b409574 100644 --- a/lib/notify.h +++ b/lib/notify.h @@ -60,6 +60,7 @@ typedef enum { typedef struct _notify_script { const char **args; /* Script args - should be "char const * const *" */ int num_args; /* Used for notify script when adding last 4 parameters */ + const char *path; /* The path to the executable if different from args[0] */ int flags; uid_t uid; /* uid of user to execute script */ gid_t gid; /* gid of group to execute script */ @@ -89,6 +90,7 @@ free_notify_script(notify_script_t **script) extern bool script_security; /* prototypes */ +extern void set_symlinks(bool); extern const char *cmd_str_r(const notify_script_t *, char *, size_t); extern const char *cmd_str(const notify_script_t *); extern void notify_fifo_open(notify_fifo_t*, notify_fifo_t*, thread_func_t, const char *); @@ -106,6 +108,7 @@ extern bool set_script_uid_gid(const vector_t *, unsigned, uid_t *, gid_t *); extern void set_script_params_array(const vector_t *, notify_script_t *, unsigned); extern notify_script_t* notify_script_init(int, const char *); extern void add_script_param(notify_script_t *, const char *); +extern void notify_free_script(notify_script_t *); extern void notify_resource_release(void); extern bool notify_script_compare(const notify_script_t *, const notify_script_t *) __attribute__ ((pure)); extern void set_our_uid_gid(void);