push
This commit is contained in:
412
server.c
Normal file
412
server.c
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "prog.h"
|
||||
#include "command_mode.h"
|
||||
#include "search_mode.h"
|
||||
#include "options.h"
|
||||
#include "output.h"
|
||||
#include "utils.h"
|
||||
#include "xmalloc.h"
|
||||
#include "player.h"
|
||||
#include "file.h"
|
||||
#include "compiler.h"
|
||||
#include "debug.h"
|
||||
#include "gbuf.h"
|
||||
#include "ui_curses.h"
|
||||
#include "misc.h"
|
||||
#include "keyval.h"
|
||||
#include "convert.h"
|
||||
#include "format_print.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
int server_socket;
|
||||
LIST_HEAD(client_head);
|
||||
|
||||
static char *title_buf = NULL;
|
||||
|
||||
static union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_un un;
|
||||
struct sockaddr_storage sas;
|
||||
} addr;
|
||||
|
||||
#define MAX_CLIENTS 10
|
||||
|
||||
static int cmd_status(struct client *client)
|
||||
{
|
||||
const char *export_options[] = {
|
||||
"aaa_mode",
|
||||
"continue",
|
||||
"play_library",
|
||||
"play_sorted",
|
||||
"replaygain",
|
||||
"replaygain_limit",
|
||||
"replaygain_preamp",
|
||||
"repeat",
|
||||
"repeat_current",
|
||||
"shuffle",
|
||||
"softvol",
|
||||
NULL
|
||||
};
|
||||
const struct track_info *ti;
|
||||
struct cmus_opt *opt;
|
||||
char optbuf[OPTION_MAX_SIZE];
|
||||
GBUF(buf);
|
||||
int vol_left, vol_right;
|
||||
int i, ret;
|
||||
enum player_status status;
|
||||
|
||||
gbuf_addf(&buf, "status %s\n", player_status_names[player_info.status]);
|
||||
ti = player_info.ti;
|
||||
if (ti) {
|
||||
gbuf_addf(&buf, "file %s\n", escape(ti->filename));
|
||||
gbuf_addf(&buf, "duration %d\n", ti->duration);
|
||||
gbuf_addf(&buf, "position %d\n", player_info.pos);
|
||||
for (i = 0; ti->comments[i].key; i++)
|
||||
gbuf_addf(&buf, "tag %s %s\n",
|
||||
ti->comments[i].key,
|
||||
escape(ti->comments[i].val));
|
||||
}
|
||||
|
||||
/* add track metadata to cmus-status */
|
||||
status = player_info.status;
|
||||
if (status == PLAYER_STATUS_PLAYING && ti && is_http_url(player_info.ti->filename)) {
|
||||
const char *title = get_stream_title();
|
||||
if (title != NULL) {
|
||||
free(title_buf);
|
||||
title_buf = to_utf8(title, icecast_default_charset);
|
||||
// we have a stream title (probably artist/track/album info)
|
||||
gbuf_addf(&buf, "stream %s\n", escape(title_buf));
|
||||
} else if (ti->comment != NULL) {
|
||||
// fallback to the radio station name
|
||||
gbuf_addf(&buf, "stream %s\n", escape(ti->comment));
|
||||
}
|
||||
}
|
||||
|
||||
/* output options */
|
||||
for (i = 0; export_options[i]; i++) {
|
||||
opt = option_find(export_options[i]);
|
||||
if (opt) {
|
||||
opt->get(opt->data, optbuf, OPTION_MAX_SIZE);
|
||||
gbuf_addf(&buf, "set %s %s\n", opt->name, optbuf);
|
||||
}
|
||||
}
|
||||
|
||||
/* get volume (copied from ui_curses.c) */
|
||||
if (soft_vol) {
|
||||
vol_left = soft_vol_l;
|
||||
vol_right = soft_vol_r;
|
||||
} else if (!volume_max) {
|
||||
vol_left = vol_right = -1;
|
||||
} else {
|
||||
vol_left = scale_to_percentage(volume_l, volume_max);
|
||||
vol_right = scale_to_percentage(volume_r, volume_max);
|
||||
}
|
||||
|
||||
/* output volume */
|
||||
gbuf_addf(&buf, "set vol_left %d\n", vol_left);
|
||||
gbuf_addf(&buf, "set vol_right %d\n", vol_right);
|
||||
|
||||
gbuf_add_str(&buf, "\n");
|
||||
|
||||
ret = write_all(client->fd, buf.buffer, buf.len);
|
||||
gbuf_free(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cmd_format_print(struct client *client, char *arg)
|
||||
{
|
||||
if (run_only_safe_commands) {
|
||||
d_print("trying to execute unsafe command over net\n");
|
||||
return write_all(client->fd, "\n", strlen("\n"));
|
||||
}
|
||||
|
||||
int args_idx, ac, i, ret;
|
||||
char **args = NULL;
|
||||
|
||||
if (arg)
|
||||
args = parse_cmd(arg, &args_idx, &ac);
|
||||
|
||||
if (args == NULL) {
|
||||
error_msg("not enough arguments\n");
|
||||
return write_all(client->fd, "\n", strlen("\n"));
|
||||
}
|
||||
|
||||
GBUF(buf);
|
||||
|
||||
const struct format_option *fopts = get_global_fopts();
|
||||
for (i = 0; i < ac; ++i) {
|
||||
if (format_valid(args[i], fopts))
|
||||
format_print(&buf, 0, args[i], fopts);
|
||||
gbuf_add_ch(&buf, '\n');
|
||||
free(args[i]);
|
||||
}
|
||||
gbuf_add_ch(&buf, '\n');
|
||||
|
||||
ret = write_all(client->fd, buf.buffer, buf.len);
|
||||
gbuf_free(&buf);
|
||||
free(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t send_answer(int fd, const char *format, ...)
|
||||
{
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
vsnprintf(buf, sizeof(buf), format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return write_all(fd, buf, strlen(buf));
|
||||
}
|
||||
|
||||
static void read_commands(struct client *client)
|
||||
{
|
||||
char buf[1024];
|
||||
int pos = 0;
|
||||
if (!client->authenticated)
|
||||
client->authenticated = addr.sa.sa_family == AF_UNIX;
|
||||
|
||||
while (1) {
|
||||
int rc, s, i;
|
||||
|
||||
rc = read(client->fd, buf + pos, sizeof(buf) - pos);
|
||||
if (rc == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN)
|
||||
return;
|
||||
goto close;
|
||||
}
|
||||
if (rc == 0)
|
||||
goto close;
|
||||
pos += rc;
|
||||
|
||||
s = 0;
|
||||
for (i = 0; i < pos; i++) {
|
||||
const char *line, *msg;
|
||||
char *cmd, *arg;
|
||||
int ret;
|
||||
|
||||
if (buf[i] != '\n')
|
||||
continue;
|
||||
|
||||
buf[i] = 0;
|
||||
line = buf + s;
|
||||
s = i + 1;
|
||||
|
||||
if (!client->authenticated) {
|
||||
if (!server_password) {
|
||||
msg = "password is unset, tcp/ip disabled";
|
||||
d_print("%s\n", msg);
|
||||
ret = send_answer(client->fd, "%s\n\n", msg);
|
||||
goto close;
|
||||
}
|
||||
if (strncmp(line, "passwd ", 7) == 0)
|
||||
line += 7;
|
||||
client->authenticated = !strcmp(line, server_password);
|
||||
if (!client->authenticated) {
|
||||
msg = "authentication failed";
|
||||
d_print("%s\n", msg);
|
||||
ret = send_answer(client->fd, "%s\n\n", msg);
|
||||
goto close;
|
||||
}
|
||||
ret = write_all(client->fd, "\n", 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (isspace((unsigned char)*line))
|
||||
line++;
|
||||
|
||||
if (*line == '/') {
|
||||
int restricted = 0;
|
||||
line++;
|
||||
search_direction = SEARCH_FORWARD;
|
||||
if (*line == '/') {
|
||||
line++;
|
||||
restricted = 1;
|
||||
}
|
||||
search_text(line, restricted, 1);
|
||||
ret = write_all(client->fd, "\n", 1);
|
||||
} else if (*line == '?') {
|
||||
int restricted = 0;
|
||||
line++;
|
||||
search_direction = SEARCH_BACKWARD;
|
||||
if (*line == '?') {
|
||||
line++;
|
||||
restricted = 1;
|
||||
}
|
||||
search_text(line, restricted, 1);
|
||||
ret = write_all(client->fd, "\n", 1);
|
||||
} else if (parse_command(line, &cmd, &arg)) {
|
||||
if (!strcmp(cmd, "status")) {
|
||||
ret = cmd_status(client);
|
||||
} else if (!strcmp(cmd, "format_print")) {
|
||||
ret = cmd_format_print(client, arg);
|
||||
} else {
|
||||
if (strcmp(cmd, "passwd") != 0) {
|
||||
set_client_fd(client->fd);
|
||||
run_parsed_command(cmd, arg);
|
||||
set_client_fd(-1);
|
||||
}
|
||||
ret = write_all(client->fd, "\n", 1);
|
||||
}
|
||||
free(cmd);
|
||||
free(arg);
|
||||
} else {
|
||||
// don't hang cmus-remote
|
||||
ret = write_all(client->fd, "\n", 1);
|
||||
}
|
||||
if (ret < 0) {
|
||||
d_print("write: %s\n", strerror(errno));
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
memmove(buf, buf + s, pos - s);
|
||||
pos -= s;
|
||||
}
|
||||
return;
|
||||
close:
|
||||
close(client->fd);
|
||||
list_del(&client->node);
|
||||
free(client);
|
||||
}
|
||||
|
||||
void server_accept(void)
|
||||
{
|
||||
struct client *client;
|
||||
struct sockaddr saddr;
|
||||
socklen_t saddr_size = sizeof(saddr);
|
||||
int fd;
|
||||
|
||||
fd = accept(server_socket, &saddr, &saddr_size);
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
client = xnew(struct client, 1);
|
||||
client->fd = fd;
|
||||
client->authenticated = 0;
|
||||
list_add_tail(&client->node, &client_head);
|
||||
}
|
||||
|
||||
void server_serve(struct client *client)
|
||||
{
|
||||
/* unix connection is secure, other insecure */
|
||||
run_only_safe_commands = addr.sa.sa_family != AF_UNIX;
|
||||
read_commands(client);
|
||||
run_only_safe_commands = 0;
|
||||
}
|
||||
|
||||
void server_init(char *address)
|
||||
{
|
||||
const char *port = STRINGIZE(DEFAULT_PORT);
|
||||
size_t addrlen;
|
||||
|
||||
if (strchr(address, '/')) {
|
||||
addr.sa.sa_family = AF_UNIX;
|
||||
strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
|
||||
|
||||
addrlen = sizeof(struct sockaddr_un);
|
||||
} else {
|
||||
const struct addrinfo hints = {
|
||||
.ai_socktype = SOCK_STREAM
|
||||
};
|
||||
struct addrinfo *result;
|
||||
char *s = strrchr(address, ':');
|
||||
int rc;
|
||||
|
||||
if (s) {
|
||||
*s++ = 0;
|
||||
port = s;
|
||||
}
|
||||
|
||||
rc = getaddrinfo(address, port, &hints, &result);
|
||||
if (rc != 0)
|
||||
die("getaddrinfo: %s\n", gai_strerror(rc));
|
||||
memcpy(&addr.sa, result->ai_addr, result->ai_addrlen);
|
||||
addrlen = result->ai_addrlen;
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
server_socket = socket(addr.sa.sa_family, SOCK_STREAM, 0);
|
||||
if (server_socket == -1)
|
||||
die_errno("socket");
|
||||
|
||||
if (bind(server_socket, &addr.sa, addrlen) == -1) {
|
||||
int sock;
|
||||
|
||||
if (errno != EADDRINUSE)
|
||||
die_errno("bind");
|
||||
|
||||
/* address already in use */
|
||||
if (addr.sa.sa_family != AF_UNIX)
|
||||
die("cmus is already listening on %s:%s\n", address, port);
|
||||
|
||||
/* try to connect to server */
|
||||
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1)
|
||||
die_errno("socket");
|
||||
|
||||
if (connect(sock, &addr.sa, addrlen) == -1) {
|
||||
if (errno != ENOENT && errno != ECONNREFUSED)
|
||||
die_errno("connect");
|
||||
|
||||
/* server not running => dead socket */
|
||||
|
||||
/* try to remove dead socket */
|
||||
if (unlink(addr.un.sun_path) == -1 && errno != ENOENT)
|
||||
die_errno("unlink");
|
||||
if (bind(server_socket, &addr.sa, addrlen) == -1)
|
||||
die_errno("bind");
|
||||
} else {
|
||||
/* server already running */
|
||||
die("cmus is already listening on socket %s\n", address);
|
||||
}
|
||||
close(sock);
|
||||
}
|
||||
|
||||
if (listen(server_socket, MAX_CLIENTS) == -1)
|
||||
die_errno("listen");
|
||||
}
|
||||
|
||||
void server_exit(void)
|
||||
{
|
||||
close(server_socket);
|
||||
if (addr.sa.sa_family == AF_UNIX)
|
||||
unlink(addr.un.sun_path);
|
||||
}
|
||||
Reference in New Issue
Block a user