commits
tags
#include "daemon.h"
#include "../third_party/cJSON.h"
#include "util.h"
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
int daemon_host_is_local(const char *host)
{
return strcmp(host, "127.0.0.1") == 0 || strcmp(host, "localhost") == 0 ||
strcmp(host, "::1") == 0;
}
int daemon_start(char *err, size_t errlen)
{
pid_t pid = fork();
if (pid < 0) {
snprintf(err, errlen, "fork failed: %s", strerror(errno));
return -1;
}
if (pid == 0) {
/* transmission-daemon daemonizes (double-forks) itself by default,
* so this child just execs it and lets it detach on its own. Give
* it a clean session and /dev/null fds so it doesn't inherit our
* controlling terminal or fight ncurses for it. */
setsid();
int devnull = open("/dev/null", O_RDWR);
if (devnull >= 0) {
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
if (devnull > STDERR_FILENO)
close(devnull);
}
execlp("transmission-daemon", "transmission-daemon", (char *)NULL);
_exit(127); /* only reached if execlp failed */
}
int status = 0;
if (waitpid(pid, &status, 0) < 0) {
snprintf(err, errlen, "waitpid failed: %s", strerror(errno));
return -1;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
if (access("/etc/debian_version", F_OK) == 0)
snprintf(err, errlen,
"transmission-daemon is not installed. Run: sudo apt install transmission-daemon");
else
snprintf(err, errlen, "transmission-daemon is not installed (install it via your package manager)");
return -1;
}
if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
snprintf(err, errlen, "transmission-daemon exited with code %d", WEXITSTATUS(status));
return -1;
}
if (WIFSIGNALED(status)) {
snprintf(err, errlen, "transmission-daemon was killed by signal %d", WTERMSIG(status));
return -1;
}
return 0;
}
/* Runs argv[0] with argv as its arguments (no shell involved - the caller's
* values, e.g. a user-chosen password, never pass through a shell so there's
* nothing to escape or inject). Inherits our stdin/stdout/stderr so sudo can
* prompt on the real terminal. */
static int run_argv(char *const argv[], char *err, size_t errlen)
{
pid_t pid = fork();
if (pid < 0) {
if (err)
snprintf(err, errlen, "fork failed: %s", strerror(errno));
return -1;
}
if (pid == 0) {
execvp(argv[0], argv);
_exit(127);
}
int status = 0;
if (waitpid(pid, &status, 0) < 0) {
if (err)
snprintf(err, errlen, "waitpid failed: %s", strerror(errno));
return -1;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (err)
snprintf(err, errlen, "%s failed (cancelled at sudo?)", argv[0]);
return -1;
}
return 0;
}
#define TRANSMISSION_SETTINGS_PATH "/etc/transmission-daemon/settings.json"
#define TRANSMISSION_SERVICE "transmission-daemon"
int daemon_fix_auth(const char *username, const char *password, char *err, size_t errlen)
{
size_t flen = 0;
unsigned char *data = read_file_all(TRANSMISSION_SETTINGS_PATH, &flen);
if (!data) {
snprintf(err, errlen, "cannot find %s - are you running Debian's transmission-daemon package?",
TRANSMISSION_SETTINGS_PATH);
return -1;
}
cJSON *root = cJSON_ParseWithLength((const char *)data, flen);
free(data);
if (!root) {
snprintf(err, errlen, "could not parse %s", TRANSMISSION_SETTINGS_PATH);
return -1;
}
if (password && password[0]) {
cJSON_ReplaceItemInObjectCaseSensitive(root, "rpc-username", cJSON_CreateString(username));
cJSON_ReplaceItemInObjectCaseSensitive(root, "rpc-password", cJSON_CreateString(password));
cJSON_ReplaceItemInObjectCaseSensitive(root, "rpc-authentication-required", cJSON_CreateBool(1));
} else {
cJSON_ReplaceItemInObjectCaseSensitive(root, "rpc-authentication-required", cJSON_CreateBool(0));
}
char *out = cJSON_Print(root);
cJSON_Delete(root);
if (!out) {
snprintf(err, errlen, "out of memory");
return -1;
}
char tmp_path[] = "/tmp/transtui-settings-XXXXXX";
int fd = mkstemp(tmp_path);
if (fd < 0) {
snprintf(err, errlen, "mkstemp failed: %s", strerror(errno));
cJSON_free(out);
return -1;
}
size_t outlen = strlen(out);
ssize_t wr = write(fd, out, outlen);
close(fd);
cJSON_free(out);
if (wr < 0 || (size_t)wr != outlen) {
snprintf(err, errlen, "could not write temp file");
unlink(tmp_path);
return -1;
}
char *stop_argv[] = {"sudo", "systemctl", "stop", TRANSMISSION_SERVICE, NULL};
char *cp_argv[] = {"sudo", "cp", tmp_path, TRANSMISSION_SETTINGS_PATH, NULL};
char *start_argv[] = {"sudo", "systemctl", "start", TRANSMISSION_SERVICE, NULL};
int rc = run_argv(stop_argv, err, errlen);
if (rc == 0)
rc = run_argv(cp_argv, err, errlen);
/* Always try to bring the service back up, even if the copy failed,
* so we don't leave it stopped - but don't let a start failure here
* clobber a more specific error from the steps above. */
char start_err[128];
int start_rc = run_argv(start_argv, start_err, sizeof(start_err));
if (rc == 0 && start_rc != 0) {
snprintf(err, errlen, "%s", start_err);
rc = -1;
}
unlink(tmp_path);
return rc;
}
#define TRANSMISSION_DAEMON_USER "debian-transmission"
int daemon_fix_download_dir_perms(const char *dir, char *err, size_t errlen)
{
struct stat dst;
if (stat(dir, &dst) != 0) {
snprintf(err, errlen, "cannot stat %s: %s", dir, strerror(errno));
return -1;
}
struct group *gr = getgrgid(dst.st_gid);
if (!gr) {
snprintf(err, errlen, "cannot resolve group for %s", dir);
return -1;
}
char *usermod_argv[] = {"sudo", "usermod", "-aG", gr->gr_name, TRANSMISSION_DAEMON_USER, NULL};
if (run_argv(usermod_argv, err, errlen) != 0)
return -1;
if (chmod(dir, (dst.st_mode & 07777) | S_IRGRP | S_IWGRP | S_IXGRP) != 0) {
snprintf(err, errlen, "chmod %s failed: %s", dir, strerror(errno));
return -1;
}
char *restart_argv[] = {"sudo", "systemctl", "restart", TRANSMISSION_SERVICE, NULL};
return run_argv(restart_argv, err, errlen);
}