#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
+#include <pwd.h>
#include "para.h"
#include "error.h"
#include "signal.h"
#include "version.h"
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
__printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
-/** define the array of error lists needed by para_audiod */
-INIT_AUDIOD_ERRLISTS;
/** define the array containing all supported audio formats */
const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL};
* the gengetopt args_info struct that holds information on all command line
* arguments
*/
-struct audiod_args_info conf;
+static struct audiod_args_info conf;
static char *socket_name;
static struct audio_format_info afi[NUM_AUDIO_FORMATS];
static struct status_task status_task_struct;
+static uid_t *uid_whitelist;
+
/**
* the task that calls the status command of para_server
*
*/
static struct status_task *stat_task = &status_task_struct;
-/*
- * The task for handling audiod commands.
- *
- * We need two listening sockets for backward compability: on Linux systems
- * fd[0] is an abstract socket (more precisely, a socket bound to an address in
- * the abstract namespace), and fd[1] is the usual pathname socket. On other
- * systems, fd[0] is negative, and only the pathname socket is used.
- *
- * For 0.5.x we accept connections on both sockets to make sure that old
- * para_audioc versions can still connect. New versions use only the abstract
- * socket. Hence after v0.6.0 we can go back to a single socket, either an
- * abstract one (Linux) or a pathname socket (all other systems).
- */
struct command_task {
- /** The local listening sockets. */
- int fd[2];
+ /** The local listening socket. */
+ int fd;
/** the associated task structure */
struct task *task;
};
*/
char *get_time_string(void)
{
- int ret, seconds = 0, length;
+ int ret, seconds = 0, length = stat_task->length_seconds;
struct timeval *tmp, sum, sss, /* server stream start */
rstime, /* receiver start time */
wstime, /* writer start time */
rskip; /* receiver start - sss */
int slot_num = get_play_time_slot_num();
struct slot_info *s = slot_num < 0? NULL : &slot[slot_num];
+ bool writer_active = s && s->wns && s->wns[0].btrn;
char *msg;
if (audiod_status == AUDIOD_OFF)
goto empty;
- if (!(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) {
- if (stat_task->length_seconds) /* paused */
+ if (stat_task->server_stream_start.tv_sec == 0) {
+ if (stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)
+ goto out; /* server is about to change file */
+ if (length > 0) /* paused */
return NULL;
goto empty; /* stopped */
}
- if (audiod_status == AUDIOD_ON && !s)
- goto empty;
/*
* Valid status items and playing, set length and tmp to the stream
- * start. We use the slot info and fall back to the info from current
- * status items if no slot info is available.
+ * start. We use the writer start time from the slot info and fall back
+ * to the info from current status items if no writer is active yet.
*/
- length = stat_task->length_seconds;
tmp = &stat_task->server_stream_start;
- if (s && s->wns && s->wns[0].btrn) { /* writer active in this slot */
+ if (writer_active) {
btr_get_node_start(s->wns[0].btrn, &wstime);
if (wstime.tv_sec != 0) { /* writer wrote something */
if (s->server_stream_start.tv_sec == 0) {
tv_diff(tmp, &stat_task->sa_time_diff, &sss);
else
tv_add(tmp, &stat_task->sa_time_diff, &sss);
- if (!s || !s->wns || !s->wns[0].btrn) {
+ if (!writer_active) {
struct timeval diff;
tv_diff(now, &sss, &diff);
seconds = diff.tv_sec + stat_task->offset_seconds;
if (s->receiver_node->btrn) {
btr_get_node_start(s->receiver_node->btrn, &rstime);
ret = tv_diff(&rstime, &sss, &rskip);
- if (ret > 0) { /* audiod was started in the middle of the stream */
+ if (ret > 0 && rskip.tv_sec > 2) {
+ /* audiod was started in the middle of the stream */
tv_add(&wtime, &rskip, &sum);
seconds += sum.tv_sec;
} else
static void parse_config_or_die(void)
{
- int ret;
+ int ret, i;
char *config_file;
struct audiod_cmdline_parser_params params = {
.override = 0,
ret = file_exists(config_file);
if (conf.config_file_given && !ret) {
PARA_EMERG_LOG("can not read config file %s\n", config_file);
+ free(config_file);
goto err;
}
if (ret) {
daemon_set_loglevel(conf.loglevel_arg);
}
free(config_file);
+ if (conf.user_allow_given > 0) {
+ uid_whitelist = para_malloc(conf.user_allow_given
+ * sizeof(uid_t));
+ for (i = 0; i < conf.user_allow_given; i++) {
+ int32_t val;
+ struct passwd *pw;
+ ret = para_atoi32(conf.user_allow_arg[i], &val);
+ if (ret >= 0) {
+ uid_whitelist[i] = val;
+ continue;
+ }
+ errno = 0; /* see getpwnam(3) */
+ pw = getpwnam(conf.user_allow_arg[i]);
+ if (!pw) {
+ PARA_EMERG_LOG("invalid username: %s\n",
+ conf.user_allow_arg[i]);
+ goto err;
+ }
+ uid_whitelist[i] = pw->pw_uid;
+ }
+ }
return;
err:
- free(config_file);
exit(EXIT_FAILURE);
}
return;
for (i = a->num_filters - 1; i >= 0; i--) {
struct filter_node *fn = s->fns + i;
- struct filter *f;
+ const struct filter *f;
if (!fn)
continue;
- f = filters + fn->filter_num;
+ f = filter_get(fn->filter_num);
if (f->close)
f->close(fn);
btr_remove_node(&fn->btrn);
parent = s->receiver_node->btrn;
for (i = 0; i < nf; i++) {
char buf[20];
- struct filter *f = filters + a->filter_nums[i];
+ const struct filter *f = filter_get(a->filter_nums[i]);
fn = s->fns + i;
fn->filter_num = a->filter_nums[i];
fn->conf = a->filter_conf[i];
EMBRACE(.name = f->name, .parent = parent,
.handler = f->execute, .context = fn));
- f->open(fn);
+ if (f->open)
+ f->open(fn);
sprintf(buf, "%s (slot %d)", f->name, (int)(s - slot));
fn->task = task_register(&(struct task_info) {
.name = buf,
if (count > 5) {
int s = tv_diff(&diff, &stat_task->sa_time_diff, &tmp);
if (tv_diff(&max_deviation, &tmp, NULL) < 0)
- PARA_WARNING_LOG("time diff jump: %lims\n",
+ PARA_WARNING_LOG("time diff jump: %lums\n",
s * tv2ms(&tmp));
}
count++;
a->filter_conf[nf] = cfg;
a->num_filters++;
PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf,
- filters[filter_num].name);
+ filter_get(filter_num)->name);
return filter_num;
}
}
/* add "dec" to audio format name */
tmp = make_message("%sdec", audio_formats[i]);
- for (j = 0; filters[j].name; j++)
- if (!strcmp(tmp, filters[j].name))
+ for (j = 0; filter_get(j); j++)
+ if (!strcmp(tmp, filter_get(j)->name))
break;
free(tmp);
ret = -E_UNSUPPORTED_FILTER;
- if (!filters[j].name)
+ if (!filter_get(j))
goto out;
- tmp = para_strdup(filters[j].name);
+ tmp = para_strdup(filter_get(j)->name);
ret = add_filter(i, tmp);
free(tmp);
if (ret < 0)
goto out;
PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i],
- filters[j].name);
+ filter_get(j)->name);
}
out:
return ret;
}
/* does not unlink socket on errors */
-static void init_local_sockets(struct command_task *ct)
+static void init_local_socket(struct command_task *ct)
{
if (conf.socket_given)
socket_name = para_strdup(conf.socket_arg);
PARA_NOTICE_LOG("local socket: %s\n", socket_name);
if (conf.force_given)
unlink(socket_name);
- ct->fd[0] = create_local_socket(socket_name, 0);
- ct->fd[1] = create_local_socket(socket_name,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH);
- if (ct->fd[0] >= 0 || ct->fd[1] >= 0)
+ ct->fd = create_local_socket(socket_name);
+ if (ct->fd >= 0)
return;
- PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1]));
+ PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd));
exit(EXIT_FAILURE);
}
static void command_pre_select(struct sched *s, void *context)
{
struct command_task *ct = context;
- int i;
-
- for (i = 0; i < 2; i++)
- if (ct->fd[i] >= 0)
- para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno);
+ para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
}
static int command_post_select(struct sched *s, void *context)
{
- int ret, i;
+ int ret;
struct command_task *ct = context;
static struct timeval last_status_dump;
struct timeval tmp, delay;
ret = task_get_notification(ct->task);
if (ret < 0)
return ret;
- for (i = 0; i < 2; i++) {
- if (ct->fd[i] < 0)
- continue;
- ret = handle_connect(ct->fd[i], &s->rfds);
- if (ret < 0) {
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
- if (ret == -E_AUDIOD_TERM) {
- task_notify_all(s, -ret);
- return ret;
- }
- } else if (ret > 0)
- force = true;
- }
+ ret = handle_connect(ct->fd, &s->rfds);
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ if (ret == -E_AUDIOD_TERM) {
+ task_notify_all(s, -ret);
+ return ret;
+ }
+ } else if (ret > 0)
+ force = true;
if (force == true)
goto dump;
static void init_command_task(struct command_task *ct)
{
- init_local_sockets(ct); /* doesn't return on errors */
+ init_local_socket(ct); /* doesn't return on errors */
ct->task = task_register(&(struct task_info) {
.name = "command",
static void close_unused_slots(void)
{
int i;
+ bool dump = false;
FOR_EACH_SLOT(i)
- if (must_close_slot(i))
+ if (must_close_slot(i)) {
close_slot(i);
+ dump = true;
+ }
+ if (dump)
+ audiod_status_dump(true);
}
/*
close_unused_slots();
audiod_cmdline_parser_free(&conf);
close_stat_clients();
+ free(uid_whitelist);
}
/*
open_writers(sl);
activate_grab_clients(&sched);
btr_log_tree(sl->receiver_node->btrn, LL_NOTICE);
+ audiod_status_dump(true);
}
static void status_pre_select(struct sched *s, void *context)
if (st->clock_diff_count) { /* get status only one time */
char *argv[] = {"audiod", "--", "stat", "-p", "-n=1", NULL};
int argc = 5;
- PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count);
+ PARA_INFO_LOG("clock diff count: %u\n", st->clock_diff_count);
st->clock_diff_count--;
client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s);
set_stat_task_restart_barrier(2);
exit(0);
}
+/**
+ * Lookup the given UID in the whitelist.
+ *
+ * The whitelist is the array of arguments to the --user-allow opion. If the
+ * option was not given, the array is empty, in which case the check succeeds.
+ *
+ * \param uid User ID to look up.
+ *
+ * \return True if --user-allow was not given, or if uid matches an element of
+ * the whitelist.
+ */
+bool uid_is_whitelisted(uid_t uid)
+{
+ int i;
+
+ if (!conf.user_allow_given)
+ return true;
+ for (i = 0; i < conf.user_allow_given; i++)
+ if (uid == uid_whitelist[i])
+ return true;
+ return false;
+}
+
/**
* the main function of para_audiod
*
writer_init();
if (conf.help_given || conf.detailed_help_given)
print_help_and_die();
+ daemon_set_priority(conf.priority_arg);
daemon_drop_privileges_or_die(conf.user_arg, conf.group_arg);
parse_config_or_die();
- daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no,
- conf.logfile_given, conf.log_color_arg, conf.log_color_given);
+ if (daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no,
+ conf.logfile_given)) {
+ for (i = 0; i < conf.log_color_given; i++)
+ daemon_set_log_color_or_die(conf.log_color_arg[i]);
+ }
init_random_seed_or_die();
daemon_set_flag(DF_LOG_TIME);
daemon_set_flag(DF_LOG_HOSTNAME);
PARA_EMERG_LOG("%s\n", para_strerror(-ret));
exit(EXIT_FAILURE);
}
- daemon_log_welcome("para_audiod");
+ daemon_log_welcome("audiod");
daemon_set_start_time();
set_initial_status();
FOR_EACH_SLOT(i)