|
|
00db14 |
From 99f31a89d2f5803fe2d6229f2557e72efb3ef95f Mon Sep 17 00:00:00 2001
|
|
|
00db14 |
From: Quentin Armitage <quentin@armitage.org.uk>
|
|
|
00db14 |
Date: Tue, 28 Mar 2017 09:11:44 +0100
|
|
|
00db14 |
Subject: [PATCH] Make dynamic flag bool
|
|
|
00db14 |
|
|
|
00db14 |
Signed-off-by: Quentin Armitage <quentin@armitage.org.uk>
|
|
|
00db14 |
|
|
|
00db14 |
Fix releasing malloc'd memory for saved core pattern
|
|
|
00db14 |
|
|
|
00db14 |
Signed-off-by: Quentin Armitage <quentin@armitage.org.uk>
|
|
|
00db14 |
|
|
|
00db14 |
Fix detecting default script uid/gid
|
|
|
00db14 |
|
|
|
00db14 |
Signed-off-by: Quentin Armitage <quentin@armitage.org.uk>
|
|
|
00db14 |
|
|
|
00db14 |
Don't complain about keepalived_script user if not needed
|
|
|
00db14 |
|
|
|
00db14 |
keepalived logged a warning every time if the keepalived_script user
|
|
|
00db14 |
didn't exist. We only need that warning if there is a script that uses
|
|
|
00db14 |
the default user, and an alternative defult user isn't specified.
|
|
|
00db14 |
|
|
|
00db14 |
Signed-off-by: Quentin Armitage <quentin@armitage.org.uk>
|
|
|
00db14 |
---
|
|
|
00db14 |
doc/keepalived.conf.SYNOPSIS | 30 +++++----
|
|
|
00db14 |
doc/man/man5/keepalived.conf.5 | 4 +-
|
|
|
00db14 |
keepalived/check/check_misc.c | 72 ++++++++++++++++-----
|
|
|
00db14 |
keepalived/check/check_parser.c | 9 +--
|
|
|
00db14 |
keepalived/core/global_parser.c | 49 +-------------
|
|
|
00db14 |
keepalived/core/main.c | 15 +----
|
|
|
00db14 |
keepalived/include/check_misc.h | 2 +-
|
|
|
00db14 |
keepalived/include/main.h | 2 -
|
|
|
00db14 |
keepalived/vrrp/vrrp_data.c | 3 +-
|
|
|
00db14 |
keepalived/vrrp/vrrp_parser.c | 46 ++++++++++---
|
|
|
00db14 |
keepalived/vrrp/vrrp_print.c | 1 +
|
|
|
00db14 |
lib/notify.c | 140 ++++++++++++++++++++++++++++++++++------
|
|
|
00db14 |
lib/notify.h | 7 +-
|
|
|
00db14 |
13 files changed, 242 insertions(+), 138 deletions(-)
|
|
|
00db14 |
|
|
|
00db14 |
diff --git a/doc/keepalived.conf.SYNOPSIS b/doc/keepalived.conf.SYNOPSIS
|
|
|
00db14 |
index 5b1dfb8..90eb83d 100644
|
|
|
00db14 |
--- a/doc/keepalived.conf.SYNOPSIS
|
|
|
00db14 |
+++ b/doc/keepalived.conf.SYNOPSIS
|
|
|
00db14 |
@@ -568,12 +568,14 @@ virtual_server group <STRING> { # VS group declaration
|
|
|
00db14 |
weight <INTEGER> # weight to use (default: 1)
|
|
|
00db14 |
inhibit_on_failure # Set weight to 0 on healthchecker
|
|
|
00db14 |
# failure
|
|
|
00db14 |
- notify_up <STRING>|<QUOTED-STRING> # Script to launch when
|
|
|
00db14 |
- # healthchecker consider service
|
|
|
00db14 |
- # as up.
|
|
|
00db14 |
- notify_down <STRING>|<QUOTED-STRING> # Script to launch when
|
|
|
00db14 |
- # healthchecker consider service
|
|
|
00db14 |
- # as down.
|
|
|
00db14 |
+ notify_up <STRING>|<QUOTED-STRING> [username [groupname]]
|
|
|
00db14 |
+ # Script to launch when
|
|
|
00db14 |
+ # healthchecker consider service
|
|
|
00db14 |
+ # as up.
|
|
|
00db14 |
+ notify_down <STRING>|<QUOTED-STRING> [username [groupname]]
|
|
|
00db14 |
+ # Script to launch when
|
|
|
00db14 |
+ # healthchecker consider service
|
|
|
00db14 |
+ # as down.
|
|
|
00db14 |
|
|
|
00db14 |
HTTP_GET|SSL_GET { # HTTP and SSL healthcheckers
|
|
|
00db14 |
url { # A set of url to test
|
|
|
00db14 |
@@ -603,8 +605,8 @@ virtual_server group <STRING> { # VS group declaration
|
|
|
00db14 |
real_server <IP ADDRESS> <PORT> { # Idem
|
|
|
00db14 |
weight <INTEGER> # Idem
|
|
|
00db14 |
inhibit_on_failure # Idem
|
|
|
00db14 |
- notify_up <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
- notify_down <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
+ notify_up <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
+ notify_down <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
|
|
|
00db14 |
TCP_CHECK { # TCP healthchecker
|
|
|
00db14 |
connect_ip <IP ADDRESS> # IP address to connect
|
|
|
00db14 |
@@ -620,8 +622,8 @@ virtual_server group <STRING> { # VS group declaration
|
|
|
00db14 |
real_server <IP ADDRESS> <PORT> { # Idem
|
|
|
00db14 |
weight <INTEGER> # Idem
|
|
|
00db14 |
inhibit_on_failure # Idem
|
|
|
00db14 |
- notify_up <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
- notify_down <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
+ notify_up <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
+ notify_down <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
|
|
|
00db14 |
SMTP_CHECK { # SMTP healthchecker
|
|
|
00db14 |
connect_ip <IP ADDRESS> # Optional IP address to connect to
|
|
|
00db14 |
@@ -658,8 +660,8 @@ virtual_server group <STRING> { # VS group declaration
|
|
|
00db14 |
real_server <IP ADDRESS> <PORT> { # Idem
|
|
|
00db14 |
weight <INTEGER> # Idem
|
|
|
00db14 |
inhibit_on_failure # Idem
|
|
|
00db14 |
- notify_up <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
- notify_down <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
+ notify_up <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
+ notify_down <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
|
|
|
00db14 |
DNS_CHECK { # DNS healthchecker
|
|
|
00db14 |
connect_ip <IP ADDRESS> # Optional IP address to connect to
|
|
|
00db14 |
@@ -677,8 +679,8 @@ virtual_server group <STRING> { # VS group declaration
|
|
|
00db14 |
real_server <IP ADDRESS> <PORT> { # Idem
|
|
|
00db14 |
weight <INTEGER> # Idem
|
|
|
00db14 |
inhibit_on_failure # Idem
|
|
|
00db14 |
- notify_up <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
- notify_down <STRING>|<QUOTED-STRING> # Idem
|
|
|
00db14 |
+ notify_up <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
+ notify_down <STRING>|<QUOTED-STRING> [username [groupname]] # Idem
|
|
|
00db14 |
|
|
|
00db14 |
MISC_CHECK { # MISC healthchecker
|
|
|
00db14 |
misc_path <STRING>|<QUOTED-STRING> # External system script or program
|
|
|
00db14 |
diff --git a/doc/man/man5/keepalived.conf.5 b/doc/man/man5/keepalived.conf.5
|
|
|
00db14 |
index 9ce2206..be33063 100644
|
|
|
00db14 |
--- a/doc/man/man5/keepalived.conf.5
|
|
|
00db14 |
+++ b/doc/man/man5/keepalived.conf.5
|
|
|
00db14 |
@@ -711,10 +711,10 @@ A virtual_server can be a declaration of one of
|
|
|
00db14 |
|
|
|
00db14 |
# Script to execute when healthchecker
|
|
|
00db14 |
# considers service as up.
|
|
|
00db14 |
- notify_up <STRING>|<QUOTED-STRING>
|
|
|
00db14 |
+ notify_up <STRING>|<QUOTED-STRING> [username [groupname]]
|
|
|
00db14 |
# Script to execute when healthchecker
|
|
|
00db14 |
# considers service as down.
|
|
|
00db14 |
- notify_down <STRING>|<QUOTED-STRING>
|
|
|
00db14 |
+ notify_down <STRING>|<QUOTED-STRING> [username [groupname]]
|
|
|
00db14 |
|
|
|
00db14 |
uthreshold <INTEGER> # maximum number of connections to server
|
|
|
00db14 |
lthreshold <INTEGER> # minimum number of connections to server
|
|
|
00db14 |
diff --git a/keepalived/check/check_misc.c b/keepalived/check/check_misc.c
|
|
|
00db14 |
index ccb9b63..a041d81 100644
|
|
|
00db14 |
--- a/keepalived/check/check_misc.c
|
|
|
00db14 |
+++ b/keepalived/check/check_misc.c
|
|
|
00db14 |
@@ -44,6 +44,11 @@ static int misc_check_thread(thread_t *);
|
|
|
00db14 |
static int misc_check_child_thread(thread_t *);
|
|
|
00db14 |
static int misc_check_child_timeout_thread(thread_t *);
|
|
|
00db14 |
|
|
|
00db14 |
+static bool script_user_set;
|
|
|
00db14 |
+static bool remove_script;
|
|
|
00db14 |
+static misc_checker_t *misck_checker;
|
|
|
00db14 |
+
|
|
|
00db14 |
+
|
|
|
00db14 |
/* Configuration stream handling */
|
|
|
00db14 |
static void
|
|
|
00db14 |
free_misc_check(void *data)
|
|
|
00db14 |
@@ -70,49 +75,83 @@ dump_misc_check(void *data)
|
|
|
00db14 |
static void
|
|
|
00db14 |
misc_check_handler(__attribute__((unused)) vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- misc_checker_t *misck_checker = (misc_checker_t *) MALLOC(sizeof (misc_checker_t));
|
|
|
00db14 |
-
|
|
|
00db14 |
- misck_checker->uid = default_script_uid;
|
|
|
00db14 |
- misck_checker->gid = default_script_gid;
|
|
|
00db14 |
+ misck_checker = (misc_checker_t *) MALLOC(sizeof (misc_checker_t));
|
|
|
00db14 |
|
|
|
00db14 |
- /* queue new checker */
|
|
|
00db14 |
- queue_checker(free_misc_check, dump_misc_check, misc_check_thread,
|
|
|
00db14 |
- misck_checker, NULL);
|
|
|
00db14 |
+ script_user_set = false;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
static void
|
|
|
00db14 |
misc_path_handler(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- misc_checker_t *misck_checker = CHECKER_GET();
|
|
|
00db14 |
+ if (!misck_checker)
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+
|
|
|
00db14 |
misck_checker->path = CHECKER_VALUE_STRING(strvec);
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
static void
|
|
|
00db14 |
misc_timeout_handler(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- misc_checker_t *misck_checker = CHECKER_GET();
|
|
|
00db14 |
+ if (!misck_checker)
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+
|
|
|
00db14 |
misck_checker->timeout = CHECKER_VALUE_UINT(strvec) * TIMER_HZ;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
static void
|
|
|
00db14 |
misc_dynamic_handler(__attribute__((unused)) vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- misc_checker_t *misck_checker = CHECKER_GET();
|
|
|
00db14 |
- misck_checker->dynamic = 1;
|
|
|
00db14 |
+ if (!misck_checker)
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ misck_checker->dynamic = true;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
static void
|
|
|
00db14 |
misc_user_handler(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- misc_checker_t *misck_checker = CHECKER_GET();
|
|
|
00db14 |
+ if (!misck_checker)
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
|
|
|
00db14 |
if (vector_size(strvec) < 2) {
|
|
|
00db14 |
log_message(LOG_INFO, "No user specified for misc checker script %s", misck_checker->path);
|
|
|
00db14 |
return;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
- if (set_script_uid_gid(strvec, 1, &misck_checker->uid, &misck_checker->gid))
|
|
|
00db14 |
- log_message(LOG_INFO, "Failed to set uid/gid for misc checker script %s", misck_checker->path);
|
|
|
00db14 |
+ if (set_script_uid_gid(strvec, 1, &misck_checker->uid, &misck_checker->gid)) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Failed to set uid/gid for misc checker script %s - removing", misck_checker->path);
|
|
|
00db14 |
+ FREE(misck_checker);
|
|
|
00db14 |
+ misck_checker = NULL;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ else
|
|
|
00db14 |
+ script_user_set = true;
|
|
|
00db14 |
+}
|
|
|
00db14 |
+
|
|
|
00db14 |
+static void
|
|
|
00db14 |
+misc_end_handler(void)
|
|
|
00db14 |
+{
|
|
|
00db14 |
+ if (!misck_checker)
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (!script_user_set)
|
|
|
00db14 |
+ {
|
|
|
00db14 |
+log_message(LOG_INFO, "remove_script 1 %d", remove_script);
|
|
|
00db14 |
+ if ( set_default_script_user(NULL, NULL, global_data->script_security)) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Unable to set default user for misc script %s - removing", misck_checker->path);
|
|
|
00db14 |
+ FREE(misck_checker);
|
|
|
00db14 |
+ misck_checker = NULL;
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+log_message(LOG_INFO, "Setting uid.gid");
|
|
|
00db14 |
+ misck_checker->uid = default_script_uid;
|
|
|
00db14 |
+ misck_checker->gid = default_script_gid;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ /* queue new checker */
|
|
|
00db14 |
+ queue_checker(free_misc_check, dump_misc_check, misc_check_thread, misck_checker, NULL);
|
|
|
00db14 |
+ misck_checker = NULL;
|
|
|
00db14 |
+log_message(LOG_INFO, "Leaving misc_end_handler");
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
void
|
|
|
00db14 |
@@ -125,6 +164,7 @@ install_misc_check_keyword(void)
|
|
|
00db14 |
install_keyword("misc_dynamic", &misc_dynamic_handler);
|
|
|
00db14 |
install_keyword("warmup", &warmup_handler);
|
|
|
00db14 |
install_keyword("user", &misc_user_handler);
|
|
|
00db14 |
+ install_sublevel_end_handler(&misc_end_handler);
|
|
|
00db14 |
install_sublevel_end();
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
@@ -251,13 +291,13 @@ misc_check_child_thread(thread_t * thread)
|
|
|
00db14 |
int status;
|
|
|
00db14 |
status = WEXITSTATUS(wait_status);
|
|
|
00db14 |
if (status == 0 ||
|
|
|
00db14 |
- (misck_checker->dynamic == 1 && status >= 2 && status <= 255)) {
|
|
|
00db14 |
+ (misck_checker->dynamic && status >= 2 && status <= 255)) {
|
|
|
00db14 |
/*
|
|
|
00db14 |
* The actual weight set when using misc_dynamic is two less than
|
|
|
00db14 |
* the exit status returned. Effective range is 0..253.
|
|
|
00db14 |
* Catch legacy case of status being 0 but misc_dynamic being set.
|
|
|
00db14 |
*/
|
|
|
00db14 |
- if (misck_checker->dynamic == 1 && status != 0)
|
|
|
00db14 |
+ if (misck_checker->dynamic && status != 0)
|
|
|
00db14 |
update_svr_wgt(status - 2, checker->vs,
|
|
|
00db14 |
checker->rs, true);
|
|
|
00db14 |
|
|
|
00db14 |
diff --git a/keepalived/check/check_parser.c b/keepalived/check/check_parser.c
|
|
|
00db14 |
index 2adac98..382861c 100644
|
|
|
00db14 |
--- a/keepalived/check/check_parser.c
|
|
|
00db14 |
+++ b/keepalived/check/check_parser.c
|
|
|
00db14 |
@@ -322,14 +322,7 @@ inhibit_handler(__attribute__((unused)) vector_t *strvec)
|
|
|
00db14 |
static inline notify_script_t*
|
|
|
00db14 |
set_check_notify_script(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- notify_script_t *script = notify_script_init(strvec, default_script_uid, default_script_gid);
|
|
|
00db14 |
-
|
|
|
00db14 |
- if (vector_size(strvec) > 2 ) {
|
|
|
00db14 |
- if (set_script_uid_gid(strvec, 2, &script->uid, &script->gid))
|
|
|
00db14 |
- log_message(LOG_INFO, "Invalid user/group for quorum/notify script %s", script->name);
|
|
|
00db14 |
- }
|
|
|
00db14 |
-
|
|
|
00db14 |
- return script;
|
|
|
00db14 |
+ return notify_script_init(strvec, "quorum/notify", global_data->script_security);
|
|
|
00db14 |
}
|
|
|
00db14 |
static void
|
|
|
00db14 |
notify_up_handler(vector_t *strvec)
|
|
|
00db14 |
diff --git a/keepalived/core/global_parser.c b/keepalived/core/global_parser.c
|
|
|
00db14 |
index 45d4cfb..a59fbc0 100644
|
|
|
00db14 |
--- a/keepalived/core/global_parser.c
|
|
|
00db14 |
+++ b/keepalived/core/global_parser.c
|
|
|
00db14 |
@@ -701,53 +701,6 @@ use_pid_dir_handler(__attribute__((unused)) vector_t *strvec)
|
|
|
00db14 |
use_pid_dir = true;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
-bool
|
|
|
00db14 |
-set_script_uid_gid(vector_t *strvec, unsigned keyword_offset, uid_t *uid_p, gid_t *gid_p)
|
|
|
00db14 |
-{
|
|
|
00db14 |
- char *username;
|
|
|
00db14 |
- char *groupname;
|
|
|
00db14 |
- uid_t uid;
|
|
|
00db14 |
- gid_t gid;
|
|
|
00db14 |
- struct passwd pwd;
|
|
|
00db14 |
- struct passwd *pwd_p;
|
|
|
00db14 |
- struct group grp;
|
|
|
00db14 |
- struct group *grp_p;
|
|
|
00db14 |
- int ret;
|
|
|
00db14 |
- char buf[getpwnam_buf_len];
|
|
|
00db14 |
-
|
|
|
00db14 |
- username = strvec_slot(strvec, keyword_offset);
|
|
|
00db14 |
-
|
|
|
00db14 |
- if ((ret = getpwnam_r(username, &pwd, buf, sizeof(buf), &pwd_p))) {
|
|
|
00db14 |
- log_message(LOG_INFO, "Unable to resolve script username '%s' - ignoring", username);
|
|
|
00db14 |
- return true;
|
|
|
00db14 |
- }
|
|
|
00db14 |
- if (!pwd_p) {
|
|
|
00db14 |
- log_message(LOG_INFO, "Script user '%s' does not exist", username);
|
|
|
00db14 |
- return true;
|
|
|
00db14 |
- }
|
|
|
00db14 |
-
|
|
|
00db14 |
- uid = pwd.pw_uid;
|
|
|
00db14 |
- gid = pwd.pw_gid;
|
|
|
00db14 |
-
|
|
|
00db14 |
- if (vector_size(strvec) > keyword_offset + 1) {
|
|
|
00db14 |
- groupname = strvec_slot(strvec, keyword_offset + 1);
|
|
|
00db14 |
- if ((ret = getgrnam_r(groupname, &grp, buf, sizeof(buf), &grp_p))) {
|
|
|
00db14 |
- log_message(LOG_INFO, "Unable to resolve script group name '%s' - ignoring", groupname);
|
|
|
00db14 |
- return true;
|
|
|
00db14 |
- }
|
|
|
00db14 |
- if (!grp_p) {
|
|
|
00db14 |
- log_message(LOG_INFO, "Script group '%s' does not exist", groupname);
|
|
|
00db14 |
- return true;
|
|
|
00db14 |
- }
|
|
|
00db14 |
- gid = grp.gr_gid;
|
|
|
00db14 |
- }
|
|
|
00db14 |
-
|
|
|
00db14 |
- *uid_p = uid;
|
|
|
00db14 |
- *gid_p = gid;
|
|
|
00db14 |
-
|
|
|
00db14 |
- return false;
|
|
|
00db14 |
-}
|
|
|
00db14 |
-
|
|
|
00db14 |
static void
|
|
|
00db14 |
script_user_handler(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
@@ -756,7 +709,7 @@ script_user_handler(vector_t *strvec)
|
|
|
00db14 |
return;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
- if (set_script_uid_gid(strvec, 1, &default_script_uid, &default_script_gid))
|
|
|
00db14 |
+ if (set_default_script_user(strvec_slot(strvec, 1), vector_size(strvec) > 2 ? strvec_slot(strvec, 2) : NULL, true))
|
|
|
00db14 |
log_message(LOG_INFO, "Error setting global script uid/gid");
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
diff --git a/keepalived/core/main.c b/keepalived/core/main.c
|
|
|
00db14 |
index 55eb263..95bb76a 100644
|
|
|
00db14 |
--- a/keepalived/core/main.c
|
|
|
00db14 |
+++ b/keepalived/core/main.c
|
|
|
00db14 |
@@ -119,6 +119,9 @@ free_parent_mallocs_startup(bool am_child)
|
|
|
00db14 |
#else
|
|
|
00db14 |
free(syslog_ident);
|
|
|
00db14 |
#endif
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (orig_core_dump_pattern)
|
|
|
00db14 |
+ FREE_PTR(orig_core_dump_pattern);
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
if (free_main_pidfile) {
|
|
|
00db14 |
@@ -765,7 +768,6 @@ keepalived_main(int argc, char **argv)
|
|
|
00db14 |
bool report_stopped = true;
|
|
|
00db14 |
struct utsname uname_buf;
|
|
|
00db14 |
char *end;
|
|
|
00db14 |
- long buf_len;
|
|
|
00db14 |
|
|
|
00db14 |
/* Init debugging level */
|
|
|
00db14 |
debug = 0;
|
|
|
00db14 |
@@ -814,17 +816,6 @@ keepalived_main(int argc, char **argv)
|
|
|
00db14 |
|
|
|
00db14 |
netlink_set_recv_buf_size();
|
|
|
00db14 |
|
|
|
00db14 |
- set_default_script_user(&default_script_uid, &default_script_gid);
|
|
|
00db14 |
-
|
|
|
00db14 |
- /* Get buffer length needed for getpwnam_r/getgrnam_r */
|
|
|
00db14 |
- if ((buf_len = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1)
|
|
|
00db14 |
- getpwnam_buf_len = 1024; /* A safe default if no value is returned */
|
|
|
00db14 |
- else
|
|
|
00db14 |
- getpwnam_buf_len = (size_t)buf_len;
|
|
|
00db14 |
- if ((buf_len = sysconf(_SC_GETGR_R_SIZE_MAX)) != -1 &&
|
|
|
00db14 |
- (size_t)buf_len > getpwnam_buf_len)
|
|
|
00db14 |
- getpwnam_buf_len = (size_t)buf_len;
|
|
|
00db14 |
-
|
|
|
00db14 |
/* Some functionality depends on kernel version, so get the version here */
|
|
|
00db14 |
if (uname(&uname_buf))
|
|
|
00db14 |
log_message(LOG_INFO, "Unable to get uname() information - error %d", errno);
|
|
|
00db14 |
diff --git a/keepalived/include/check_misc.h b/keepalived/include/check_misc.h
|
|
|
00db14 |
index ed0d962..ec97149 100644
|
|
|
00db14 |
--- a/keepalived/include/check_misc.h
|
|
|
00db14 |
+++ b/keepalived/include/check_misc.h
|
|
|
00db14 |
@@ -36,7 +36,7 @@
|
|
|
00db14 |
typedef struct _misc_checker {
|
|
|
00db14 |
char *path;
|
|
|
00db14 |
unsigned long timeout;
|
|
|
00db14 |
- int dynamic; /* 0: old-style, 1: exit code from checker affects weight */
|
|
|
00db14 |
+ bool dynamic; /* false: old-style, true: exit code from checker affects weight */
|
|
|
00db14 |
bool forcing_termination; /* Set if we have sent the process a SIGTERM */
|
|
|
00db14 |
uid_t uid; /* uid for script execution */
|
|
|
00db14 |
gid_t gid; /* gid for script execution */
|
|
|
00db14 |
diff --git a/keepalived/include/main.h b/keepalived/include/main.h
|
|
|
00db14 |
index d125566..eebde77 100644
|
|
|
00db14 |
--- a/keepalived/include/main.h
|
|
|
00db14 |
+++ b/keepalived/include/main.h
|
|
|
00db14 |
@@ -73,8 +73,6 @@ extern bool namespace_with_ipsets; /* override for namespaces with ipsets on Lin
|
|
|
00db14 |
#endif
|
|
|
00db14 |
extern char *instance_name; /* keepalived instance name */
|
|
|
00db14 |
extern bool use_pid_dir; /* pid files in /var/run/keepalived */
|
|
|
00db14 |
-extern uid_t default_script_uid; /* Default user/group for script execution */
|
|
|
00db14 |
-extern gid_t default_script_gid;
|
|
|
00db14 |
extern unsigned os_major; /* Kernel version */
|
|
|
00db14 |
extern unsigned os_minor;
|
|
|
00db14 |
extern unsigned os_release;
|
|
|
00db14 |
diff --git a/keepalived/vrrp/vrrp_data.c b/keepalived/vrrp/vrrp_data.c
|
|
|
00db14 |
index 76f17a4..b5c59df 100644
|
|
|
00db14 |
--- a/keepalived/vrrp/vrrp_data.c
|
|
|
00db14 |
+++ b/keepalived/vrrp/vrrp_data.c
|
|
|
00db14 |
@@ -160,8 +160,7 @@ dump_vscript(void *data)
|
|
|
00db14 |
str = (vscript->result >= vscript->rise) ? "GOOD" : "BAD";
|
|
|
00db14 |
}
|
|
|
00db14 |
log_message(LOG_INFO, " Status = %s", str);
|
|
|
00db14 |
- if (vscript->uid || vscript->gid)
|
|
|
00db14 |
- log_message(LOG_INFO, " Script uid:gid = %d:%d", vscript->uid, vscript->gid);
|
|
|
00db14 |
+ log_message(LOG_INFO, " Script uid:gid = %d:%d", vscript->uid, vscript->gid);
|
|
|
00db14 |
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
diff --git a/keepalived/vrrp/vrrp_parser.c b/keepalived/vrrp/vrrp_parser.c
|
|
|
00db14 |
index 7a38315..c774dec 100644
|
|
|
00db14 |
--- a/keepalived/vrrp/vrrp_parser.c
|
|
|
00db14 |
+++ b/keepalived/vrrp/vrrp_parser.c
|
|
|
00db14 |
@@ -48,6 +48,9 @@
|
|
|
00db14 |
#include "bitops.h"
|
|
|
00db14 |
#include "notify.h"
|
|
|
00db14 |
|
|
|
00db14 |
+static bool script_user_set;
|
|
|
00db14 |
+static bool remove_script;
|
|
|
00db14 |
+
|
|
|
00db14 |
/* Static addresses handler */
|
|
|
00db14 |
static void
|
|
|
00db14 |
static_addresses_handler(__attribute__((unused)) vector_t *strvec)
|
|
|
00db14 |
@@ -120,14 +123,7 @@ vrrp_group_handler(vector_t *strvec)
|
|
|
00db14 |
static inline notify_script_t*
|
|
|
00db14 |
set_vrrp_notify_script(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
- notify_script_t *script = notify_script_init(strvec, default_script_uid, default_script_gid);
|
|
|
00db14 |
-
|
|
|
00db14 |
- if (vector_size(strvec) > 2) {
|
|
|
00db14 |
- if (set_script_uid_gid(strvec, 2, &script->uid, &script->gid))
|
|
|
00db14 |
- log_message(LOG_INFO, "Invalid user/group for notify script %s", script->name);
|
|
|
00db14 |
- }
|
|
|
00db14 |
-
|
|
|
00db14 |
- return script;
|
|
|
00db14 |
+ return notify_script_init(strvec, "notify", global_data->script_security);
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
static void
|
|
|
00db14 |
@@ -680,6 +676,8 @@ static void
|
|
|
00db14 |
vrrp_script_handler(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
alloc_vrrp_script(strvec_slot(strvec, 1));
|
|
|
00db14 |
+ script_user_set = false;
|
|
|
00db14 |
+ remove_script = false;
|
|
|
00db14 |
}
|
|
|
00db14 |
static void
|
|
|
00db14 |
vrrp_vscript_script_handler(vector_t *strvec)
|
|
|
00db14 |
@@ -731,8 +729,35 @@ static void
|
|
|
00db14 |
vrrp_vscript_user_handler(vector_t *strvec)
|
|
|
00db14 |
{
|
|
|
00db14 |
vrrp_script_t *vscript = LIST_TAIL_DATA(vrrp_data->vrrp_script);
|
|
|
00db14 |
- if (set_script_uid_gid(strvec, 1, &vscript->uid, &vscript->gid))
|
|
|
00db14 |
- log_message(LOG_INFO, "Unable to set uid/gid for script %s", vscript->script);
|
|
|
00db14 |
+ if (set_script_uid_gid(strvec, 1, &vscript->uid, &vscript->gid)) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Unable to set uid/gid for script %s - disabling", vscript->script);
|
|
|
00db14 |
+ remove_script = true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ else
|
|
|
00db14 |
+ script_user_set = true;
|
|
|
00db14 |
+}
|
|
|
00db14 |
+static void
|
|
|
00db14 |
+vrrp_vscript_end_handler(void)
|
|
|
00db14 |
+{
|
|
|
00db14 |
+ vrrp_script_t *vscript = LIST_TAIL_DATA(vrrp_data->vrrp_script);
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (script_user_set)
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (!remove_script &&
|
|
|
00db14 |
+ set_default_script_user(NULL, NULL, global_data->script_security)) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Unable to set default user for track script %s - removing", vscript->script);
|
|
|
00db14 |
+ remove_script = true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (remove_script) {
|
|
|
00db14 |
+ free_list_element(vrrp_data->vrrp_script, vrrp_data->vrrp_script->tail);
|
|
|
00db14 |
+ return;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ vscript->uid = default_script_uid;
|
|
|
00db14 |
+ vscript->gid = default_script_gid;
|
|
|
00db14 |
+
|
|
|
00db14 |
}
|
|
|
00db14 |
static void
|
|
|
00db14 |
vrrp_vscript_init_fail_handler(__attribute__((unused)) vector_t *strvec)
|
|
|
00db14 |
@@ -964,6 +989,7 @@ init_vrrp_keywords(bool active)
|
|
|
00db14 |
install_keyword("fall", &vrrp_vscript_fall_handler);
|
|
|
00db14 |
install_keyword("user", &vrrp_vscript_user_handler);
|
|
|
00db14 |
install_keyword("init_fail", &vrrp_vscript_init_fail_handler);
|
|
|
00db14 |
+ install_sublevel_end_handler(&vrrp_vscript_end_handler);
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
vector_t *
|
|
|
00db14 |
diff --git a/keepalived/vrrp/vrrp_print.c b/keepalived/vrrp/vrrp_print.c
|
|
|
00db14 |
index 7adb701..54da044 100644
|
|
|
00db14 |
--- a/keepalived/vrrp/vrrp_print.c
|
|
|
00db14 |
+++ b/keepalived/vrrp/vrrp_print.c
|
|
|
00db14 |
@@ -92,6 +92,7 @@ vscript_print(FILE *file, void *data)
|
|
|
00db14 |
fprintf(file, " Rise = %d\n", vscript->rise);
|
|
|
00db14 |
fprintf(file, " Full = %d\n", vscript->fall);
|
|
|
00db14 |
fprintf(file, " Insecure = %s\n", vscript->insecure ? "yes" : "no");
|
|
|
00db14 |
+ fprintf(file, " uid:gid = %d:%d\n", vscript->uid, vscript->gid);
|
|
|
00db14 |
|
|
|
00db14 |
switch (vscript->result) {
|
|
|
00db14 |
case VRRP_SCRIPT_STATUS_INIT:
|
|
|
00db14 |
diff --git a/lib/notify.c b/lib/notify.c
|
|
|
00db14 |
index d92c50d..a8742fe 100644
|
|
|
00db14 |
--- a/lib/notify.c
|
|
|
00db14 |
+++ b/lib/notify.c
|
|
|
00db14 |
@@ -44,10 +44,15 @@
|
|
|
00db14 |
#include "vector.h"
|
|
|
00db14 |
#include "parser.h"
|
|
|
00db14 |
|
|
|
00db14 |
-size_t getpwnam_buf_len; /* Buffer length needed for getpwnam_r/getgrname_r */
|
|
|
00db14 |
|
|
|
00db14 |
+uid_t default_script_uid; /* Default user/group for script execution */
|
|
|
00db14 |
+gid_t default_script_gid;
|
|
|
00db14 |
+static bool default_script_uid_set = false;
|
|
|
00db14 |
+static bool default_user_fail = false; /* Set if failed to set default user,
|
|
|
00db14 |
+ unless it defaults to root */
|
|
|
00db14 |
static char *path;
|
|
|
00db14 |
static bool path_is_malloced;
|
|
|
00db14 |
+static size_t getpwnam_buf_len; /* Buffer length needed for getpwnam_r/getgrname_r */
|
|
|
00db14 |
|
|
|
00db14 |
/* perform a system call */
|
|
|
00db14 |
static int
|
|
|
00db14 |
@@ -565,40 +570,135 @@ check_notify_script_secure(notify_script_t **script_p, bool script_security, boo
|
|
|
00db14 |
return flags;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
-/* The default script user/group is keepalived_script if it exists, or root otherwise */
|
|
|
00db14 |
-void
|
|
|
00db14 |
-set_default_script_user(uid_t *uid, gid_t *gid)
|
|
|
00db14 |
+static void
|
|
|
00db14 |
+set_pwnam_buf_len(void)
|
|
|
00db14 |
+{
|
|
|
00db14 |
+ long buf_len;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ /* Get buffer length needed for getpwnam_r/getgrnam_r */
|
|
|
00db14 |
+ if ((buf_len = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1)
|
|
|
00db14 |
+ getpwnam_buf_len = 1024; /* A safe default if no value is returned */
|
|
|
00db14 |
+ else
|
|
|
00db14 |
+ getpwnam_buf_len = (size_t)buf_len;
|
|
|
00db14 |
+ if ((buf_len = sysconf(_SC_GETGR_R_SIZE_MAX)) != -1 &&
|
|
|
00db14 |
+ (size_t)buf_len > getpwnam_buf_len)
|
|
|
00db14 |
+ getpwnam_buf_len = (size_t)buf_len;
|
|
|
00db14 |
+}
|
|
|
00db14 |
+
|
|
|
00db14 |
+bool
|
|
|
00db14 |
+set_uid_gid(const char *username, const char *groupname, uid_t *uid_p, gid_t *gid_p, bool default_user)
|
|
|
00db14 |
{
|
|
|
00db14 |
- char buf[getpwnam_buf_len];
|
|
|
00db14 |
- char *default_user_name = "keepalived_script";
|
|
|
00db14 |
+ uid_t uid;
|
|
|
00db14 |
+ gid_t gid;
|
|
|
00db14 |
struct passwd pwd;
|
|
|
00db14 |
struct passwd *pwd_p;
|
|
|
00db14 |
+ struct group grp;
|
|
|
00db14 |
+ struct group *grp_p;
|
|
|
00db14 |
+ int ret;
|
|
|
00db14 |
+ bool using_default_default_user = false;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (!getpwnam_buf_len)
|
|
|
00db14 |
+ set_pwnam_buf_len();
|
|
|
00db14 |
|
|
|
00db14 |
- if (getpwnam_r(default_user_name, &pwd, buf, sizeof(buf), &pwd_p)) {
|
|
|
00db14 |
- log_message(LOG_INFO, "Unable to resolve default script username '%s' - ignoring", default_user_name);
|
|
|
00db14 |
- return;
|
|
|
00db14 |
+ {
|
|
|
00db14 |
+ char buf[getpwnam_buf_len];
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (default_user && !username) {
|
|
|
00db14 |
+ using_default_default_user = true;
|
|
|
00db14 |
+ username = "keepalived_script";
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if ((ret = getpwnam_r(username, &pwd, buf, sizeof(buf), &pwd_p))) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Unable to resolve %sscript username '%s' - ignoring", default_user ? "default " : "", username);
|
|
|
00db14 |
+ return true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ if (!pwd_p) {
|
|
|
00db14 |
+ if (using_default_default_user)
|
|
|
00db14 |
+ log_message(LOG_INFO, "WARNING - default user '%s' for script execution does not exist - please create.", username);
|
|
|
00db14 |
+ else
|
|
|
00db14 |
+ log_message(LOG_INFO, "%script user '%s' does not exist", default_user ? "Default s" : "S", username);
|
|
|
00db14 |
+ return true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ uid = pwd.pw_uid;
|
|
|
00db14 |
+ gid = pwd.pw_gid;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (groupname) {
|
|
|
00db14 |
+ if ((ret = getgrnam_r(groupname, &grp, buf, sizeof(buf), &grp_p))) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Unable to resolve %sscript group name '%s' - ignoring", default_user ? "default " : "", groupname);
|
|
|
00db14 |
+ return true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ if (!grp_p) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "%script group '%s' does not exist", default_user ? "Default s" : "S", groupname);
|
|
|
00db14 |
+ return true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ gid = grp.gr_gid;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ *uid_p = uid;
|
|
|
00db14 |
+ *gid_p = gid;
|
|
|
00db14 |
}
|
|
|
00db14 |
- if (!pwd_p) {
|
|
|
00db14 |
- /* The username does not exist */
|
|
|
00db14 |
- log_message(LOG_INFO, "WARNING - default user '%s' for script execution does not exist - please create.", default_user_name);
|
|
|
00db14 |
- return;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ return false;
|
|
|
00db14 |
+}
|
|
|
00db14 |
+
|
|
|
00db14 |
+bool
|
|
|
00db14 |
+set_default_script_user(const char *username, const char *groupname, bool script_security)
|
|
|
00db14 |
+{
|
|
|
00db14 |
+ if (!default_script_uid_set || username) {
|
|
|
00db14 |
+ /* Even if we fail to set it, there is no point in trying again */
|
|
|
00db14 |
+ default_script_uid_set = true;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (set_uid_gid(username, groupname, &default_script_uid, &default_script_gid, true)) {
|
|
|
00db14 |
+ if (username || script_security)
|
|
|
00db14 |
+ default_user_fail = true;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ else
|
|
|
00db14 |
+ default_user_fail = false;
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
- *uid = pwd.pw_uid;
|
|
|
00db14 |
- *gid = pwd.pw_gid;
|
|
|
00db14 |
+ return default_user_fail;
|
|
|
00db14 |
+}
|
|
|
00db14 |
+
|
|
|
00db14 |
+bool
|
|
|
00db14 |
+set_script_uid_gid(vector_t *strvec, unsigned keyword_offset, uid_t *uid_p, gid_t *gid_p)
|
|
|
00db14 |
+{
|
|
|
00db14 |
+ char *username;
|
|
|
00db14 |
+ char *groupname;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ username = strvec_slot(strvec, keyword_offset);
|
|
|
00db14 |
+ if (vector_size(strvec) > keyword_offset + 1)
|
|
|
00db14 |
+ groupname = strvec_slot(strvec, keyword_offset + 1);
|
|
|
00db14 |
+ else
|
|
|
00db14 |
+ groupname = NULL;
|
|
|
00db14 |
|
|
|
00db14 |
- log_message(LOG_INFO, "Setting default script user to '%s', uid:gid %d:%d", default_user_name, pwd.pw_uid, pwd.pw_gid);
|
|
|
00db14 |
+ return set_uid_gid(username, groupname, uid_p, gid_p, false);
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
notify_script_t*
|
|
|
00db14 |
-notify_script_init(vector_t *strvec, uid_t uid, gid_t gid)
|
|
|
00db14 |
+notify_script_init(vector_t *strvec, const char *type, bool script_security)
|
|
|
00db14 |
{
|
|
|
00db14 |
notify_script_t *script = MALLOC(sizeof(notify_script_t));
|
|
|
00db14 |
|
|
|
00db14 |
script->name = set_value(strvec);
|
|
|
00db14 |
- script->uid = uid;
|
|
|
00db14 |
- script->gid = gid;
|
|
|
00db14 |
+
|
|
|
00db14 |
+ if (vector_size(strvec) > 2) {
|
|
|
00db14 |
+ if (set_script_uid_gid(strvec, 2, &script->uid, &script->gid)) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Invalid user/group for %s script %s - ignoring", type, script->name);
|
|
|
00db14 |
+ FREE(script);
|
|
|
00db14 |
+ return NULL;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+ else {
|
|
|
00db14 |
+ if (set_default_script_user(NULL, NULL, script_security)) {
|
|
|
00db14 |
+ log_message(LOG_INFO, "Failed to set default user for %s script %s - ignoring", type, script->name);
|
|
|
00db14 |
+ FREE(script);
|
|
|
00db14 |
+ return NULL;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
+
|
|
|
00db14 |
+ script->uid = default_script_uid;
|
|
|
00db14 |
+ script->gid = default_script_gid;
|
|
|
00db14 |
+ }
|
|
|
00db14 |
|
|
|
00db14 |
return script;
|
|
|
00db14 |
}
|
|
|
00db14 |
-
|
|
|
00db14 |
diff --git a/lib/notify.h b/lib/notify.h
|
|
|
00db14 |
index ac07edb..3d092ea 100644
|
|
|
00db14 |
--- a/lib/notify.h
|
|
|
00db14 |
+++ b/lib/notify.h
|
|
|
00db14 |
@@ -56,7 +56,8 @@ free_notify_script(notify_script_t **script)
|
|
|
00db14 |
}
|
|
|
00db14 |
|
|
|
00db14 |
/* Global variables */
|
|
|
00db14 |
-extern size_t getpwnam_buf_len; /* Buffer length needed for getpwnam_r/getgrnam_r */
|
|
|
00db14 |
+extern uid_t default_script_uid; /* Default user/group for script execution */
|
|
|
00db14 |
+extern gid_t default_script_gid;
|
|
|
00db14 |
|
|
|
00db14 |
/* prototypes */
|
|
|
00db14 |
extern int system_call_script(thread_master_t *, int (*) (thread_t *), void *, unsigned long, const char*, uid_t, gid_t);
|
|
|
00db14 |
@@ -64,7 +65,7 @@ extern int notify_exec(const notify_script_t *);
|
|
|
00db14 |
extern void script_killall(thread_master_t *, int);
|
|
|
00db14 |
extern int check_script_secure(notify_script_t *, bool, bool);
|
|
|
00db14 |
extern int check_notify_script_secure(notify_script_t **, bool, bool);
|
|
|
00db14 |
-extern void set_default_script_user(uid_t *, gid_t *);
|
|
|
00db14 |
-extern notify_script_t* notify_script_init(vector_t *, uid_t, gid_t);
|
|
|
00db14 |
+extern bool set_default_script_user(const char *, const char *, bool);
|
|
|
00db14 |
+extern notify_script_t* notify_script_init(vector_t *, const char *, bool);
|
|
|
00db14 |
|
|
|
00db14 |
#endif
|
|
|
00db14 |
--
|
|
|
00db14 |
2.9.4
|
|
|
00db14 |
|