>From 000c5e7faaa233e0adf1ee550237bc79b706a6aa Mon Sep 17 00:00:00 2001
From: Yclept Nemo <pscjtwjdjtAhnbjm/dpn>
Date: Mon, 18 May 2015 14:07:13 -0400
Subject: [PATCH 2/2] smbclient auth support

There is now a backend block which in turns provides several password
configuration backends:

Configuration file:
    backend {
        name                "smbclient"
        password_backend    "config"
        username            "ExampleName"
        password            "ExamplePassword"
    }
Libsecret (keyring):
    backend {
        name                "smbclient"
        password_backend    "libsecret"
        username            "ExampleName"
    }

Some values are optional:
    backend {
        name        "smbclient"
        workgroup   "FANCYWORKGROUP"
        username    "ExampleName"
    }

Defaults:
    workgroup           "WORKGROUP"
    username            ""
    password            ""
    password_backend    "config"

Use scripts/mpd.secret.py to store a secret (password) and optionally
create a new collection (keyring).
---
 INSTALL                        |   3 +
 Makefile.am                    |  18 ++++-
 configure.ac                   |   5 ++
 doc/user.xml                   | 133 +++++++++++++++++++++++++++++++
 scripts/mpd.secret.py          | 111 ++++++++++++++++++++++++++
 src/config/ConfigOption.hxx    |   1 +
 src/config/ConfigTemplates.cxx |   1 +
 src/lib/smbclient/Init.cxx     | 176
++++++++++++++++++++++++++++++++++++-----
 src/lib/smbclient/Init.hxx     |  89 ++++++++++++++++++++-
 9 files changed, 513 insertions(+), 24 deletions(-)
 create mode 100755 scripts/mpd.secret.py

diff --git a/INSTALL b/INSTALL
index 792bbd7..8de660d 100644
--- a/INSTALL
+++ b/INSTALL
@@ -138,6 +138,9 @@ For playing audio CDs.
 libsystemd-daemon - http://freedesktop.org/wiki/Software/systemd/
 For systemd activation.

+libsecret - https://wiki.gnome.org/Projects/Libsecret
+For smbclient libsecret authentication.
+

 pkg-config
 ----------
diff --git a/Makefile.am b/Makefile.am
index c6bcfa1..fc8cc51 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -611,12 +611,14 @@ libstorage_a_SOURCES = \

 libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
     $(NFS_CFLAGS) \
-    $(SMBCLIENT_CFLAGS)
+    $(SMBCLIENT_CFLAGS) \
+    $(LIBSECRET_CFLAGS)

 STORAGE_LIBS = \
     libstorage.a \
     $(NFS_LIBS) \
-    $(SMBCLIENT_LIBS)
+    $(SMBCLIENT_LIBS) \
+    $(LIBSECRET_LIBS)

 if ENABLE_SMBCLIENT
 libstorage_a_SOURCES += \
@@ -651,7 +653,8 @@ libneighbor_a_SOURCES = \
     src/neighbor/NeighborPlugin.hxx

 libneighbor_a_CPPFLAGS = $(AM_CPPFLAGS) \
-    $(SMBCLIENT_CFLAGS)
+    $(SMBCLIENT_CFLAGS) \
+    $(LIBSECRET_CFLAGS)

 if ENABLE_SMBCLIENT
 libneighbor_a_SOURCES += \
@@ -661,6 +664,7 @@ endif

 NEIGHBOR_LIBS = \
     $(SMBCLIENT_LIBS) \
+    $(LIBSECRET_LIBS) \
     libneighbor.a

 if ENABLE_UPNP
@@ -1165,6 +1169,7 @@ libinput_a_SOURCES = \
 libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
     $(CURL_CFLAGS) \
     $(SMBCLIENT_CFLAGS) \
+    $(LIBSECRET_CFLAGS) \
     $(NFS_CFLAGS) \
     $(CDIO_PARANOIA_CFLAGS) \
     $(FFMPEG_CFLAGS) \
@@ -1174,6 +1179,7 @@ INPUT_LIBS = \
     libinput.a \
     $(CURL_LIBS) \
     $(SMBCLIENT_LIBS) \
+    $(LIBSECRET_LIBS) \
     $(NFS_LIBS) \
     $(CDIO_PARANOIA_LIBS) \
     $(FFMPEG_LIBS2) \
@@ -2194,6 +2200,11 @@ endif
 man_MANS = doc/mpd.1 doc/mpd.conf.5
 doc_DATA = AUTHORS COPYING NEWS README doc/mpdconf.example

+if ENABLE_LIBSECRET
+docscriptsdir = $(docdir)/scripts
+docscripts_SCRIPTS = scripts/mpd.secret.py
+endif
+
 DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml

 if ENABLE_DOCUMENTATION
