aboutsummaryrefslogtreecommitdiffstats
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/Client.cxx24
-rw-r--r--src/client/Client.hxx192
-rw-r--r--src/client/ClientEvent.cxx37
-rw-r--r--src/client/ClientExpire.cxx43
-rw-r--r--src/client/ClientFile.cxx67
-rw-r--r--src/client/ClientFile.hxx40
-rw-r--r--src/client/ClientGlobal.cxx45
-rw-r--r--src/client/ClientIdle.cxx75
-rw-r--r--src/client/ClientInternal.hxx39
-rw-r--r--src/client/ClientList.cxx62
-rw-r--r--src/client/ClientList.hxx64
-rw-r--r--src/client/ClientMessage.cxx41
-rw-r--r--src/client/ClientMessage.hxx52
-rw-r--r--src/client/ClientNew.cxx117
-rw-r--r--src/client/ClientProcess.cxx141
-rw-r--r--src/client/ClientRead.cxx75
-rw-r--r--src/client/ClientSubscribe.cxx87
-rw-r--r--src/client/ClientWrite.cxx61
18 files changed, 1262 insertions, 0 deletions
diff --git a/src/client/Client.cxx b/src/client/Client.cxx
new file mode 100644
index 000000000..ce99faa89
--- /dev/null
+++ b/src/client/Client.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "util/Domain.hxx"
+
+const Domain client_domain("client");
diff --git a/src/client/Client.hxx b/src/client/Client.hxx
new file mode 100644
index 000000000..ec7d2d741
--- /dev/null
+++ b/src/client/Client.hxx
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_H
+#define MPD_CLIENT_H
+
+#include "check.h"
+#include "ClientMessage.hxx"
+#include "command/CommandListBuilder.hxx"
+#include "event/FullyBufferedSocket.hxx"
+#include "event/TimeoutMonitor.hxx"
+#include "Compiler.h"
+
+#include <set>
+#include <string>
+#include <list>
+
+#include <stddef.h>
+#include <stdarg.h>
+
+struct sockaddr;
+class EventLoop;
+struct Partition;
+
+class Client final : private FullyBufferedSocket, TimeoutMonitor {
+public:
+ Partition &partition;
+ struct playlist &playlist;
+ struct PlayerControl &player_control;
+
+ unsigned permission;
+
+ /** the uid of the client process, or -1 if unknown */
+ int uid;
+
+ CommandListBuilder cmd_list;
+
+ unsigned int num; /* client number */
+
+ /** is this client waiting for an "idle" response? */
+ bool idle_waiting;
+
+ /** idle flags pending on this client, to be sent as soon as
+ the client enters "idle" */
+ unsigned idle_flags;
+
+ /** idle flags that the client wants to receive */
+ unsigned idle_subscriptions;
+
+ /**
+ * A list of channel names this client is subscribed to.
+ */
+ std::set<std::string> subscriptions;
+
+ /**
+ * The number of subscriptions in #subscriptions. Used to
+ * limit the number of subscriptions.
+ */
+ unsigned num_subscriptions;
+
+ /**
+ * A list of messages this client has received.
+ */
+ std::list<ClientMessage> messages;
+
+ Client(EventLoop &loop, Partition &partition,
+ int fd, int uid, int num);
+
+ ~Client() {
+ if (FullyBufferedSocket::IsDefined())
+ FullyBufferedSocket::Close();
+ }
+
+ bool IsConnected() const {
+ return FullyBufferedSocket::IsDefined();
+ }
+
+ gcc_pure
+ bool IsExpired() const {
+ return !FullyBufferedSocket::IsDefined();
+ }
+
+ void Close();
+ void SetExpired();
+
+ using FullyBufferedSocket::Write;
+
+ /**
+ * returns the uid of the client process, or a negative value
+ * if the uid is unknown
+ */
+ int GetUID() const {
+ return uid;
+ }
+
+ /**
+ * Is this client running on the same machine, connected with
+ * a local (UNIX domain) socket?
+ */
+ bool IsLocal() const {
+ return uid > 0;
+ }
+
+ unsigned GetPermission() const {
+ return permission;
+ }
+
+ void SetPermission(unsigned _permission) {
+ permission = _permission;
+ }
+
+ /**
+ * Send "idle" response to this client.
+ */
+ void IdleNotify();
+ void IdleAdd(unsigned flags);
+ bool IdleWait(unsigned flags);
+
+ enum class SubscribeResult {
+ /** success */
+ OK,
+
+ /** invalid channel name */
+ INVALID,
+
+ /** already subscribed to this channel */
+ ALREADY,
+
+ /** too many subscriptions */
+ FULL,
+ };
+
+ gcc_pure
+ bool IsSubscribed(const char *channel_name) const {
+ return subscriptions.find(channel_name) != subscriptions.end();
+ }
+
+ SubscribeResult Subscribe(const char *channel);
+ bool Unsubscribe(const char *channel);
+ void UnsubscribeAll();
+ bool PushMessage(const ClientMessage &msg);
+
+private:
+ /* virtual methods from class BufferedSocket */
+ virtual InputResult OnSocketInput(void *data, size_t length) override;
+ virtual void OnSocketError(Error &&error) override;
+ virtual void OnSocketClosed() override;
+
+ /* virtual methods from class TimeoutMonitor */
+ virtual void OnTimeout() override;
+};
+
+void client_manager_init(void);
+
+void
+client_new(EventLoop &loop, Partition &partition,
+ int fd, const sockaddr *sa, size_t sa_length, int uid);
+
+/**
+ * Write a C string to the client.
+ */
+void client_puts(Client &client, const char *s);
+
+/**
+ * Write a printf-like formatted string to the client.
+ */
+void client_vprintf(Client &client, const char *fmt, va_list args);
+
+/**
+ * Write a printf-like formatted string to the client.
+ */
+gcc_printf(2,3)
+void
+client_printf(Client &client, const char *fmt, ...);
+
+#endif
diff --git a/src/client/ClientEvent.cxx b/src/client/ClientEvent.cxx
new file mode 100644
index 000000000..fd9f24b0d
--- /dev/null
+++ b/src/client/ClientEvent.cxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+void
+Client::OnSocketError(Error &&error)
+{
+ FormatError(error, "error on client %d", num);
+
+ SetExpired();
+}
+
+void
+Client::OnSocketClosed()
+{
+ SetExpired();
+}
diff --git a/src/client/ClientExpire.cxx b/src/client/ClientExpire.cxx
new file mode 100644
index 000000000..5891756b6
--- /dev/null
+++ b/src/client/ClientExpire.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Log.hxx"
+
+void
+Client::SetExpired()
+{
+ if (IsExpired())
+ return;
+
+ FullyBufferedSocket::Close();
+ TimeoutMonitor::Schedule(0);
+}
+
+void
+Client::OnTimeout()
+{
+ if (!IsExpired()) {
+ assert(!idle_waiting);
+ FormatDebug(client_domain, "[%u] timeout", num);
+ }
+
+ Close();
+}
diff --git a/src/client/ClientFile.cxx b/src/client/ClientFile.cxx
new file mode 100644
index 000000000..bdd9b0426
--- /dev/null
+++ b/src/client/ClientFile.cxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientFile.hxx"
+#include "Client.hxx"
+#include "protocol/Ack.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+bool
+client_allow_file(const Client &client, Path path_fs, Error &error)
+{
+#ifdef WIN32
+ (void)client;
+ (void)path_fs;
+
+ error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
+ return false;
+#else
+ const int uid = client.GetUID();
+ if (uid >= 0 && (uid_t)uid == geteuid())
+ /* always allow access if user runs his own MPD
+ instance */
+ return true;
+
+ if (uid <= 0) {
+ /* unauthenticated client */
+ error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
+ return false;
+ }
+
+ struct stat st;
+ if (!StatFile(path_fs, st)) {
+ error.SetErrno();
+ return false;
+ }
+
+ if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
+ /* client is not owner */
+ error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
+ return false;
+ }
+
+ return true;
+#endif
+}
diff --git a/src/client/ClientFile.hxx b/src/client/ClientFile.hxx
new file mode 100644
index 000000000..5a02a8df7
--- /dev/null
+++ b/src/client/ClientFile.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_FILE_HXX
+#define MPD_CLIENT_FILE_HXX
+
+class Client;
+class Path;
+class Error;
+
+/**
+ * Is this client allowed to use the specified local file?
+ *
+ * Note that this function is vulnerable to timing/symlink attacks.
+ * We cannot fix this as long as there are plugins that open a file by
+ * its name, and not by file descriptor / callbacks.
+ *
+ * @param path_fs the absolute path name in filesystem encoding
+ * @return true if access is allowed
+ */
+bool
+client_allow_file(const Client &client, Path path_fs, Error &error);
+
+#endif
diff --git a/src/client/ClientGlobal.cxx b/src/client/ClientGlobal.cxx
new file mode 100644
index 000000000..8d90721e9
--- /dev/null
+++ b/src/client/ClientGlobal.cxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "config/ConfigGlobal.hxx"
+
+#define CLIENT_TIMEOUT_DEFAULT (60)
+#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
+#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
+
+int client_timeout;
+size_t client_max_command_list_size;
+size_t client_max_output_buffer_size;
+
+void client_manager_init(void)
+{
+ client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
+ CLIENT_TIMEOUT_DEFAULT);
+ client_max_command_list_size =
+ config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
+ CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
+ * 1024;
+
+ client_max_output_buffer_size =
+ config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
+ CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
+ * 1024;
+}
diff --git a/src/client/ClientIdle.cxx b/src/client/ClientIdle.cxx
new file mode 100644
index 000000000..0b4fa5751
--- /dev/null
+++ b/src/client/ClientIdle.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Idle.hxx"
+
+#include <assert.h>
+
+void
+Client::IdleNotify()
+{
+ assert(idle_waiting);
+ assert(idle_flags != 0);
+
+ unsigned flags = idle_flags;
+ idle_flags = 0;
+ idle_waiting = false;
+
+ const char *const*idle_names = idle_get_names();
+ for (unsigned i = 0; idle_names[i]; ++i) {
+ if (flags & (1 << i) & idle_subscriptions)
+ client_printf(*this, "changed: %s\n",
+ idle_names[i]);
+ }
+
+ client_puts(*this, "OK\n");
+
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+}
+
+void
+Client::IdleAdd(unsigned flags)
+{
+ if (IsExpired())
+ return;
+
+ idle_flags |= flags;
+ if (idle_waiting && (idle_flags & idle_subscriptions))
+ IdleNotify();
+}
+
+bool
+Client::IdleWait(unsigned flags)
+{
+ assert(!idle_waiting);
+
+ idle_waiting = true;
+ idle_subscriptions = flags;
+
+ if (idle_flags & idle_subscriptions) {
+ IdleNotify();
+ return true;
+ } else {
+ /* disable timeouts while in "idle" */
+ TimeoutMonitor::Cancel();
+ return false;
+ }
+}
diff --git a/src/client/ClientInternal.hxx b/src/client/ClientInternal.hxx
new file mode 100644
index 000000000..a819d64b8
--- /dev/null
+++ b/src/client/ClientInternal.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_INTERNAL_HXX
+#define MPD_CLIENT_INTERNAL_HXX
+
+#include "check.h"
+#include "Client.hxx"
+#include "command/CommandResult.hxx"
+
+static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16;
+static constexpr unsigned CLIENT_MAX_MESSAGES = 64;
+
+extern const class Domain client_domain;
+
+extern int client_timeout;
+extern size_t client_max_command_list_size;
+extern size_t client_max_output_buffer_size;
+
+CommandResult
+client_process_line(Client &client, char *line);
+
+#endif
diff --git a/src/client/ClientList.cxx b/src/client/ClientList.cxx
new file mode 100644
index 000000000..101802479
--- /dev/null
+++ b/src/client/ClientList.cxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientList.hxx"
+#include "ClientInternal.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+
+void
+ClientList::Remove(Client &client)
+{
+ assert(size > 0);
+ assert(!list.empty());
+
+ auto i = std::find(list.begin(), list.end(), &client);
+ assert(i != list.end());
+ list.erase(i);
+ --size;
+}
+
+void
+ClientList::CloseAll()
+{
+ while (!list.empty()) {
+ delete list.front();
+ list.pop_front();
+
+#ifndef NDEBUG
+ --size;
+#endif
+ }
+
+ assert(size == 0);
+}
+
+void
+ClientList::IdleAdd(unsigned flags)
+{
+ assert(flags != 0);
+
+ for (const auto &client : list)
+ client->IdleAdd(flags);
+}
diff --git a/src/client/ClientList.hxx b/src/client/ClientList.hxx
new file mode 100644
index 000000000..35022fbf1
--- /dev/null
+++ b/src/client/ClientList.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_LIST_HXX
+#define MPD_CLIENT_LIST_HXX
+
+#include <list>
+
+class Client;
+
+class ClientList {
+ const unsigned max_size;
+
+ unsigned size;
+ std::list<Client *> list;
+
+public:
+ ClientList(unsigned _max_size)
+ :max_size(_max_size), size(0) {}
+ ~ClientList() {
+ CloseAll();
+ }
+
+ std::list<Client *>::iterator begin() {
+ return list.begin();
+ }
+
+ std::list<Client *>::iterator end() {
+ return list.end();
+ }
+
+ bool IsFull() const {
+ return size >= max_size;
+ }
+
+ void Add(Client &client) {
+ list.push_front(&client);
+ ++size;
+ }
+
+ void Remove(Client &client);
+
+ void CloseAll();
+
+ void IdleAdd(unsigned flags);
+};
+
+#endif
diff --git a/src/client/ClientMessage.cxx b/src/client/ClientMessage.cxx
new file mode 100644
index 000000000..be6d2f007
--- /dev/null
+++ b/src/client/ClientMessage.cxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ClientMessage.hxx"
+#include "util/CharUtil.hxx"
+#include "Compiler.h"
+
+gcc_const
+static bool
+valid_channel_char(const char ch)
+{
+ return IsAlphaNumericASCII(ch) ||
+ ch == '_' || ch == '-' || ch == '.' || ch == ':';
+}
+
+bool
+client_message_valid_channel_name(const char *name)
+{
+ do {
+ if (!valid_channel_char(*name))
+ return false;
+ } while (*++name != 0);
+
+ return true;
+}
diff --git a/src/client/ClientMessage.hxx b/src/client/ClientMessage.hxx
new file mode 100644
index 000000000..3d28e01dc
--- /dev/null
+++ b/src/client/ClientMessage.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_MESSAGE_H
+#define MPD_CLIENT_MESSAGE_H
+
+#include "Compiler.h"
+
+#include <string>
+
+/**
+ * A client-to-client message.
+ */
+class ClientMessage {
+ std::string channel, message;
+
+public:
+ template<typename T, typename U>
+ ClientMessage(T &&_channel, U &&_message)
+ :channel(std::forward<T>(_channel)),
+ message(std::forward<U>(_message)) {}
+
+ const char *GetChannel() const {
+ return channel.c_str();
+ }
+
+ const char *GetMessage() const {
+ return message.c_str();
+ }
+};
+
+gcc_pure
+bool
+client_message_valid_channel_name(const char *name);
+
+#endif
diff --git a/src/client/ClientNew.cxx b/src/client/ClientNew.cxx
new file mode 100644
index 000000000..a080e9ec6
--- /dev/null
+++ b/src/client/ClientNew.cxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "ClientList.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "system/fd_util.h"
+#include "system/Resolver.hxx"
+#include "Permission.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
+static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
+
+Client::Client(EventLoop &_loop, Partition &_partition,
+ int _fd, int _uid, int _num)
+ :FullyBufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size),
+ TimeoutMonitor(_loop),
+ partition(_partition),
+ playlist(partition.playlist), player_control(partition.pc),
+ permission(getDefaultPermissions()),
+ uid(_uid),
+ num(_num),
+ idle_waiting(false), idle_flags(0),
+ num_subscriptions(0)
+{
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+}
+
+void
+client_new(EventLoop &loop, Partition &partition,
+ int fd, const struct sockaddr *sa, size_t sa_length, int uid)
+{
+ static unsigned int next_client_num;
+ const auto remote = sockaddr_to_string(sa, sa_length);
+
+ assert(fd >= 0);
+
+#ifdef HAVE_LIBWRAP
+ if (sa->sa_family != AF_UNIX) {
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ FormatWarning(client_domain,
+ "libwrap refused connection (libwrap=%s) from %s",
+ progname, remote.c_str());
+
+ close_socket(fd);
+ return;
+ }
+ }
+#endif /* HAVE_WRAP */
+
+ ClientList &client_list = *partition.instance.client_list;
+ if (client_list.IsFull()) {
+ LogWarning(client_domain, "Max connections reached");
+ close_socket(fd);
+ return;
+ }
+
+ Client *client = new Client(loop, partition, fd, uid,
+ next_client_num++);
+
+ (void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
+
+ client_list.Add(*client);
+
+ FormatInfo(client_domain, "[%u] opened from %s",
+ client->num, remote.c_str());
+}
+
+void
+Client::Close()
+{
+ partition.instance.client_list->Remove(*this);
+
+ SetExpired();
+
+ FormatInfo(client_domain, "[%u] closed", num);
+ delete this;
+}
diff --git a/src/client/ClientProcess.cxx b/src/client/ClientProcess.cxx
new file mode 100644
index 000000000..96099a91c
--- /dev/null
+++ b/src/client/ClientProcess.cxx
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "protocol/Result.hxx"
+#include "command/AllCommands.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
+#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
+#define CLIENT_LIST_MODE_END "command_list_end"
+
+static CommandResult
+client_process_command_list(Client &client, bool list_ok,
+ std::list<std::string> &&list)
+{
+ CommandResult ret = CommandResult::OK;
+ unsigned num = 0;
+
+ for (auto &&i : list) {
+ char *cmd = &*i.begin();
+
+ FormatDebug(client_domain, "process command \"%s\"", cmd);
+ ret = command_process(client, num++, cmd);
+ FormatDebug(client_domain, "command returned %i", ret);
+ if (ret != CommandResult::OK || client.IsExpired())
+ break;
+ else if (list_ok)
+ client_puts(client, "list_OK\n");
+ }
+
+ return ret;
+}
+
+CommandResult
+client_process_line(Client &client, char *line)
+{
+ CommandResult ret;
+
+ if (strcmp(line, "noidle") == 0) {
+ if (client.idle_waiting) {
+ /* send empty idle response and leave idle mode */
+ client.idle_waiting = false;
+ command_success(client);
+ }
+
+ /* do nothing if the client wasn't idling: the client
+ has already received the full idle response from
+ client_idle_notify(), which he can now evaluate */
+
+ return CommandResult::OK;
+ } else if (client.idle_waiting) {
+ /* during idle mode, clients must not send anything
+ except "noidle" */
+ FormatWarning(client_domain,
+ "[%u] command \"%s\" during idle",
+ client.num, line);
+ return CommandResult::CLOSE;
+ }
+
+ if (client.cmd_list.IsActive()) {
+ if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
+ FormatDebug(client_domain,
+ "[%u] process command list",
+ client.num);
+
+ auto &&cmd_list = client.cmd_list.Commit();
+
+ ret = client_process_command_list(client,
+ client.cmd_list.IsOKMode(),
+ std::move(cmd_list));
+ FormatDebug(client_domain,
+ "[%u] process command "
+ "list returned %i", client.num, ret);
+
+ if (ret == CommandResult::CLOSE ||
+ client.IsExpired())
+ return CommandResult::CLOSE;
+
+ if (ret == CommandResult::OK)
+ command_success(client);
+
+ client.cmd_list.Reset();
+ } else {
+ if (!client.cmd_list.Add(line)) {
+ FormatWarning(client_domain,
+ "[%u] command list size "
+ "is larger than the max (%lu)",
+ client.num,
+ (unsigned long)client_max_command_list_size);
+ return CommandResult::CLOSE;
+ }
+
+ ret = CommandResult::OK;
+ }
+ } else {
+ if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
+ client.cmd_list.Begin(false);
+ ret = CommandResult::OK;
+ } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
+ client.cmd_list.Begin(true);
+ ret = CommandResult::OK;
+ } else {
+ FormatDebug(client_domain,
+ "[%u] process command \"%s\"",
+ client.num, line);
+ ret = command_process(client, 0, line);
+ FormatDebug(client_domain,
+ "[%u] command returned %i",
+ client.num, ret);
+
+ if (ret == CommandResult::CLOSE ||
+ client.IsExpired())
+ return CommandResult::CLOSE;
+
+ if (ret == CommandResult::OK)
+ command_success(client);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/client/ClientRead.cxx b/src/client/ClientRead.cxx
new file mode 100644
index 000000000..19deebd52
--- /dev/null
+++ b/src/client/ClientRead.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Main.hxx"
+#include "event/Loop.hxx"
+#include "util/CharUtil.hxx"
+
+#include <string.h>
+
+BufferedSocket::InputResult
+Client::OnSocketInput(void *data, size_t length)
+{
+ char *p = (char *)data;
+ char *newline = (char *)memchr(p, '\n', length);
+ if (newline == nullptr)
+ return InputResult::MORE;
+
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+
+ BufferedSocket::ConsumeInput(newline + 1 - p);
+
+ /* skip whitespace at the end of the line */
+ while (newline > p && IsWhitespaceOrNull(newline[-1]))
+ --newline;
+
+ /* terminate the string at the end of the line */
+ *newline = 0;
+
+ CommandResult result = client_process_line(*this, p);
+ switch (result) {
+ case CommandResult::OK:
+ case CommandResult::IDLE:
+ case CommandResult::ERROR:
+ break;
+
+ case CommandResult::KILL:
+ Close();
+ main_loop->Break();
+ return InputResult::CLOSED;
+
+ case CommandResult::FINISH:
+ if (Flush())
+ Close();
+ return InputResult::CLOSED;
+
+ case CommandResult::CLOSE:
+ Close();
+ return InputResult::CLOSED;
+ }
+
+ if (IsExpired()) {
+ Close();
+ return InputResult::CLOSED;
+ }
+
+ return InputResult::AGAIN;
+}
diff --git a/src/client/ClientSubscribe.cxx b/src/client/ClientSubscribe.cxx
new file mode 100644
index 000000000..8ea2e363b
--- /dev/null
+++ b/src/client/ClientSubscribe.cxx
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Idle.hxx"
+
+#include <assert.h>
+
+Client::SubscribeResult
+Client::Subscribe(const char *channel)
+{
+ assert(channel != nullptr);
+
+ if (!client_message_valid_channel_name(channel))
+ return Client::SubscribeResult::INVALID;
+
+ if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
+ return Client::SubscribeResult::FULL;
+
+ auto r = subscriptions.insert(channel);
+ if (!r.second)
+ return Client::SubscribeResult::ALREADY;
+
+ ++num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ return Client::SubscribeResult::OK;
+}
+
+bool
+Client::Unsubscribe(const char *channel)
+{
+ const auto i = subscriptions.find(channel);
+ if (i == subscriptions.end())
+ return false;
+
+ assert(num_subscriptions > 0);
+
+ subscriptions.erase(i);
+ --num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ assert((num_subscriptions == 0) ==
+ subscriptions.empty());
+
+ return true;
+}
+
+void
+Client::UnsubscribeAll()
+{
+ subscriptions.clear();
+ num_subscriptions = 0;
+}
+
+bool
+Client::PushMessage(const ClientMessage &msg)
+{
+ if (messages.size() >= CLIENT_MAX_MESSAGES ||
+ !IsSubscribed(msg.GetChannel()))
+ return false;
+
+ if (messages.empty())
+ IdleAdd(IDLE_MESSAGE);
+
+ messages.push_back(msg);
+ return true;
+}
diff --git a/src/client/ClientWrite.cxx b/src/client/ClientWrite.cxx
new file mode 100644
index 000000000..b5d172a8d
--- /dev/null
+++ b/src/client/ClientWrite.cxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "util/FormatString.hxx"
+
+#include <string.h>
+
+/**
+ * Write a block of data to the client.
+ */
+static void
+client_write(Client &client, const char *data, size_t length)
+{
+ /* if the client is going to be closed, do nothing */
+ if (client.IsExpired() || length == 0)
+ return;
+
+ client.Write(data, length);
+}
+
+void
+client_puts(Client &client, const char *s)
+{
+ client_write(client, s, strlen(s));
+}
+
+void
+client_vprintf(Client &client, const char *fmt, va_list args)
+{
+ char *p = FormatNewV(fmt, args);
+ client_write(client, p, strlen(p));
+ delete[] p;
+}
+
+void
+client_printf(Client &client, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ client_vprintf(client, fmt, args);
+ va_end(args);
+}