|
|
|
|
@@ -1235,12 +1235,6 @@ static int prompt_relay_configuration(didactyl_config_t* cfg) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ensure_dir_exists_local(const char* path) {
|
|
|
|
|
if (!path || path[0] == '\0') return -1;
|
|
|
|
|
if (mkdir(path, 0755) == 0 || errno == EEXIST) return 0;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int run_command_local(char* const argv[]) {
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
if (pid < 0) return -1;
|
|
|
|
|
@@ -1255,31 +1249,64 @@ static int run_command_local(char* const argv[]) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int copy_file_local(const char* src, const char* dst) {
|
|
|
|
|
if (!src || !dst || src[0] == '\0' || dst[0] == '\0') return -1;
|
|
|
|
|
static int run_privileged_command_local(char* const argv[]) {
|
|
|
|
|
if (!argv || !argv[0]) return -1;
|
|
|
|
|
|
|
|
|
|
FILE* in = fopen(src, "rb");
|
|
|
|
|
if (!in) return -1;
|
|
|
|
|
if (geteuid() == 0) {
|
|
|
|
|
return run_command_local(argv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FILE* out = fopen(dst, "wb");
|
|
|
|
|
if (!out) {
|
|
|
|
|
fclose(in);
|
|
|
|
|
char* sudo_argv[64] = {0};
|
|
|
|
|
sudo_argv[0] = "sudo";
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
for (; argv[i] && i < 62; i++) {
|
|
|
|
|
sudo_argv[i + 1] = argv[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argv[i] != NULL) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char buf[16384];
|
|
|
|
|
size_t n = 0;
|
|
|
|
|
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
|
|
|
|
if (fwrite(buf, 1, n, out) != n) {
|
|
|
|
|
fclose(in);
|
|
|
|
|
fclose(out);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
sudo_argv[i + 1] = NULL;
|
|
|
|
|
return run_command_local(sudo_argv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int write_file_privileged_local(const char* path, const char* content, unsigned int mode) {
|
|
|
|
|
if (!path || path[0] == '\0' || !content) return -1;
|
|
|
|
|
|
|
|
|
|
char tmp_template[] = "/tmp/didactyl-wizard-XXXXXX";
|
|
|
|
|
int fd = mkstemp(tmp_template);
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fclose(in);
|
|
|
|
|
fclose(out);
|
|
|
|
|
return 0;
|
|
|
|
|
FILE* fp = fdopen(fd, "w");
|
|
|
|
|
if (!fp) {
|
|
|
|
|
close(fd);
|
|
|
|
|
unlink(tmp_template);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t len = strlen(content);
|
|
|
|
|
int ok = (fwrite(content, 1, len, fp) == len) ? 1 : 0;
|
|
|
|
|
if (fclose(fp) != 0) {
|
|
|
|
|
ok = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
unlink(tmp_template);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char mode_buf[16] = {0};
|
|
|
|
|
snprintf(mode_buf, sizeof(mode_buf), "%04o", mode & 07777U);
|
|
|
|
|
|
|
|
|
|
char* install_argv[] = {"install", "-m", mode_buf, tmp_template, (char*)path, NULL};
|
|
|
|
|
int rc = run_privileged_command_local(install_argv);
|
|
|
|
|
|
|
|
|
|
unlink(tmp_template);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sanitize_service_user_from_name(const char* name, char* out, size_t out_size) {
|
|
|
|
|
@@ -1332,37 +1359,44 @@ static int write_sudoers_file(const char* service_user) {
|
|
|
|
|
char sudoers_path[PATH_MAX] = {0};
|
|
|
|
|
snprintf(sudoers_path, sizeof(sudoers_path), "/etc/sudoers.d/%s", service_user);
|
|
|
|
|
|
|
|
|
|
FILE* fp = fopen(sudoers_path, "w");
|
|
|
|
|
if (!fp) {
|
|
|
|
|
fprintf(stderr, "%sFailed to write sudoers file at %s.%s\n", ANSI_RED, sudoers_path, ANSI_RESET);
|
|
|
|
|
char content[4096] = {0};
|
|
|
|
|
int n = snprintf(content,
|
|
|
|
|
sizeof(content),
|
|
|
|
|
"# Didactyl agent — scoped system maintenance privileges\n"
|
|
|
|
|
"# Generated by didactyl setup wizard\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"# Service management\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl start *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-enabled *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl list-units *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"# Log inspection\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/journalctl *\n",
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user);
|
|
|
|
|
if (n < 0 || (size_t)n >= sizeof(content)) {
|
|
|
|
|
fprintf(stderr, "%sFailed to build sudoers content for %s.%s\n", ANSI_RED, service_user, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(fp,
|
|
|
|
|
"# Didactyl agent — scoped system maintenance privileges\n"
|
|
|
|
|
"# Generated by didactyl setup wizard\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"# Service management\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl start *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-enabled *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl list-units *\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"# Log inspection\n"
|
|
|
|
|
"%s ALL=(ALL) NOPASSWD: /usr/bin/journalctl *\n",
|
|
|
|
|
service_user, service_user, service_user, service_user,
|
|
|
|
|
service_user, service_user, service_user, service_user,
|
|
|
|
|
service_user, service_user, service_user);
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
|
|
if (chmod(sudoers_path, 0440) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to chmod sudoers file at %s.%s\n", ANSI_RED, sudoers_path, ANSI_RESET);
|
|
|
|
|
if (write_file_privileged_local(sudoers_path, content, 0440U) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to write sudoers file at %s.%s\n", ANSI_RED, sudoers_path, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1373,8 +1407,12 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
|
|
|
|
|
if (!cfg || cfg->keys.nsec[0] == '\0') return -1;
|
|
|
|
|
|
|
|
|
|
if (geteuid() != 0) {
|
|
|
|
|
fprintf(stderr, "%sSystem install requires root (run with sudo).%s\n", ANSI_RED, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
fprintf(stderr, " Requesting sudo authentication for system install...\n");
|
|
|
|
|
char* sudo_validate_argv[] = {"sudo", "-v", NULL};
|
|
|
|
|
if (run_command_local(sudo_validate_argv) != 0) {
|
|
|
|
|
fprintf(stderr, "%sSudo authentication failed. System install canceled.%s\n", ANSI_RED, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- Derive service username from agent name --- */
|
|
|
|
|
@@ -1388,7 +1426,7 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
|
|
|
|
|
char comment[128] = {0};
|
|
|
|
|
snprintf(comment, sizeof(comment), "Didactyl Nostr Agent (%s)", agent_name);
|
|
|
|
|
char* useradd_argv[] = {"useradd", "-m", "-s", "/bin/bash", "-c", comment, service_user, NULL};
|
|
|
|
|
if (run_command_local(useradd_argv) != 0) {
|
|
|
|
|
if (run_privileged_command_local(useradd_argv) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to create system user '%s'.%s\n", ANSI_RED, service_user, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
@@ -1407,11 +1445,6 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ensure_dir_exists_local(home) != 0) {
|
|
|
|
|
fprintf(stderr, "%sUnable to create/access home directory: %s%s\n", ANSI_RED, home, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- Copy binary --- */
|
|
|
|
|
char self_path[PATH_MAX] = {0};
|
|
|
|
|
ssize_t self_len = readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);
|
|
|
|
|
@@ -1424,19 +1457,23 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
|
|
|
|
|
char bin_path[PATH_MAX] = {0};
|
|
|
|
|
snprintf(bin_path, sizeof(bin_path), "%s/didactyl", home);
|
|
|
|
|
fprintf(stderr, " Copying binary to %s...\n", bin_path);
|
|
|
|
|
if (copy_file_local(self_path, bin_path) != 0) {
|
|
|
|
|
char* install_bin_argv[] = {"install", "-m", "0755", self_path, bin_path, NULL};
|
|
|
|
|
if (run_privileged_command_local(install_bin_argv) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to copy binary to %s.%s\n", ANSI_RED, bin_path, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (chmod(bin_path, 0755) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to chmod binary at %s.%s\n", ANSI_RED, bin_path, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- Set ownership on home directory and installed binary --- */
|
|
|
|
|
if (chown(home, pw->pw_uid, pw->pw_gid) != 0 ||
|
|
|
|
|
chmod(home, 0750) != 0 ||
|
|
|
|
|
chown(bin_path, pw->pw_uid, pw->pw_gid) != 0) {
|
|
|
|
|
char owner_spec[64] = {0};
|
|
|
|
|
snprintf(owner_spec, sizeof(owner_spec), "%lu:%lu", (unsigned long)pw->pw_uid, (unsigned long)pw->pw_gid);
|
|
|
|
|
|
|
|
|
|
char* chown_home_argv[] = {"chown", owner_spec, (char*)home, NULL};
|
|
|
|
|
char* chmod_home_argv[] = {"chmod", "0750", (char*)home, NULL};
|
|
|
|
|
char* chown_bin_argv[] = {"chown", owner_spec, bin_path, NULL};
|
|
|
|
|
|
|
|
|
|
if (run_privileged_command_local(chown_home_argv) != 0 ||
|
|
|
|
|
run_privileged_command_local(chmod_home_argv) != 0 ||
|
|
|
|
|
run_privileged_command_local(chown_bin_argv) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to set ownership/permissions for installed files.%s\n", ANSI_RED, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
@@ -1459,60 +1496,66 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
|
|
|
|
|
char service_path[PATH_MAX] = {0};
|
|
|
|
|
snprintf(service_path, sizeof(service_path), "/etc/systemd/system/%s", service_name);
|
|
|
|
|
fprintf(stderr, " Writing systemd unit to %s...\n", service_path);
|
|
|
|
|
FILE* svc_fp = fopen(service_path, "w");
|
|
|
|
|
if (!svc_fp) {
|
|
|
|
|
fprintf(stderr, "%sFailed to write service file at %s.%s\n", ANSI_RED, service_path, ANSI_RESET);
|
|
|
|
|
|
|
|
|
|
char env_line[PATH_MAX + 64] = {0};
|
|
|
|
|
if (ca_bundle) {
|
|
|
|
|
snprintf(env_line, sizeof(env_line), "Environment=SSL_CERT_FILE=%s\\n", ca_bundle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char service_content[8192] = {0};
|
|
|
|
|
int svc_len = snprintf(service_content,
|
|
|
|
|
sizeof(service_content),
|
|
|
|
|
"[Unit]\n"
|
|
|
|
|
"Description=Didactyl Nostr Agent (%s)\n"
|
|
|
|
|
"After=network-online.target\n"
|
|
|
|
|
"Wants=network-online.target\n\n"
|
|
|
|
|
"[Service]\n"
|
|
|
|
|
"Type=simple\n"
|
|
|
|
|
"User=%s\n"
|
|
|
|
|
"Group=%s\n"
|
|
|
|
|
"WorkingDirectory=%s\n"
|
|
|
|
|
"ExecStart=%s --nsec %s --debug 3\n"
|
|
|
|
|
"%s"
|
|
|
|
|
"SyslogIdentifier=%s\n"
|
|
|
|
|
"NoNewPrivileges=false\n"
|
|
|
|
|
"ProtectSystem=strict\n"
|
|
|
|
|
"ReadWritePaths=%s\n"
|
|
|
|
|
"ProtectHome=false\n"
|
|
|
|
|
"PrivateTmp=yes\n"
|
|
|
|
|
"Restart=on-failure\n"
|
|
|
|
|
"RestartSec=10\n"
|
|
|
|
|
"StandardOutput=journal\n"
|
|
|
|
|
"StandardError=journal\n\n"
|
|
|
|
|
"[Install]\n"
|
|
|
|
|
"WantedBy=multi-user.target\n",
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
home,
|
|
|
|
|
bin_path,
|
|
|
|
|
cfg->keys.nsec,
|
|
|
|
|
env_line,
|
|
|
|
|
service_user,
|
|
|
|
|
home);
|
|
|
|
|
if (svc_len < 0 || (size_t)svc_len >= sizeof(service_content)) {
|
|
|
|
|
fprintf(stderr, "%sFailed to build service file content.%s\n", ANSI_RED, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(svc_fp,
|
|
|
|
|
"[Unit]\n"
|
|
|
|
|
"Description=Didactyl Nostr Agent (%s)\n"
|
|
|
|
|
"After=network-online.target\n"
|
|
|
|
|
"Wants=network-online.target\n\n"
|
|
|
|
|
"[Service]\n"
|
|
|
|
|
"Type=simple\n"
|
|
|
|
|
"User=%s\n"
|
|
|
|
|
"Group=%s\n"
|
|
|
|
|
"WorkingDirectory=%s\n"
|
|
|
|
|
"ExecStart=%s --nsec %s --debug 3\n",
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
service_user,
|
|
|
|
|
home,
|
|
|
|
|
bin_path,
|
|
|
|
|
cfg->keys.nsec);
|
|
|
|
|
|
|
|
|
|
if (ca_bundle) {
|
|
|
|
|
fprintf(svc_fp, "Environment=SSL_CERT_FILE=%s\n", ca_bundle);
|
|
|
|
|
if (write_file_privileged_local(service_path, service_content, 0644U) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to write service file at %s.%s\n", ANSI_RED, service_path, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(svc_fp,
|
|
|
|
|
"SyslogIdentifier=%s\n"
|
|
|
|
|
"NoNewPrivileges=false\n"
|
|
|
|
|
"ProtectSystem=strict\n"
|
|
|
|
|
"ReadWritePaths=%s\n"
|
|
|
|
|
"ProtectHome=false\n"
|
|
|
|
|
"PrivateTmp=yes\n"
|
|
|
|
|
"Restart=on-failure\n"
|
|
|
|
|
"RestartSec=10\n"
|
|
|
|
|
"StandardOutput=journal\n"
|
|
|
|
|
"StandardError=journal\n\n"
|
|
|
|
|
"[Install]\n"
|
|
|
|
|
"WantedBy=multi-user.target\n",
|
|
|
|
|
service_user,
|
|
|
|
|
home);
|
|
|
|
|
fclose(svc_fp);
|
|
|
|
|
|
|
|
|
|
/* --- Enable and start the service --- */
|
|
|
|
|
fprintf(stderr, " Enabling and starting %s...\n", service_name);
|
|
|
|
|
char* reload_argv[] = {"systemctl", "daemon-reload", NULL};
|
|
|
|
|
char* enable_argv[] = {"systemctl", "enable", service_name, NULL};
|
|
|
|
|
char* start_argv[] = {"systemctl", "start", service_name, NULL};
|
|
|
|
|
|
|
|
|
|
if (run_command_local(reload_argv) != 0 ||
|
|
|
|
|
run_command_local(enable_argv) != 0 ||
|
|
|
|
|
run_command_local(start_argv) != 0) {
|
|
|
|
|
if (run_privileged_command_local(reload_argv) != 0 ||
|
|
|
|
|
run_privileged_command_local(enable_argv) != 0 ||
|
|
|
|
|
run_privileged_command_local(start_argv) != 0) {
|
|
|
|
|
fprintf(stderr, "%sFailed to enable/start systemd service %s.%s\n", ANSI_RED, service_name, ANSI_RESET);
|
|
|
|
|
fprintf(stderr, " You can manually start with:\n");
|
|
|
|
|
fprintf(stderr, " sudo systemctl daemon-reload\n");
|
|
|
|
|
@@ -1717,10 +1760,33 @@ static int new_agent_flow(didactyl_config_t* cfg, char* genesis_path_out, size_t
|
|
|
|
|
fprintf(stderr, "%sFailed to install dedicated-user system service.%s\n", ANSI_RED, ANSI_RESET);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "%sStep 7 of 7 -- Booting new agent. Check your messages for initial greeting.%s\n",
|
|
|
|
|
ANSI_YELLOW,
|
|
|
|
|
ANSI_RESET);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
char service_user[64] = {0};
|
|
|
|
|
sanitize_service_user_from_name(agent_name, service_user, sizeof(service_user));
|
|
|
|
|
|
|
|
|
|
char service_name[96] = {0};
|
|
|
|
|
snprintf(service_name, sizeof(service_name), "%s.service", service_user);
|
|
|
|
|
|
|
|
|
|
sleep(2);
|
|
|
|
|
char* is_active_argv[] = {"systemctl", "is-active", "--quiet", service_name, NULL};
|
|
|
|
|
if (run_privileged_command_local(is_active_argv) == 0) {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"%sStep 7 of 7 -- Service %s is active. Setup is complete; this wizard will now exit. "
|
|
|
|
|
"Expect a startup message in the admin inbox.%s\n",
|
|
|
|
|
ANSI_YELLOW,
|
|
|
|
|
service_name,
|
|
|
|
|
ANSI_RESET);
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"%sStep 7 of 7 -- Service installed but not active yet. This wizard will now exit. "
|
|
|
|
|
"Check status with: sudo systemctl status %s ; logs with: sudo journalctl -u %s -f%s\n",
|
|
|
|
|
ANSI_RED,
|
|
|
|
|
service_name,
|
|
|
|
|
service_name,
|
|
|
|
|
ANSI_RESET);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
|