/* * 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 . */ #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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); }