@@ -2251,6 +2262,7 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
     test/test_archive_zzip.sh \
     $(wildcard scripts/*.sh) \
     $(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
+    scripts/mpd.secret.py
     systemd/mpd.socket \
     android/AndroidManifest.xml \
     android/build.py \
diff --git a/configure.ac b/configure.ac
index dfd208a..82f814d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -709,6 +709,10 @@ dnl ----------------------------------- CURL
----------------------------------
 MPD_ENABLE_AUTO_PKG(curl, CURL, [libcurl >= 7.18],
     [libcurl HTTP streaming], [libcurl not found])

+dnl --------------------------------- LIBSECRET
-------------------------------
+MPD_ENABLE_AUTO_PKG(libsecret, LIBSECRET, [libsecret-1],
+    [libsecret password support], [libsecret not found])
+
 dnl ----------------------------------- smbclient
-----------------------------
 MPD_ENABLE_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
     [smbclient], [smbc_init], [-lsmbclient], [],
@@ -1421,6 +1425,7 @@ results(soxr, [libsoxr])
 results(libmpdclient, [libmpdclient])
 results(inotify, [inotify])
 results(sqlite, [SQLite])
+results(libsecret, [LIBSECRET])

 printf '\nMetadata support:\n\t'
 results(id3,[ID3])
diff --git a/doc/user.xml b/doc/user.xml
index 7a60b04..1838485 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -1025,6 +1025,64 @@ database {
         plugin).
       </para>
     </section>
+
+    <section id="smbclient_auth">
+      <title>Smbclient Authentication</title>
+
+      <para>
+        <application>MPD</application> can can be configured to
+        authenticate itself to SMB/CIFS servers. This section
+        illustrates several setups.
+      </para>
+
+      <para>
+        Both of the following configurations use the
+        <filename>mpd.conf</filename> configuration file to store the
+        authentication information. Notice some values have been omitted
from
+        the second section. See <link
+
linkend="smbclient_backend"><varname>smbclient</varname></link> for
+        default values:
+      </para>
+
+      <programlisting>
+backend {
+    name                "smbclient"
+    password_backend    "config"
+    username            "ExampleName"
+    password            "ExamplePassword"
+}
+
+backend {
+    name        "smbclient"
+    workgroup   "FANCYWORKGROUP"
+    username    "ExampleName"
+}
+      </programlisting>
+
+      <para>
+        This configuration queries the "Secret Service" via DBus. This can
+        obtain the password from multiple implementations, including
+        <application>gnome-keyring</application> and
+        <application>ksecretservice</application>.
+      </para>
+      <programlisting>
+backend {
+    name                "smbclient"
+    password_backend    "libsecret"
+    username            "ExampleName"
+}
+      </programlisting>
+
+      <para>
+        The provided <filename>python3</filename> script,
+        <filename>mpd.secret.py</filename>, aids in creating both the
+        collection (keyring) and secret (password). It is only installed if
+        <application>MPD</application> has been compiled with
+        <filename>libsecret</filename>, and requires the
+        <filename>gobject-introspection</filename> bindings to
+        <filename>libsecret</filename>.
+      </para>
+    </section>
   </chapter>

   <chapter id="use">
@@ -3427,4 +3485,79 @@ buffer_size: 16384</programlisting>
       </section>
     </section>
   </chapter>
+
+  <chapter id="backend_reference">
+    <title>Backend reference</title>
+
+    <para>
+      The <varname>backend</varname> block configures various backends
+      through the <varname>name</varname> setting. At the moment, only
+      <parameter>smbclient</parameter> can be configured in this
+      manner.
+    </para>
+
+    <section id="smbclient_backend">
+        <title><parameter>smbclient</parameter></title>
+
+      <para>
+        The <varname>smbclient</varname> backend supports
+        authentication if configured.
+      </para>
+
+      <informaltable>
+        <tgroup cols="2">
+          <thead>
+            <row>
+              <entry>Setting</entry>
+              <entry>Description</entry>
+            </row>
+          </thead>
+          <tbody>
+            <row>
+              <entry>
+                <varname>password_backend</varname>
+                <parameter>config|libsecret</parameter>
+              </entry>
+              <entry>
+                Source from which to read the password. When set to
+                <parameter>config</parameter>, the password will be
+                read from <varname>password</varname>. When set to
+                <parameter>libsecret</parameter>, the "Secret Service"
+                will be queried over DBus. Defaults to
+                <parameter>config</parameter>.
+              </entry>
+            </row>
+            <row>
+              <entry>
+                <varname>username</varname>
+                <parameter>NAME</parameter>
+              </entry>
+              <entry>
+                Username with which to authenticate. Defaults to "".
+              </entry>
+            </row>
+            <row>
+              <entry>
+                <varname>password</varname>
+                <parameter>WORD</parameter>
+              </entry>
+              <entry>
+                Password with which to authenticate. Defaults to "".
+              </entry>
+            </row>
+            <row>
+              <entry>
+                <varname>workgroup</varname>
+                <parameter>GROUP</parameter>
+              </entry>
+              <entry>
+                Workgroup with which to authenticate. Defaults to
+                "WORKGROUP".
+              </entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </section>
+  </chapter>
 </book>
diff --git a/scripts/mpd.secret.py b/scripts/mpd.secret.py
new file mode 100755
index 0000000..30cb497
--- /dev/null
+++ b/scripts/mpd.secret.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2003-2015 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.
+#
+
+
+import getpass
+from gi.repository import Secret
+
+
+mpd_schema_attributes =\
+    { "server"    : Secret.SchemaAttributeType.STRING
+    , "share"     : Secret.SchemaAttributeType.STRING
+    , "workgroup" : Secret.SchemaAttributeType.STRING
+    , "username"  : Secret.SchemaAttributeType.STRING
+    }
+
+mpd_schema = Secret.Schema.new\
+    ( "org.mpd.smbclient.password"
+    , Secret.SchemaFlags.NONE
+    , mpd_schema_attributes
+    )
+
+mpd_default_input_data =\
+    { "collection"  : { "default": "MusicPD" }
+    , "password"    : { "secret": True }
+    , "server"      : { }
+    , "share"       : { }
+    , "workgroup"   : { "default": "WORKGROUP" }
+    , "username"    : { }
+    }
+
+
+def mpd_prompt(prompt, default=None, secret=False):
+    if default is None:
+        prompt = "{}: ".format(prompt)
+    else:
+        prompt = "{} [{}]: ".format(prompt, default)
+    string = ""
+    while not string:
+        if secret:
+            string = getpass.getpass(prompt)
+        else:
+            string = input(prompt)
+        if not string and default is not None:
+            string = default
+            break
+    return string
+
+def mpd_main():
+    input_data = mpd_input(mpd_default_input_data)
+    mpd_collection_ensure(input_data["collection"])
+    mpd_password_store(input_data)
+
+def mpd_input(input_data_arguments):
+    input_data = {}
+    print("<Enter> for default values (in parentheses)")
+    for attribute, arguments in sorted(input_data_arguments.items()):
+        input_data[attribute] = mpd_prompt(attribute, **arguments)
+    return input_data
+
+def mpd_collection_ensure(label):
+    service = Secret.Service.get_sync(Secret.ServiceFlags.LOAD_COLLECTIONS)
+    collections = Secret.Service.get_collections(service)
+    labels = [collection.get_label() for collection in collections]
+
+    if label not in labels:
+        Secret.Collection.create_sync\
+            ( service
+            , label
+            , None
+            , Secret.CollectionCreateFlags.COLLECTION_CREATE_NONE
+            , None
+            )
+
+def mpd_input_to_attributes(d):
+    return {k:v for k,v in d.items() if k in mpd_schema_attributes}
+
+def mpd_input_get_collection_path(input_data):
+    return
"/org/freedesktop/secrets/collection/{}".format(input_data["collection"])
+
+def mpd_input_get_password_label(input_data):
+    return "Smbclient Password: {}".format(input_data["server"])
+
+def mpd_password_store(input_data):
+    Secret.password_store_sync\
+        ( mpd_schema
+        , mpd_input_to_attributes(input_data)
+        , mpd_input_get_collection_path(input_data)
+        , mpd_input_get_password_label(input_data)
+        , input_data["password"]
+        , None
+        )
+
+if __name__ == "__main__":
+    mpd_main()
diff --git a/src/config/ConfigOption.hxx b/src/config/ConfigOption.hxx
index 5acd6fd..8d11a3f 100644
--- a/src/config/ConfigOption.hxx
+++ b/src/config/ConfigOption.hxx
@@ -90,6 +90,7 @@ enum class ConfigBlockOption {
     AUDIO_FILTER,
     DATABASE,
     NEIGHBORS,
+    BACKEND,
     MAX
 };

diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx
index 6fbf025..d283e29 100644
--- a/src/config/ConfigTemplates.cxx
+++ b/src/config/ConfigTemplates.cxx
@@ -90,6 +90,7 @@ const ConfigTemplate config_block_templates[] = {
     { "filter", true },
     { "database", false },
     { "neighbors", true },
+    { "backend", "true" },
 };

 static constexpr unsigned n_config_block_templates =
diff --git a/src/lib/smbclient/Init.cxx b/src/lib/smbclient/Init.cxx
index 999e60f..cf5299b 100644
--- a/src/lib/smbclient/Init.cxx
+++ b/src/lib/smbclient/Init.cxx
@@ -22,34 +22,172 @@
 #include "Mutex.hxx"
 #include "thread/Mutex.hxx"
 #include "util/Error.hxx"
+#include "util/Alloc.hxx"
+#include "config/ConfigGlobal.hxx"

 #include <libsmbclient.h>

+#ifdef ENABLE_LIBSECRET
+#include <libsecret/secret.h>
+#endif
+
 #include <string.h>

-static void
-mpd_smbc_get_auth_data(gcc_unused const char *srv,
-               gcc_unused const char *shr,
-               char *wg, gcc_unused int wglen,
-               char *un, gcc_unused int unlen,
-               char *pw, gcc_unused int pwlen)
-{
-    // TODO: implement
-    strcpy(wg, "WORKGROUP");
-    strcpy(un, "");
-    strcpy(pw, "");
+
+SmbAuthData smb_auth_data;
+SmbState smb_state = { false };
+
+
+SmbAuthData::~SmbAuthData()
+{   free(password);
+}
+
+void
+SmbAuthData::set_password(const char* pw)
+{   free(password);
+    if (pw == nullptr)
+        password = xstrdup("");
+    else
+        password = xstrdup(pw);
+}
+
+void
+SmbAuthData::initialize(const char* svr, const char* shr)
+{   if (initialized)
+        return;
+    initialized = true;
+    server = svr;
+    share = shr;
+
+    const ConfigBlock* block =
config_get_block(ConfigBlockOption::BACKEND);
+
+    if (block == nullptr)
+        return;
+
+    const char* backend_name = block->GetBlockValue("name", "");
+
+    if (strcmp(backend_name, "smbclient") != 0)
+        return;
+
+    workgroup = block->GetBlockValue("workgroup", "WORKGROUP");
+    username = block->GetBlockValue("username", "");
+
+    initialize_password(block);
+}
+
+void
+SmbAuthData::initialize_password
+    ( const ConfigBlock* block
+    )
+{   const char* password_backend =
block->GetBlockValue("password_backend", "config");
+
+    if (strcmp(password_backend, "config") == 0)
+        initialize_password_config(block);
+#ifdef ENABLE_LIBSECRET
+    else if (strcmp(password_backend, "libsecret") == 0)
+        initialize_password_secret();
+#endif
+}
+
+void
+SmbAuthData::initialize_password_config
+    ( const ConfigBlock* block
+    )
+{   set_password(block->GetBlockValue("password", ""));
+}
+
+#ifdef ENABLE_LIBSECRET
+void
+SmbAuthData::initialize_password_secret()
+{   gchar* pw = nullptr;
+    GError *error = nullptr;
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+    const SecretSchema mpd_schema =
+        { "org.mpd.smbclient.password"
+        , SECRET_SCHEMA_NONE
+        , { { "server"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "share"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "workgroup"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "username"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "NULL"
+            , (SecretSchemaAttributeType)0
+            }
+          }
+        };
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
+    pw = secret_password_lookup_sync
+        ( &mpd_schema , nullptr , &error
+        , "server", server
+        , "share", share
+        , "workgroup", workgroup
+        , "username", username
+        , nullptr
+        );
+
+    if (error != nullptr)
+        g_error_free(error);
+
+    set_password(pw);
+    secret_password_free(pw);
+}
+#endif
+
+
+void
+mpd_smbc_set_auth_data
+    ( char* destination
+    , const char* source
+    , size_t num
+    )
+{   size_t source_len = strlen(source) + 1;
+    memcpy(destination, source, std::min(source_len, num));
+    if (source_len > num)
+        destination[num] = '\0';
+}
+
+void
+mpd_smbc_get_auth_data
+    ( const char* server
+    , const char* share
+    , char* workgroup,  int workgrouplen
+    , char* username,   int usernamelen
+    , char* password,   int passwordlen
+    )
+{   smb_auth_data.initialize(server, share);
+
+    mpd_smbc_set_auth_data(workgroup, smb_auth_data.workgroup,
workgrouplen);
+    mpd_smbc_set_auth_data(username, smb_auth_data.username, usernamelen);
+    mpd_smbc_set_auth_data(password, smb_auth_data.password, passwordlen);
 }

 bool
 SmbclientInit(Error &error)
-{
-    const ScopeLock protect(smbclient_mutex);
+{   const ScopeLock protect(smbclient_mutex);

-    constexpr int debug = 0;
-    if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
-        error.SetErrno("smbc_init() failed");
-        return false;
-    }
+    if (!smb_state.initialized) {
+        constexpr int debug = 0;
+        if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
+            error.SetErrno("smbc_init() failed");
+            return false;
+        }
+        else {
+            smb_state.initialized = false;
+        }
+    }

-    return true;
+    return true;
 }
diff --git a/src/lib/smbclient/Init.hxx b/src/lib/smbclient/Init.hxx
index 1ccaec0..f2bf2dc 100644
--- a/src/lib/smbclient/Init.hxx
+++ b/src/lib/smbclient/Init.hxx
@@ -22,12 +22,97 @@

 #include "check.h"

+#include "config/Block.hxx"
+#include "util/Alloc.hxx"
+
+
 class Error;

-/**
+struct SmbAuthData
+    { const char* server;
+      const char* share;
+      const char* workgroup;
+      const char* username;
+      char* password;
+      bool initialized;
+
+      SmbAuthData &operator=
+        ( const SmbAuthData &
+        ) = delete;
+
+      SmbAuthData
+        ()
+        : server("")
+        , share("")
+        , workgroup("WORKGROUP")
+        , username("")
+        , password(xstrdup(""))
+        , initialized(false)
+        {}
+
+      ~SmbAuthData
+        ();
+
+      void
+      initialize
+        ( const char*
+        , const char*
+        );
+
+    private:
+      void
+      set_password
+        ( const char*
+        );
+
+      void
+      initialize_password
+        ( const ConfigBlock*
+        );
+
+      void
+      initialize_password_config
+        ( const ConfigBlock*
+        );
+
+#ifdef ENABLE_LIBSECRET
+      void
+      initialize_password_secret
+        ();
+#endif
+    };
+
+struct SmbState
+    { bool initialized;
+    };
+
+
+extern SmbAuthData smb_auth_data;
+extern SmbState smb_state;
+
+
+void
+mpd_smbc_set_auth_data
+    ( char*
+    , const char*
+    , size_t
+    );
+
+void
+mpd_smbc_get_auth_data
+    ( const char*
+    , const char*
+    , char*, int
+    , char*, int
+    , char*, int
+    );
+
+/*
  * Initialize libsmbclient.
  */
 bool
-SmbclientInit(Error &error);
+SmbclientInit
+    (Error&
+    );

 #endif
-- 
2.1.4
From 000c5e7faaa233e0adf1ee550237bc79b706a6aa Mon Sep 17 00:00:00 2001
From: Yclept Nemo <pscjtwjdjtAhnbjm/dpn>
Date: Mon, 18 May 2015 14:07:13 -0400
Subject: [PATCH 2/2] smbclient auth support

There is now a backend block which in turns provides several password
configuration backends:

Configuration file:
    backend {
        name                "smbclient"
        password_backend    "config"
        username            "ExampleName"
        password            "ExamplePassword"
    }
Libsecret (keyring):
    backend {
        name                "smbclient"
        password_backend    "libsecret"
        username            "ExampleName"
    }

Some values are optional:
    backend {
        name        "smbclient"
        workgroup   "FANCYWORKGROUP"
        username    "ExampleName"
    }

Defaults:
    workgroup           "WORKGROUP"
    username            ""
    password            ""
    password_backend    "config"

Use scripts/mpd.secret.py to store a secret (password) and optionally
create a new collection (keyring).
---
 INSTALL                        |   3 +
 Makefile.am                    |  18 ++++-
 configure.ac                   |   5 ++
 doc/user.xml                   | 133 +++++++++++++++++++++++++++++++
 scripts/mpd.secret.py          | 111 ++++++++++++++++++++++++++
 src/config/ConfigOption.hxx    |   1 +
 src/config/ConfigTemplates.cxx |   1 +
 src/lib/smbclient/Init.cxx     | 176 ++++++++++++++++++++++++++++++++++++-----
 src/lib/smbclient/Init.hxx     |  89 ++++++++++++++++++++-
 9 files changed, 513 insertions(+), 24 deletions(-)
 create mode 100755 scripts/mpd.secret.py

diff --git a/INSTALL b/INSTALL
index 792bbd7..8de660d 100644
--- a/INSTALL
+++ b/INSTALL
@@ -138,6 +138,9 @@ For playing audio CDs.
 libsystemd-daemon - http://freedesktop.org/wiki/Software/systemd/
 For systemd activation.
 
+libsecret - https://wiki.gnome.org/Projects/Libsecret
+For smbclient libsecret authentication.
+
 
 pkg-config
 ----------
diff --git a/Makefile.am b/Makefile.am
index c6bcfa1..fc8cc51 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -611,12 +611,14 @@ libstorage_a_SOURCES = \
 
 libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
 	$(NFS_CFLAGS) \
-	$(SMBCLIENT_CFLAGS)
+	$(SMBCLIENT_CFLAGS) \
+	$(LIBSECRET_CFLAGS)
 
 STORAGE_LIBS = \
 	libstorage.a \
 	$(NFS_LIBS) \
-	$(SMBCLIENT_LIBS)
+	$(SMBCLIENT_LIBS) \
+	$(LIBSECRET_LIBS)
 
 if ENABLE_SMBCLIENT
 libstorage_a_SOURCES += \
@@ -651,7 +653,8 @@ libneighbor_a_SOURCES = \
 	src/neighbor/NeighborPlugin.hxx
 
 libneighbor_a_CPPFLAGS = $(AM_CPPFLAGS) \
-	$(SMBCLIENT_CFLAGS)
+	$(SMBCLIENT_CFLAGS) \
+	$(LIBSECRET_CFLAGS)
 
 if ENABLE_SMBCLIENT
 libneighbor_a_SOURCES += \
@@ -661,6 +664,7 @@ endif
 
 NEIGHBOR_LIBS = \
 	$(SMBCLIENT_LIBS) \
+	$(LIBSECRET_LIBS) \
 	libneighbor.a
 
 if ENABLE_UPNP
@@ -1165,6 +1169,7 @@ libinput_a_SOURCES = \
 libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
 	$(CURL_CFLAGS) \
 	$(SMBCLIENT_CFLAGS) \
+	$(LIBSECRET_CFLAGS) \
 	$(NFS_CFLAGS) \
 	$(CDIO_PARANOIA_CFLAGS) \
 	$(FFMPEG_CFLAGS) \
@@ -1174,6 +1179,7 @@ INPUT_LIBS = \
 	libinput.a \
 	$(CURL_LIBS) \
 	$(SMBCLIENT_LIBS) \
+	$(LIBSECRET_LIBS) \
 	$(NFS_LIBS) \
 	$(CDIO_PARANOIA_LIBS) \
 	$(FFMPEG_LIBS2) \
@@ -2194,6 +2200,11 @@ endif
 man_MANS = doc/mpd.1 doc/mpd.conf.5
 doc_DATA = AUTHORS COPYING NEWS README doc/mpdconf.example
 
+if ENABLE_LIBSECRET
+docscriptsdir = $(docdir)/scripts
+docscripts_SCRIPTS = scripts/mpd.secret.py
+endif
+
 DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml
 
 if ENABLE_DOCUMENTATION
@@ -2251,6 +2262,7 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
 	test/test_archive_zzip.sh \
 	$(wildcard scripts/*.sh) \
 	$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
+	scripts/mpd.secret.py
 	systemd/mpd.socket \
 	android/AndroidManifest.xml \
 	android/build.py \
diff --git a/configure.ac b/configure.ac
index dfd208a..82f814d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -709,6 +709,10 @@ dnl ----------------------------------- CURL ----------------------------------
 MPD_ENABLE_AUTO_PKG(curl, CURL, [libcurl >= 7.18],
 	[libcurl HTTP streaming], [libcurl not found])
 
+dnl --------------------------------- LIBSECRET -------------------------------
+MPD_ENABLE_AUTO_PKG(libsecret, LIBSECRET, [libsecret-1],
+	[libsecret password support], [libsecret not found])
+
 dnl ----------------------------------- smbclient -----------------------------
 MPD_ENABLE_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
 	[smbclient], [smbc_init], [-lsmbclient], [],
@@ -1421,6 +1425,7 @@ results(soxr, [libsoxr])
 results(libmpdclient, [libmpdclient])
 results(inotify, [inotify])
 results(sqlite, [SQLite])
+results(libsecret, [LIBSECRET])
 
 printf '\nMetadata support:\n\t'
 results(id3,[ID3])
diff --git a/doc/user.xml b/doc/user.xml
index 7a60b04..1838485 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -1025,6 +1025,64 @@ database {
         plugin).
       </para>
     </section>
+
+    <section id="smbclient_auth">
+      <title>Smbclient Authentication</title>
+
+      <para>
+        <application>MPD</application> can can be configured to
+        authenticate itself to SMB/CIFS servers. This section
+        illustrates several setups.
+      </para>
+
+      <para>
+        Both of the following configurations use the
+        <filename>mpd.conf</filename> configuration file to store the
+        authentication information. Notice some values have been omitted from
+        the second section. See <link
+            linkend="smbclient_backend"><varname>smbclient</varname></link> for
+        default values:
+      </para>
+
+      <programlisting>
+backend {
+    name                "smbclient"
+    password_backend    "config"
+    username            "ExampleName"
+    password            "ExamplePassword"
+}
+
+backend {
+    name        "smbclient"
+    workgroup   "FANCYWORKGROUP"
+    username    "ExampleName"
+}
+      </programlisting>
+
+      <para>
+        This configuration queries the "Secret Service" via DBus. This can
+        obtain the password from multiple implementations, including
+        <application>gnome-keyring</application> and
+        <application>ksecretservice</application>.
+      </para>
+      <programlisting>
+backend {
+    name                "smbclient"
+    password_backend    "libsecret"
+    username            "ExampleName"
+}
+      </programlisting>
+
+      <para>
+        The provided <filename>python3</filename> script,
+        <filename>mpd.secret.py</filename>, aids in creating both the
+        collection (keyring) and secret (password). It is only installed if
+        <application>MPD</application> has been compiled with
+        <filename>libsecret</filename>, and requires the
+        <filename>gobject-introspection</filename> bindings to
+        <filename>libsecret</filename>.
+      </para>
+    </section>
   </chapter>
 
   <chapter id="use">
@@ -3427,4 +3485,79 @@ buffer_size: 16384</programlisting>
       </section>
     </section>
   </chapter>
+
+  <chapter id="backend_reference">
+    <title>Backend reference</title>
+
+    <para>
+      The <varname>backend</varname> block configures various backends
+      through the <varname>name</varname> setting. At the moment, only
+      <parameter>smbclient</parameter> can be configured in this
+      manner.
+    </para>
+
+    <section id="smbclient_backend">
+        <title><parameter>smbclient</parameter></title>
+
+      <para>
+        The <varname>smbclient</varname> backend supports
+        authentication if configured.
+      </para>
+
+      <informaltable>
+        <tgroup cols="2">
+          <thead>
+            <row>
+              <entry>Setting</entry>
+              <entry>Description</entry>
+            </row>
+          </thead>
+          <tbody>
+            <row>
+              <entry>
+                <varname>password_backend</varname>
+                <parameter>config|libsecret</parameter>
+              </entry>
+              <entry>
+                Source from which to read the password. When set to
+                <parameter>config</parameter>, the password will be
+                read from <varname>password</varname>. When set to
+                <parameter>libsecret</parameter>, the "Secret Service"
+                will be queried over DBus. Defaults to
+                <parameter>config</parameter>.
+              </entry>
+            </row>
+            <row>
+              <entry>
+                <varname>username</varname>
+                <parameter>NAME</parameter>
+              </entry>
+              <entry>
+                Username with which to authenticate. Defaults to "".
+              </entry>
+            </row>
+            <row>
+              <entry>
+                <varname>password</varname>
+                <parameter>WORD</parameter>
+              </entry>
+              <entry>
+                Password with which to authenticate. Defaults to "".
+              </entry>
+            </row>
+            <row>
+              <entry>
+                <varname>workgroup</varname>
+                <parameter>GROUP</parameter>
+              </entry>
+              <entry>
+                Workgroup with which to authenticate. Defaults to
+                "WORKGROUP".
+              </entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </section>
+  </chapter>
 </book>
diff --git a/scripts/mpd.secret.py b/scripts/mpd.secret.py
new file mode 100755
index 0000000..30cb497
--- /dev/null
+++ b/scripts/mpd.secret.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2003-2015 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.
+#
+
+
+import getpass
+from gi.repository import Secret
+
+
+mpd_schema_attributes =\
+    { "server"    : Secret.SchemaAttributeType.STRING
+    , "share"     : Secret.SchemaAttributeType.STRING
+    , "workgroup" : Secret.SchemaAttributeType.STRING
+    , "username"  : Secret.SchemaAttributeType.STRING
+    }
+
+mpd_schema = Secret.Schema.new\
+    ( "org.mpd.smbclient.password"
+    , Secret.SchemaFlags.NONE
+    , mpd_schema_attributes
+    )
+
+mpd_default_input_data =\
+    { "collection"  : { "default": "MusicPD" }
+    , "password"    : { "secret": True }
+    , "server"      : { }
+    , "share"       : { }
+    , "workgroup"   : { "default": "WORKGROUP" }
+    , "username"    : { }
+    }
+
+
+def mpd_prompt(prompt, default=None, secret=False):
+    if default is None:
+        prompt = "{}: ".format(prompt) 
+    else:
+        prompt = "{} [{}]: ".format(prompt, default)
+    string = ""
+    while not string:
+        if secret:
+            string = getpass.getpass(prompt)
+        else:
+            string = input(prompt)
+        if not string and default is not None:
+            string = default
+            break
+    return string
+
+def mpd_main():
+    input_data = mpd_input(mpd_default_input_data)
+    mpd_collection_ensure(input_data["collection"])
+    mpd_password_store(input_data)
+
+def mpd_input(input_data_arguments):
+    input_data = {}
+    print("<Enter> for default values (in parentheses)")
+    for attribute, arguments in sorted(input_data_arguments.items()):
+        input_data[attribute] = mpd_prompt(attribute, **arguments)
+    return input_data
+
+def mpd_collection_ensure(label):
+    service = Secret.Service.get_sync(Secret.ServiceFlags.LOAD_COLLECTIONS)
+    collections = Secret.Service.get_collections(service)
+    labels = [collection.get_label() for collection in collections]
+
+    if label not in labels:
+        Secret.Collection.create_sync\
+            ( service
+            , label
+            , None
+            , Secret.CollectionCreateFlags.COLLECTION_CREATE_NONE
+            , None
+            )
+
+def mpd_input_to_attributes(d):
+    return {k:v for k,v in d.items() if k in mpd_schema_attributes}
+
+def mpd_input_get_collection_path(input_data):
+    return "/org/freedesktop/secrets/collection/{}".format(input_data["collection"])
+
+def mpd_input_get_password_label(input_data):
+    return "Smbclient Password: {}".format(input_data["server"])
+
+def mpd_password_store(input_data):
+    Secret.password_store_sync\
+        ( mpd_schema
+        , mpd_input_to_attributes(input_data)
+        , mpd_input_get_collection_path(input_data)
+        , mpd_input_get_password_label(input_data)
+        , input_data["password"]
+        , None
+        )
+
+if __name__ == "__main__":
+    mpd_main()
diff --git a/src/config/ConfigOption.hxx b/src/config/ConfigOption.hxx
index 5acd6fd..8d11a3f 100644
--- a/src/config/ConfigOption.hxx
+++ b/src/config/ConfigOption.hxx
@@ -90,6 +90,7 @@ enum class ConfigBlockOption {
 	AUDIO_FILTER,
 	DATABASE,
 	NEIGHBORS,
+    BACKEND,
 	MAX
 };
 
diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx
index 6fbf025..d283e29 100644
--- a/src/config/ConfigTemplates.cxx
+++ b/src/config/ConfigTemplates.cxx
@@ -90,6 +90,7 @@ const ConfigTemplate config_block_templates[] = {
 	{ "filter", true },
 	{ "database", false },
 	{ "neighbors", true },
+    { "backend", "true" },
 };
 
 static constexpr unsigned n_config_block_templates =
diff --git a/src/lib/smbclient/Init.cxx b/src/lib/smbclient/Init.cxx
index 999e60f..cf5299b 100644
--- a/src/lib/smbclient/Init.cxx
+++ b/src/lib/smbclient/Init.cxx
@@ -22,34 +22,172 @@
 #include "Mutex.hxx"
 #include "thread/Mutex.hxx"
 #include "util/Error.hxx"
+#include "util/Alloc.hxx"
+#include "config/ConfigGlobal.hxx"
 
 #include <libsmbclient.h>
 
+#ifdef ENABLE_LIBSECRET
+#include <libsecret/secret.h>
+#endif
+
 #include <string.h>
 
-static void
-mpd_smbc_get_auth_data(gcc_unused const char *srv,
-		       gcc_unused const char *shr,
-		       char *wg, gcc_unused int wglen,
-		       char *un, gcc_unused int unlen,
-		       char *pw, gcc_unused int pwlen)
-{
-	// TODO: implement
-	strcpy(wg, "WORKGROUP");
-	strcpy(un, "");
-	strcpy(pw, "");
+
+SmbAuthData smb_auth_data;
+SmbState smb_state = { false };
+
+
+SmbAuthData::~SmbAuthData()
+{   free(password);
+}
+
+void
+SmbAuthData::set_password(const char* pw)
+{   free(password);
+    if (pw == nullptr)
+        password = xstrdup("");
+    else
+        password = xstrdup(pw);
+}
+
+void
+SmbAuthData::initialize(const char* svr, const char* shr)
+{   if (initialized)
+        return;
+    initialized = true;
+    server = svr;
+    share = shr;
+
+    const ConfigBlock* block = config_get_block(ConfigBlockOption::BACKEND);
+
+    if (block == nullptr)
+        return;
+
+    const char* backend_name = block->GetBlockValue("name", "");
+
+    if (strcmp(backend_name, "smbclient") != 0)
+        return;
+
+    workgroup = block->GetBlockValue("workgroup", "WORKGROUP");
+    username = block->GetBlockValue("username", "");
+
+    initialize_password(block);
+}
+
+void
+SmbAuthData::initialize_password
+    ( const ConfigBlock* block
+    )
+{   const char* password_backend = block->GetBlockValue("password_backend", "config");
+
+    if (strcmp(password_backend, "config") == 0)
+        initialize_password_config(block);
+#ifdef ENABLE_LIBSECRET
+    else if (strcmp(password_backend, "libsecret") == 0)
+        initialize_password_secret();
+#endif
+}
+
+void
+SmbAuthData::initialize_password_config
+    ( const ConfigBlock* block
+    )
+{   set_password(block->GetBlockValue("password", ""));
+}
+
+#ifdef ENABLE_LIBSECRET
+void
+SmbAuthData::initialize_password_secret()
+{   gchar* pw = nullptr;
+    GError *error = nullptr;
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+    const SecretSchema mpd_schema =
+        { "org.mpd.smbclient.password"
+        , SECRET_SCHEMA_NONE
+        , { { "server"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "share"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "workgroup"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "username"
+            , SECRET_SCHEMA_ATTRIBUTE_STRING
+            }
+          , { "NULL"
+            , (SecretSchemaAttributeType)0
+            }
+          }
+        };
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
+    pw = secret_password_lookup_sync
+        ( &mpd_schema , nullptr , &error
+        , "server", server
+        , "share", share
+        , "workgroup", workgroup
+        , "username", username
+        , nullptr
+        );
+
+    if (error != nullptr)
+        g_error_free(error);
+
+    set_password(pw);
+    secret_password_free(pw);
+}
+#endif
+
+
+void
+mpd_smbc_set_auth_data
+    ( char* destination
+    , const char* source
+    , size_t num
+    )
+{   size_t source_len = strlen(source) + 1;
+    memcpy(destination, source, std::min(source_len, num));
+    if (source_len > num)
+        destination[num] = '\0';
+}
+
+void
+mpd_smbc_get_auth_data
+    ( const char* server
+    , const char* share
+    , char* workgroup,  int workgrouplen
+    , char* username,   int usernamelen
+    , char* password,   int passwordlen
+    )
+{   smb_auth_data.initialize(server, share);
+
+    mpd_smbc_set_auth_data(workgroup, smb_auth_data.workgroup, workgrouplen);
+    mpd_smbc_set_auth_data(username, smb_auth_data.username, usernamelen);
+    mpd_smbc_set_auth_data(password, smb_auth_data.password, passwordlen);
 }
 
 bool
 SmbclientInit(Error &error)
-{
-	const ScopeLock protect(smbclient_mutex);
+{   const ScopeLock protect(smbclient_mutex);
 
-	constexpr int debug = 0;
-	if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
-		error.SetErrno("smbc_init() failed");
-		return false;
-	}
+    if (!smb_state.initialized) {
+        constexpr int debug = 0;
+        if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
+            error.SetErrno("smbc_init() failed");
+            return false;
+        }
+        else {
+            smb_state.initialized = false;
+        }
+    }
 
-	return true;
+    return true;
 }
diff --git a/src/lib/smbclient/Init.hxx b/src/lib/smbclient/Init.hxx
index 1ccaec0..f2bf2dc 100644
--- a/src/lib/smbclient/Init.hxx
+++ b/src/lib/smbclient/Init.hxx
@@ -22,12 +22,97 @@
 
 #include "check.h"
 
+#include "config/Block.hxx"
+#include "util/Alloc.hxx"
+
+
 class Error;
 
-/**
+struct SmbAuthData
+    { const char* server;
+      const char* share;
+      const char* workgroup;
+      const char* username;
+      char* password;
+      bool initialized;
+
+      SmbAuthData &operator=
+        ( const SmbAuthData &
+        ) = delete;
+
+      SmbAuthData
+        ()
+        : server("")
+        , share("")
+        , workgroup("WORKGROUP")
+        , username("")
+        , password(xstrdup(""))
+        , initialized(false)
+        {}
+
+      ~SmbAuthData
+        ();
+
+      void
+      initialize
+        ( const char*
+        , const char*
+        );
+
+    private:
+      void
+      set_password
+        ( const char*
+        );
+
+      void
+      initialize_password
+        ( const ConfigBlock*
+        );
+
+      void
+      initialize_password_config
+        ( const ConfigBlock*
+        );
+
+#ifdef ENABLE_LIBSECRET
+      void
+      initialize_password_secret
+        ();
+#endif
+    };
+
+struct SmbState
+    { bool initialized;
+    };
+
+
+extern SmbAuthData smb_auth_data;
+extern SmbState smb_state;
+
+
+void
+mpd_smbc_set_auth_data
+    ( char*
+    , const char*
+    , size_t
+    );
+
+void
+mpd_smbc_get_auth_data
+    ( const char*
+    , const char*
+    , char*, int
+    , char*, int
+    , char*, int
+    );
+
+/*
  * Initialize libsmbclient.
  */
 bool
-SmbclientInit(Error &error);
+SmbclientInit
+    (Error&
+    );
 
 #endif
-- 
2.1.4

_______________________________________________
mpd-devel mailing list
[email protected]
http://mailman.blarg.de/listinfo/mpd-devel

Reply via email to