Merge branch 'atv_verification1'

This commit is contained in:
ejurgensen 2017-06-20 22:17:14 +02:00
commit 44fad55b0b
31 changed files with 2312 additions and 642 deletions

View File

@ -3,12 +3,11 @@ sudo: required
dist: trusty dist: trusty
env: env:
matrix: matrix:
- CFG="" - CFG="--disable-verification"
- CFG="--enable-lastfm" - CFG="--enable-lastfm --disable-verification"
- CFG="--enable-itunes" - CFG="--enable-spotify --disable-verification"
- CFG="--enable-spotify" - CFG="--enable-chromecast --disable-verification"
- CFG="--enable-chromecast" - CFG="--with-pulseaudio --disable-verification"
- CFG="--with-pulseaudio"
script: script:
- autoreconf -fi - autoreconf -fi

42
INSTALL
View File

@ -31,16 +31,17 @@ libraries:
antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \ antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \ libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \ libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
libevent-dev libevent-dev libplist-dev libsodium-dev
Optional packages: Optional packages:
Feature | Configure argument | Packages Feature | Configure argument | Packages
-----------|---------------------|--------------------------------------------- --------------------|------------------------|---------------------------------------------
Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev
iTunes XML | --enable-itunes | libplist-dev iTunes XML | --disable-itunes | libplist-dev
Pulseaudio | --with-pulseaudio | libpulse-dev Device verification | --disable-verification | libplist-dev libsodium-dev
Pulseaudio | --with-pulseaudio | libpulse-dev
Note that while forked-daapd will work with versions of libevent between 2.0.0 Note that while forked-daapd will work with versions of libevent between 2.0.0
and 2.1.3, it is recommended to use 2.1.4+. Otherwise you may not have support and 2.1.3, it is recommended to use 2.1.4+. Otherwise you may not have support
@ -68,7 +69,8 @@ will need ffmpeg. You can google how to do that. Then run:
sudo yum install \ sudo yum install \
git automake autoconf gettext-devel gperf gawk libtool \ git automake autoconf gettext-devel gperf gawk libtool \
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \ sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
libplist-devel libsodium-devel
Clone the forked-daapd repo: Clone the forked-daapd repo:
@ -137,12 +139,13 @@ Add the following to .bashrc:
Optional features require the following additional ports: Optional features require the following additional ports:
Feature | Configure argument | Ports Feature | Configure argument | Ports
-----------|---------------------|-------------------------------------------- --------------------|------------------------|--------------------------------------------
Chromecast | --enable-chromecast | json-c gnutls protobuf-c Chromecast | --enable-chromecast | json-c gnutls protobuf-c
LastFM | --enable-lastfm | curl LastFM | --enable-lastfm | curl
iTunes XML | --enable-itunes | libplist iTunes XML | --disable-itunes | libplist
Pulseaudio | --with-pulseaudio | pulseaudio Device verification | --disable-verification | libplist libsodium
Pulseaudio | --with-pulseaudio | pulseaudio
Clone the forked-daapd repo: Clone the forked-daapd repo:
git clone https://github.com/ejurgensen/forked-daapd.git git clone https://github.com/ejurgensen/forked-daapd.git
@ -219,8 +222,10 @@ Libraries:
often already installed as part of your distro often already installed as part of your distro
- libpulse (optional - Pulseaudio local audio) - libpulse (optional - Pulseaudio local audio)
from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/> from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
- libplist 0.16+ (optional - iTunes XML support) - libplist 0.16+ (optional - iTunes XML support and Apple TV device verification)
from <http://github.com/JonathanBeck/libplist/downloads> from <http://github.com/JonathanBeck/libplist/downloads>
- libsodium (optional - Apple TV device verification)
from <https://download.libsodium.org/doc/>
- libspotify (optional - Spotify support) - libspotify (optional - Spotify support)
from <https://developer.spotify.com> from <https://developer.spotify.com>
- libcurl (optional - LastFM support) - libcurl (optional - LastFM support)
@ -278,12 +283,15 @@ disabled).
Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this
feature. feature.
Support for iTunes Music Library XML format is optional. Use --enable-itunes Support for iTunes Music Library XML format is optional. Use --disable-itunes
to enable this feature. to disable this feature.
Support for the MPD protocol is optional. Use --disable-mpd to disable this Support for the MPD protocol is optional. Use --disable-mpd to disable this
feature. feature.
Support for Apple TV device verification is optional. Use --disable-verification
to disable this feature.
Support for Chromecast devices is optional. Use --enable-chromecast to enable Support for Chromecast devices is optional. Use --enable-chromecast to enable
this feature. this feature.

View File

@ -8,9 +8,6 @@ MPD clients, Chromecast, network streaming, internet radio, Spotify and LastFM.
It does not support streaming video by AirPlay nor Chromecast. It does not support streaming video by AirPlay nor Chromecast.
**Note: Airplay to Apple TV's that are updated to tvOS 10.2 is currently broken,
because these Apple TV's require device verification.**
DAAP stands for Digital Audio Access Protocol, and is the protocol used DAAP stands for Digital Audio Access Protocol, and is the protocol used
by iTunes and friends to share/stream media libraries over the network. by iTunes and friends to share/stream media libraries over the network.
@ -128,16 +125,8 @@ Or, if that doesn't work:
doesn't work properly on your network. doesn't work properly on your network.
4. Prepare a text file with a filename ending with .remote; the filename 4. Prepare a text file with a filename ending with .remote; the filename
doesn't matter, only the .remote ending does. This file must contain doesn't matter, only the .remote ending does. This first line in the file
two lines: the first line is the name of your iPod/iPhone/iPad, the second must contain the 4-digit pairing code displayed by Remote.
is the 4-digit pairing code displayed by Remote.
If your iPod/iPhone/iPad is named "Foobar" and Remote gives you the pairing
code 5387, the file content must be:
```
Foobar
5387
```
5. Move this file somewhere in your library 5. Move this file somewhere in your library
@ -154,12 +143,11 @@ forked-daapd does not get notified about new files on network mounts, so the
Solution: Set two library paths in the config, and add the .remote file to the Solution: Set two library paths in the config, and add the .remote file to the
local path. See [Libraries on network mounts](#libraries-on-network-mounts). local path. See [Libraries on network mounts](#libraries-on-network-mounts).
#### You did not enter the correct name or pairing code #### You did not enter the correct pairing code
You will see an error in the log about pairing failure with a HTTP response code You will see an error in the log about pairing failure with a HTTP response code
that is *not* 0. that is *not* 0.
Solution: Copy-paste the name to be sure to get specials characters right. You Solution: Try again. You can also try the pairinghelper script located in the
can also try the pairinghelper script located in the scripts-folder of the scripts-folder of the source or the mpc method described above.
source.
#### No response from Remote, possibly a network issue #### No response from Remote, possibly a network issue
If you see an error in the log with either: If you see an error in the log with either:
@ -191,9 +179,8 @@ Otherwise try using avahi-browse for troubleshooting:
Hit Ctrl-C to terminate avahi-browse. Hit Ctrl-C to terminate avahi-browse.
The name of your iPod/iPhone/iPad is the value of the DvNm field above. In this To check for network issues you can try to connect to address and port with
example, the correct value is Foobar. To check for network issues you can try to telnet.
connect to address and port with telnet.
### Selecting output devices ### Selecting output devices
@ -217,6 +204,18 @@ devices that are password-protected, the device's AirPlay name and password
must be given in the configuration file. See the sample configuration file must be given in the configuration file. See the sample configuration file
for the syntax. for the syntax.
If your Apple TV requires device verification (always required by Apple TV4 with
tvOS 10.2) then you must select the device for playback, whereafter a PIN will
be displayed by the Apple TV. The do either of the following:
Alternative 1: Create a file ending with .verification in your music library,
input the PIN, and save the file. Forked-daapd will now pair with the device,
and if you select the device again, playback should start.
Alternative 2: Run "mpc sendmessage verification [PIN]" (requires the mpc tool),
and then select the device again. Playback should start.
For troubleshooting, see [using Remote](#using-remote).
## Chromecast ## Chromecast

View File

@ -273,6 +273,14 @@ dnl Build with libcurl
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL], FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
[libcurl], [curl_global_init], [curl/curl.h]) [libcurl], [curl_global_init], [curl/curl.h])
dnl Build with libsodium
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libsodium support], [libsodium], [LIBSODIUM],
[libsodium], [sodium_init], [sodium.h])
dnl Build with libplist
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libplist support], [libplist], [LIBPLIST],
[libplist >= 0.16], [plist_dict_get_item], [plist/plist.h])
dnl Build with libevent_pthreads dnl Build with libevent_pthreads
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support], FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support],
[libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads], [libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
@ -300,12 +308,6 @@ AS_IF([[test "x$with_avahi" = "xno"]],
[AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])]) [AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])])
AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]]) AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]])
dnl iTunes playlists with libplist
FORK_ARG_ENABLE([iTunes Music Library XML support], [itunes], [ITUNES],
[FORK_MODULES_CHECK([FORKED_OPTS], [LIBPLIST], [libplist >= 0.16],
[plist_dict_get_item], [plist/plist.h])])
AM_CONDITIONAL([COND_ITUNES], [[test "x$enable_itunes" = "xyes"]])
dnl Spotify with dynamic linking to libspotify dnl Spotify with dynamic linking to libspotify
FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY], FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
[AS_IF([[test "x$with_json" = "xno"]], [AS_IF([[test "x$with_json" = "xno"]],
@ -352,10 +354,27 @@ FORK_ARG_ENABLE([Chromecast support], [chromecast], [CHROMECAST],
AM_CONDITIONAL([COND_CHROMECAST], [[test "x$enable_chromecast" = "xyes"]]) AM_CONDITIONAL([COND_CHROMECAST], [[test "x$enable_chromecast" = "xyes"]])
AM_CONDITIONAL([COND_PROTOBUF_OLD], [[test "x$protobuf_old" = "xyes"]]) AM_CONDITIONAL([COND_PROTOBUF_OLD], [[test "x$protobuf_old" = "xyes"]])
dnl iTunes playlists with libplist
FORK_ARG_DISABLE([iTunes Music Library XML support], [itunes], [ITUNES],
[AS_IF([[test "x$with_libplist" = "xno"]],
[AC_MSG_ERROR([[iTunes Music Library XML support requires libplist]])])
])
AM_CONDITIONAL([COND_ITUNES], [[test "x$enable_itunes" = "xyes"]])
dnl MPD support dnl MPD support
FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD]) FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD])
AM_CONDITIONAL([COND_MPD], [[test "x$enable_mpd" = "xyes"]]) AM_CONDITIONAL([COND_MPD], [[test "x$enable_mpd" = "xyes"]])
dnl Apple device verification
FORK_ARG_DISABLE([Apple TV device verification], [verification], [RAOP_VERIFICATION],
[
AS_IF([[test "x$with_libsodium" = "xno"]],
[AC_MSG_ERROR([[Apple TV device verification requires libsodium]])])
AS_IF([[test "x$with_libplist" = "xno"]],
[AC_MSG_ERROR([[Apple TV device verification requires libplist]])])
])
AM_CONDITIONAL([COND_RAOP_VERIFICATION], [[test "x$enable_verification" = "xyes"]])
dnl Defining users and groups dnl Defining users and groups
AC_ARG_WITH([daapd_user], AC_ARG_WITH([daapd_user],
[AS_HELP_STRING([--with-daapd-user=USER], [AS_HELP_STRING([--with-daapd-user=USER],

View File

@ -95,7 +95,7 @@ if [ -z "$pin" ]; then
fi fi
echo "Writing pair.remote to $library_path..." echo "Writing pair.remote to $library_path..."
printf "$remote\n$pin" > "$rf" printf "$pin" > "$rf"
if [ ! -f "$rf" ]; then if [ ! -f "$rf" ]; then
echo "Unable to create '$rf' - check directory permissions" echo "Unable to create '$rf' - check directory permissions"
exit 1 exit 1
@ -106,7 +106,7 @@ n=20
echo "Waiting for pairing to complete (up to $n secs)..." echo "Waiting for pairing to complete (up to $n secs)..."
while [ $n -gt 0 ]; do while [ $n -gt 0 ]; do
n=`expr $n - 1` n=`expr $n - 1`
result=`tail -1000 "$logfile" | sed -n "/.*remote:/ s,.*remote: ,,p" | awk '/^Discovered remote/{ f="" } /^Read Remote pairing data/ { f=$0; } END { print f }'` result=`tail -1000 "$logfile" | sed -n "/.*remote:/ s,.*remote: ,,p" | awk '/^Discovered remote/{ f="" } /^Kickoff pairing with pin/ { f=$0; } END { print f }'`
[ -n "$result" ] && break [ -n "$result" ] && break
sleep 1 sleep 1
done done

View File

@ -25,6 +25,10 @@ if COND_MPD
MPD_SRC=mpd.c mpd.h MPD_SRC=mpd.c mpd.h
endif endif
if COND_RAOP_VERIFICATION
RAOP_VERIFICATION_SRC=outputs/raop_verification.c outputs/raop_verification.h
endif
if COND_ALSA if COND_ALSA
ALSA_SRC=outputs/alsa.c ALSA_SRC=outputs/alsa.c
endif endif
@ -112,7 +116,8 @@ forked_daapd_SOURCES = main.c \
input.h input.c \ input.h input.c \
inputs/file_http.c inputs/pipe.c \ inputs/file_http.c inputs/pipe.c \
outputs.h outputs.c \ outputs.h outputs.c \
outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \ outputs/raop.c $(RAOP_VERIFICATION_SRC) \
outputs/streaming.c outputs/dummy.c outputs/fifo.c \
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \ $(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \ evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
$(SPOTIFY_SRC) \ $(SPOTIFY_SRC) \

View File

@ -3824,21 +3824,21 @@ db_admin_delete(const char *key)
/* Speakers */ /* Speakers */
int int
db_speaker_save(uint64_t id, int selected, int volume, const char *name) db_speaker_save(struct output_device *device)
{ {
#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name) VALUES (%" PRIi64 ", %d, %d, '%q');" #define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key) VALUES (%" PRIi64 ", %d, %d, %Q, %Q);"
char *query; char *query;
query = sqlite3_mprintf(Q_TMPL, id, selected, volume, name); query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key);
return db_query_run(query, 1, 0); return db_query_run(query, 1, 0);
#undef Q_TMPL #undef Q_TMPL
} }
int int
db_speaker_get(uint64_t id, int *selected, int *volume) db_speaker_get(struct output_device *device, uint64_t id)
{ {
#define Q_TMPL "SELECT s.selected, s.volume FROM speakers s WHERE s.id = %" PRIi64 ";" #define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key FROM speakers s WHERE s.id = %" PRIi64 ";"
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
char *query; char *query;
int ret; int ret;
@ -3847,7 +3847,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
if (!query) if (!query)
{ {
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return -1; return -1;
} }
@ -3857,7 +3856,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
ret = -1; ret = -1;
goto out; goto out;
} }
@ -3867,15 +3865,20 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
{ {
if (ret != SQLITE_DONE) if (ret != SQLITE_DONE)
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
ret = -1; ret = -1;
goto out; goto out;
} }
*selected = sqlite3_column_int(stmt, 0); device->id = id;
*volume = sqlite3_column_int(stmt, 1); device->selected = sqlite3_column_int(stmt, 0);
device->volume = sqlite3_column_int(stmt, 1);
free(device->name);
device->name = safe_strdup((char *)sqlite3_column_text(stmt, 2));
free(device->auth_key);
device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3));
#ifdef DB_PROFILE #ifdef DB_PROFILE
while (db_blocking_step(stmt) == SQLITE_ROW) while (db_blocking_step(stmt) == SQLITE_ROW)

View File

@ -8,6 +8,7 @@
#include <sqlite3.h> #include <sqlite3.h>
#include "outputs.h"
enum index_type { enum index_type {
I_NONE, I_NONE,
@ -675,12 +676,12 @@ db_admin_get(const char *key);
int int
db_admin_delete(const char *key); db_admin_delete(const char *key);
/* Speakers */ /* Speakers/outputs */
int int
db_speaker_save(uint64_t id, int selected, int volume, const char *name); db_speaker_save(struct output_device *device);
int int
db_speaker_get(uint64_t id, int *selected, int *volume); db_speaker_get(struct output_device *device, uint64_t id);
void void
db_speaker_clear_all(void); db_speaker_clear_all(void);

View File

@ -140,7 +140,8 @@
" id INTEGER PRIMARY KEY NOT NULL," \ " id INTEGER PRIMARY KEY NOT NULL," \
" selected INTEGER NOT NULL," \ " selected INTEGER NOT NULL," \
" volume INTEGER NOT NULL," \ " volume INTEGER NOT NULL," \
" name VARCHAR(255) DEFAULT NULL" \ " name VARCHAR(255) DEFAULT NULL," \
" auth_key VARCHAR(2048) DEFAULT NULL" \
");" ");"
#define T_INOTIFY \ #define T_INOTIFY \

View File

@ -26,7 +26,7 @@
* is a major upgrade. In other words minor version upgrades permit downgrading * is a major upgrade. In other words minor version upgrades permit downgrading
* forked-daapd after the database was upgraded. */ * forked-daapd after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 19 #define SCHEMA_VERSION_MAJOR 19
#define SCHEMA_VERSION_MINOR 03 #define SCHEMA_VERSION_MINOR 04
int int
db_init_indices(sqlite3 *hdl); db_init_indices(sqlite3 *hdl);

View File

@ -1547,6 +1547,23 @@ static const struct db_upgrade_query db_upgrade_v1903_queries[] =
}; };
#define U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY \
"ALTER TABLE speakers ADD COLUMN auth_key VARCHAR(2048) DEFAULT NULL;"
#define U_V1904_SCVER_MAJOR \
"UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
#define U_V1904_SCVER_MINOR \
"UPDATE admin SET value = '04' WHERE key = 'schema_version_minor';"
static const struct db_upgrade_query db_upgrade_v1904_queries[] =
{
{ U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY, "alter table speakers add column auth_key" },
{ U_V1904_SCVER_MAJOR, "set schema_version_major to 19" },
{ U_V1904_SCVER_MINOR, "set schema_version_minor to 04" },
};
int int
db_upgrade(sqlite3 *hdl, int db_ver) db_upgrade(sqlite3 *hdl, int db_ver)
{ {
@ -1678,6 +1695,13 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0) if (ret < 0)
return -1; return -1;
/* FALLTHROUGH */
case 1903:
ret = db_generic_upgrade(hdl, db_upgrade_v1904_queries, sizeof(db_upgrade_v1904_queries) / sizeof(db_upgrade_v1904_queries[0]));
if (ret < 0)
return -1;
break; break;
default: default:

View File

@ -46,6 +46,7 @@ extern "C" {
/* Response codes */ /* Response codes */
#define RTSP_OK 200 #define RTSP_OK 200
#define RTSP_UNAUTHORIZED 401 #define RTSP_UNAUTHORIZED 401
#define RTSP_FORBIDDEN 403
struct evrtsp_connection; struct evrtsp_connection;
@ -62,6 +63,7 @@ enum evrtsp_cmd_type {
EVRTSP_REQ_SET_PARAMETER, EVRTSP_REQ_SET_PARAMETER,
EVRTSP_REQ_FLUSH, EVRTSP_REQ_FLUSH,
EVRTSP_REQ_TEARDOWN, EVRTSP_REQ_TEARDOWN,
EVRTSP_REQ_POST,
}; };
enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE }; enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE };

View File

@ -260,6 +260,10 @@ evrtsp_method(enum evrtsp_cmd_type type)
method = "TEARDOWN"; method = "TEARDOWN";
break; break;
case EVRTSP_REQ_POST:
method = "POST";
break;
default: default:
method = NULL; method = NULL;
break; break;

View File

@ -63,108 +63,6 @@ static char *lastfm_session_key = NULL;
/* --------------------------------- HELPERS ------------------------------- */ /* --------------------------------- HELPERS ------------------------------- */
/* Reads a LastFM credentials file (1st line username, 2nd line password) */
static int
credentials_read(char *path, char **username, char **password)
{
FILE *fp;
char *u;
char *p;
char buf[256];
int len;
fp = fopen(path, "rb");
if (!fp)
{
DPRINTF(E_LOG, L_LASTFM, "Could not open lastfm credentials file %s: %s\n", path, strerror(errno));
return -1;
}
u = fgets(buf, sizeof(buf), fp);
if (!u)
{
DPRINTF(E_LOG, L_LASTFM, "Empty lastfm credentials file %s\n", path);
fclose(fp);
return -1;
}
len = strlen(u);
if (buf[len - 1] != '\n')
{
DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: username name too long or missing password\n", path);
fclose(fp);
return -1;
}
while (len)
{
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
{
buf[len - 1] = '\0';
len--;
}
else
break;
}
if (!len)
{
DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: empty line where username expected\n", path);
fclose(fp);
return -1;
}
u = strdup(buf);
if (!u)
{
DPRINTF(E_LOG, L_LASTFM, "Out of memory for username while reading %s\n", path);
fclose(fp);
return -1;
}
p = fgets(buf, sizeof(buf), fp);
fclose(fp);
if (!p)
{
DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: no password\n", path);
free(u);
return -1;
}
len = strlen(p);
while (len)
{
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
{
buf[len - 1] = '\0';
len--;
}
else
break;
}
p = strdup(buf);
if (!p)
{
DPRINTF(E_LOG, L_LASTFM, "Out of memory for password while reading %s\n", path);
free(u);
return -1;
}
DPRINTF(E_LOG, L_LASTFM, "lastfm credentials file OK, logging in with username %s\n", u);
*username = u;
*password = p;
return 0;
}
/* Creates an md5 signature of the concatenated parameters and adds it to keyval */ /* Creates an md5 signature of the concatenated parameters and adds it to keyval */
static int static int
@ -417,59 +315,39 @@ scrobble(int id)
/* ---------------------------- Our lastfm API --------------------------- */ /* ---------------------------- Our lastfm API --------------------------- */
/* Thread: filescanner */ /* Thread: filescanner */
int void
lastfm_login(char *path) lastfm_login(char **arglist)
{ {
struct keyval *kv; struct keyval *kv;
char *username;
char *password;
int ret; int ret;
DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n"); DPRINTF(E_LOG, L_LASTFM, "LastFM credentials file OK, logging in with username %s\n", arglist[0]);
// Delete any existing session key // Delete any existing session key
if (lastfm_session_key) free(lastfm_session_key);
free(lastfm_session_key);
lastfm_session_key = NULL; lastfm_session_key = NULL;
db_admin_delete("lastfm_sk"); db_admin_delete("lastfm_sk");
// Read the credentials file
ret = credentials_read(path, &username, &password);
if (ret < 0)
return -1;
// Enable LastFM now that we got a login attempt // Enable LastFM now that we got a login attempt
lastfm_disabled = 0; lastfm_disabled = 0;
kv = keyval_alloc(); kv = keyval_alloc();
if (!kv) if (!kv)
{ return;
ret = -1;
goto out_free_credentials;
}
ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) && ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
(keyval_add(kv, "username", username) == 0) && (keyval_add(kv, "username", arglist[0]) == 0) &&
(keyval_add(kv, "password", password) == 0) ); (keyval_add(kv, "password", arglist[1]) == 0) );
if (!ret) if (!ret)
{ goto out_free_kv;
ret = -1;
goto out_free_kv;
}
// Send the login request // Send the login request
ret = request_post("auth.getMobileSession", kv, 1); request_post("auth.getMobileSession", kv, 1);
out_free_kv: out_free_kv:
keyval_clear(kv); keyval_clear(kv);
free(kv); free(kv);
out_free_credentials:
free(username);
free(password);
return ret;
} }
/* Thread: worker */ /* Thread: worker */

View File

@ -2,8 +2,8 @@
#ifndef __LASTFM_H__ #ifndef __LASTFM_H__
#define __LASTFM_H__ #define __LASTFM_H__
int void
lastfm_login(char *path); lastfm_login(char **arglist);
int int
lastfm_scrobble(int id); lastfm_scrobble(int id);

View File

@ -92,6 +92,7 @@ enum file_type {
FILE_ITUNES, FILE_ITUNES,
FILE_ARTWORK, FILE_ARTWORK,
FILE_CTRL_REMOTE, FILE_CTRL_REMOTE,
FILE_CTRL_RAOP_VERIFICATION,
FILE_CTRL_LASTFM, FILE_CTRL_LASTFM,
FILE_CTRL_SPOTIFY, FILE_CTRL_SPOTIFY,
FILE_CTRL_INITSCAN, FILE_CTRL_INITSCAN,
@ -318,6 +319,9 @@ file_type_get(const char *path) {
if (strcasecmp(ext, ".remote") == 0) if (strcasecmp(ext, ".remote") == 0)
return FILE_CTRL_REMOTE; return FILE_CTRL_REMOTE;
if (strcasecmp(ext, ".verification") == 0)
return FILE_CTRL_RAOP_VERIFICATION;
if (strcasecmp(ext, ".lastfm") == 0) if (strcasecmp(ext, ".lastfm") == 0)
return FILE_CTRL_LASTFM; return FILE_CTRL_LASTFM;
@ -356,6 +360,25 @@ process_playlist(char *file, time_t mtime, int dir_id)
#endif #endif
} }
/* If we found a control file we want to kickoff some action */
static void
kickoff(void (*kickoff_func)(char **arg), const char *file, int lines)
{
char **file_content;
int i;
file_content = m_readfile(file, lines);
if (!file_content)
return;
kickoff_func(file_content);
for (i = 0; i < lines; i++)
free(file_content[i]);
free(file_content);
}
/* Thread: scan */ /* Thread: scan */
static void static void
defer_playlist(char *path, time_t mtime, int dir_id) defer_playlist(char *path, time_t mtime, int dir_id)
@ -523,7 +546,13 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
if (flags & F_SCAN_BULK) if (flags & F_SCAN_BULK)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file); DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else else
remote_pairing_kickoff_byfile(file); kickoff(remote_pairing_kickoff, file, 1);
case FILE_CTRL_RAOP_VERIFICATION:
if (flags & F_SCAN_BULK)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else
kickoff(player_raop_verification_kickoff, file, 1);
break; break;
case FILE_CTRL_LASTFM: case FILE_CTRL_LASTFM:
@ -531,7 +560,7 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
if (flags & F_SCAN_BULK) if (flags & F_SCAN_BULK)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file); DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else else
lastfm_login(file); kickoff(lastfm_login, file, 2);
#else #else
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file); DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file);
#endif #endif
@ -542,7 +571,7 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
if (flags & F_SCAN_BULK) if (flags & F_SCAN_BULK)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file); DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else else
spotify_login(file); kickoff(spotify_login, file, 2);
#else #else
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file); DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
#endif #endif

View File

@ -606,6 +606,9 @@ main(int argc, char **argv)
#ifdef MPD #ifdef MPD
strcat(buildopts, " --enable-mpd"); strcat(buildopts, " --enable-mpd");
#endif #endif
#ifdef RAOP_VERIFICATION
strcat(buildopts, " --enable-verification");
#endif
#ifdef HAVE_ALSA #ifdef HAVE_ALSA
strcat(buildopts, " --with-alsa"); strcat(buildopts, " --with-alsa");
#endif #endif

View File

@ -533,6 +533,57 @@ m_realpath(const char *pathname)
return ret; return ret;
} }
char **
m_readfile(const char *path, int num_lines)
{
char buf[256];
FILE *fp;
char **lines;
char *line;
int i;
// Alloc array of char pointers
lines = calloc(num_lines, sizeof(char *));
if (!lines)
return NULL;
fp = fopen(path, "rb");
if (!fp)
{
DPRINTF(E_LOG, L_MISC, "Could not open file '%s' for reading: %s\n", path, strerror(errno));
free(lines);
return NULL;
}
for (i = 0; i < num_lines; i++)
{
line = fgets(buf, sizeof(buf), fp);
if (!line)
{
DPRINTF(E_LOG, L_MISC, "File '%s' has fewer lines than expected (found %d, expected %d)\n", path, i, num_lines);
goto error;
}
lines[i] = trimwhitespace(line);
if (!lines[i] || (strlen(lines[i]) == 0))
{
DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path);
goto error;
}
}
fclose(fp);
return lines;
error:
for (i = 0; i < num_lines; i++)
free(lines[i]);
free(lines);
fclose(fp);
return NULL;
}
char * char *
unicode_fixup_string(char *str, const char *fromcode) unicode_fixup_string(char *str, const char *fromcode)

View File

@ -80,6 +80,9 @@ keyval_sort(struct keyval *kv);
char * char *
m_realpath(const char *pathname); m_realpath(const char *pathname);
char **
m_readfile(const char *path, int num_lines);
char * char *
unicode_fixup_string(char *str, const char *fromcode); unicode_fixup_string(char *str, const char *fromcode);

View File

@ -3520,7 +3520,13 @@ channel_outputvolume(const char *message)
static void static void
channel_pairing(const char *message) channel_pairing(const char *message)
{ {
remote_pairing_kickoff_bypin(message); remote_pairing_kickoff((char **)&message);
}
static void
channel_verification(const char *message)
{
player_raop_verification_kickoff((char **)&message);
} }
struct mpd_channel struct mpd_channel
@ -3546,6 +3552,10 @@ static struct mpd_channel mpd_channels[] =
.channel = "pairing", .channel = "pairing",
.handler = channel_pairing .handler = channel_pairing
}, },
{
.channel = "verification",
.handler = channel_verification
},
{ {
.channel = NULL, .channel = NULL,
.handler = NULL .handler = NULL

View File

@ -100,23 +100,22 @@ outputs_device_probe(struct output_device *device, output_status_cb cb)
void void
outputs_device_free(struct output_device *device) outputs_device_free(struct output_device *device)
{ {
if (!device)
return;
if (outputs[device->type]->disabled) if (outputs[device->type]->disabled)
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n"); DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n");
if (device->session)
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
if (outputs[device->type]->device_free_extra) if (outputs[device->type]->device_free_extra)
outputs[device->type]->device_free_extra(device); outputs[device->type]->device_free_extra(device);
if (device->name) free(device->name);
free(device->name); free(device->auth_key);
free(device->v4_address);
if (device->v4_address) free(device->v6_address);
free(device->v4_address);
if (device->v6_address)
free(device->v6_address);
if (device->session)
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
free(device); free(device);
} }
@ -313,6 +312,16 @@ outputs_metadata_free(struct output_metadata *omd)
} }
} }
void
outputs_authorize(enum output_types type, const char *pin)
{
if (outputs[type]->disabled)
return;
if (outputs[type]->authorize)
outputs[type]->authorize(pin);
}
int int
outputs_priority(struct output_device *device) outputs_priority(struct output_device *device)
{ {

View File

@ -102,9 +102,11 @@ struct output_device
unsigned advertised:1; unsigned advertised:1;
unsigned has_password:1; unsigned has_password:1;
unsigned has_video:1; unsigned has_video:1;
unsigned requires_auth:1;
// Password if relevant // Credentials if relevant
const char *password; const char *password;
char *auth_key;
// Device volume // Device volume
int volume; int volume;
@ -187,6 +189,9 @@ struct output_definition
// Flush all sessions, the return must be number of sessions pending the flush // Flush all sessions, the return must be number of sessions pending the flush
int (*flush)(output_status_cb cb, uint64_t rtptime); int (*flush)(output_status_cb cb, uint64_t rtptime);
// Authorize an output with a pin-code (probably coming from the filescanner)
void (*authorize)(const char *pin);
// Change the call back associated with a session // Change the call back associated with a session
void (*status_cb)(struct output_session *session, output_status_cb cb); void (*status_cb)(struct output_session *session, output_status_cb cb);
@ -242,6 +247,9 @@ outputs_metadata_prune(uint64_t rtptime);
void void
outputs_metadata_free(struct output_metadata *omd); outputs_metadata_free(struct output_metadata *omd);
void
outputs_authorize(enum output_types type, const char *pin);
int int
outputs_priority(struct output_device *device); outputs_priority(struct output_device *device);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
#ifndef __VERIFICATION_H__
#define __VERIFICATION_H__
#include <stdint.h>
struct verification_setup_context;
struct verification_verify_context;
/* When you have the pin-code (must be 4 bytes), create a new context with this
* function and then call verification_setup_request1()
*/
struct verification_setup_context *
verification_setup_new(const char *pin);
void
verification_setup_free(struct verification_setup_context *sctx);
/* Returns last error message
*/
const char *
verification_setup_errmsg(struct verification_setup_context *sctx);
uint8_t *
verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx);
uint8_t *
verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx);
uint8_t *
verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx);
int
verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
int
verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
int
verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
/* Returns a 0-terminated string that is the authorisation key. The caller
* should save it and use it later to initialize verification_verify_new().
* Note that the pointer becomes invalid when you free sctx.
*/
int
verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx);
/* When you have completed the setup you can extract a key with
* verification_setup_result(). Give the string as input to this function to
* create a verification context and then call verification_verify_request1()
*/
struct verification_verify_context *
verification_verify_new(const char *authorisation_key);
void
verification_verify_free(struct verification_verify_context *vctx);
/* Returns last error message
*/
const char *
verification_verify_errmsg(struct verification_verify_context *vctx);
uint8_t *
verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx);
uint8_t *
verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx);
int
verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len);
#endif /* !__VERIFICATION_H__ */

View File

@ -138,6 +138,12 @@ struct metadata_param
struct output_metadata *output; struct output_metadata *output;
}; };
struct speaker_auth_param
{
enum output_types type;
char pin[5];
};
union player_arg union player_arg
{ {
struct volume_param vol_param; struct volume_param vol_param;
@ -150,6 +156,7 @@ union player_arg
uint32_t *id_ptr; uint32_t *id_ptr;
struct speaker_set_param speaker_set_param; struct speaker_set_param speaker_set_param;
enum repeat_mode mode; enum repeat_mode mode;
struct speaker_auth_param auth;
uint32_t id; uint32_t id;
int intval; int intval;
}; };
@ -1235,7 +1242,7 @@ device_remove(struct output_device *remove)
return; return;
/* Save device volume */ /* Save device volume */
ret = db_speaker_save(remove->id, remove->selected, remove->volume, remove->name); ret = db_speaker_save(remove);
if (ret < 0) if (ret < 0)
DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", remove->type_name, remove->name); DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", remove->type_name, remove->name);
@ -1273,7 +1280,7 @@ device_add(void *arg, int *retval)
union player_arg *cmdarg; union player_arg *cmdarg;
struct output_device *add; struct output_device *add;
struct output_device *device; struct output_device *device;
int selected; char *keep_name;
int ret; int ret;
cmdarg = arg; cmdarg = arg;
@ -1290,15 +1297,21 @@ device_add(void *arg, int *retval)
{ {
device = add; device = add;
ret = db_speaker_get(device->id, &selected, &device->volume); keep_name = strdup(device->name);
ret = db_speaker_get(device, device->id);
if (ret < 0) if (ret < 0)
{ {
selected = 0; device->selected = 0;
device->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME; device->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME;
} }
if (selected && (player_state != PLAY_PLAYING)) free(device->name);
device->name = keep_name;
if (device->selected && (player_state != PLAY_PLAYING))
speaker_select_output(device); speaker_select_output(device);
else
device->selected = 0;
device->next = dev_list; device->next = dev_list;
dev_list = device; dev_list = device;
@ -1403,6 +1416,18 @@ device_remove_family(void *arg, int *retval)
return COMMAND_END; return COMMAND_END;
} }
static enum command_state
device_auth_kickoff(void *arg, int *retval)
{
union player_arg *cmdarg = arg;
outputs_authorize(cmdarg->auth.type, cmdarg->auth.pin);
*retval = 0;
return COMMAND_END;
}
static enum command_state static enum command_state
metadata_send(void *arg, int *retval) metadata_send(void *arg, int *retval)
{ {
@ -1651,10 +1676,12 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
static void static void
device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status) device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{ {
int retval;
int ret; int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type)); DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device); ret = device_check(device);
if (ret < 0) if (ret < 0)
{ {
@ -1663,9 +1690,17 @@ device_restart_cb(struct output_device *device, struct output_session *session,
outputs_status_cb(session, device_lost_cb); outputs_status_cb(session, device_lost_cb);
outputs_device_stop(session); outputs_device_stop(session);
if (retval != -2)
retval = -1;
goto out; goto out;
} }
if (status == OUTPUT_STATE_PASSWORD)
{
status = OUTPUT_STATE_FAILED;
retval = -2;
}
if (status == OUTPUT_STATE_FAILED) if (status == OUTPUT_STATE_FAILED)
{ {
speaker_deselect_output(device); speaker_deselect_output(device);
@ -1673,6 +1708,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
if (!device->advertised) if (!device->advertised)
device_remove(device); device_remove(device);
if (retval != -2)
retval = -1;
goto out; goto out;
} }
@ -1682,7 +1719,7 @@ device_restart_cb(struct output_device *device, struct output_session *session,
outputs_status_cb(session, device_streaming_cb); outputs_status_cb(session, device_streaming_cb);
out: out:
commands_exec_end(cmdbase, 0); commands_exec_end(cmdbase, retval);
} }
/* Internal abort routine */ /* Internal abort routine */
@ -3020,6 +3057,31 @@ player_device_remove(void *device)
return ret; return ret;
} }
static void
player_device_auth_kickoff(enum output_types type, char **arglist)
{
union player_arg *cmdarg;
cmdarg = calloc(1, sizeof(union player_arg));
if (!cmdarg)
{
DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");
return;
}
cmdarg->auth.type = type;
memcpy(cmdarg->auth.pin, arglist[0], 4);
commands_exec_async(cmdbase, device_auth_kickoff, cmdarg);
}
/* Thread: filescanner */
void
player_raop_verification_kickoff(char **arglist)
{
player_device_auth_kickoff(OUTPUT_TYPE_RAOP, arglist);
}
/* Thread: worker */ /* Thread: worker */
static void static void
player_metadata_send(struct input_metadata *imd, struct output_metadata *omd) player_metadata_send(struct input_metadata *imd, struct output_metadata *omd)
@ -3057,7 +3119,7 @@ player(void *arg)
for (device = dev_list; device; device = device->next) for (device = dev_list; device; device = device->next)
{ {
ret = db_speaker_save(device->id, device->selected, device->volume, device->name); ret = db_speaker_save(device);
if (ret < 0) if (ret < 0)
DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", device->type_name, device->name); DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", device->type_name, device->name);
} }

View File

@ -141,6 +141,9 @@ player_device_add(void *device);
int int
player_device_remove(void *device); player_device_remove(void *device);
void
player_raop_verification_kickoff(char **arglist);
struct player_history * struct player_history *
player_history_get(void); player_history_get(void);

View File

@ -714,131 +714,28 @@ touch_remote_cb(const char *name, const char *type, const char *domain, const ch
/* Thread: filescanner, mpd */ /* Thread: filescanner, mpd */
void void
remote_pairing_kickoff_bypin(const char *pin) remote_pairing_kickoff(char **arglist)
{ {
int ret; int ret;
DPRINTF(E_INFO, L_REMOTE, "Kickoff pairing with pin '%s'\n", pin); ret = strlen(arglist[0]);
if (ret != 4)
{
DPRINTF(E_LOG, L_REMOTE, "Kickoff pairing failed, unexpected pin length (got %d, expected 4)\n", ret);
return;
}
DPRINTF(E_LOG, L_REMOTE, "Kickoff pairing with pin '%s'\n", arglist[0]);
CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck)); CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
ret = add_remote_pin_data(pin); ret = add_remote_pin_data(arglist[0]);
if (ret == 0) if (ret == 0)
kickoff_pairing(); kickoff_pairing();
CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck)); CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
} }
/* Thread: filescanner */
void
remote_pairing_kickoff_byfile(char *path)
{
char buf[256];
FILE *fp;
char *devname;
char *pin;
int len;
fp = fopen(path, "rb");
if (!fp)
{
DPRINTF(E_LOG, L_REMOTE, "Could not open Remote pairing file %s: %s\n", path, strerror(errno));
return;
}
devname = fgets(buf, sizeof(buf), fp);
if (!devname)
{
DPRINTF(E_LOG, L_REMOTE, "Empty Remote pairing file %s\n", path);
fclose(fp);
return;
}
len = strlen(devname);
if (buf[len - 1] != '\n')
{
DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: device name too long or missing pin\n", path);
fclose(fp);
return;
}
while (len)
{
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
{
buf[len - 1] = '\0';
len--;
}
else
break;
}
if (!len)
{
DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: empty line where device name expected\n", path);
fclose(fp);
return;
}
devname = strdup(buf);
if (!devname)
{
DPRINTF(E_LOG, L_REMOTE, "Out of memory for device name while reading %s\n", path);
fclose(fp);
return;
}
pin = fgets(buf, sizeof(buf), fp);
fclose(fp);
if (!pin)
{
DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: no pin\n", path);
free(devname);
return;
}
len = strlen(pin);
while (len)
{
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
{
buf[len - 1] = '\0';
len--;
}
else
break;
}
if (len != 4)
{
DPRINTF(E_LOG, L_REMOTE, "Invalid pin in Remote pairing file %s: pin length should be 4, got %d\n", path, len);
free(devname);
return;
}
pin = strdup(buf);
if (!pin)
{
DPRINTF(E_LOG, L_REMOTE, "Out of memory for device pin while reading %s\n", path);
free(devname);
return;
}
DPRINTF(E_LOG, L_REMOTE, "Read Remote pairing data (name '%s', pin '%s') from %s\n", devname, pin, path);
remote_pairing_kickoff_bypin(pin);
free(devname);
free(pin);
}
/* Thread: main */ /* Thread: main */
int int

View File

@ -3,10 +3,7 @@
#define __REMOTE_PAIRING_H__ #define __REMOTE_PAIRING_H__
void void
remote_pairing_kickoff_bypin(const char *pin); remote_pairing_kickoff(char **arglist);
void
remote_pairing_kickoff_byfile(char *path);
int int
remote_pairing_init(void); remote_pairing_init(void);

View File

@ -406,110 +406,6 @@ webapi_pl_remove(void *arg, int *ret);
static void static void
create_base_playlist(); create_base_playlist();
/* ------------------------------- MISC HELPERS ---------------------------- */
static int
spotify_file_read(char *path, char **username, char **password)
{
FILE *fp;
char *u;
char *p;
char buf[256];
int len;
fp = fopen(path, "rb");
if (!fp)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not open Spotify credentials file %s: %s\n", path, strerror(errno));
return -1;
}
u = fgets(buf, sizeof(buf), fp);
if (!u)
{
DPRINTF(E_LOG, L_SPOTIFY, "Empty Spotify credentials file %s\n", path);
fclose(fp);
return -1;
}
len = strlen(u);
if (buf[len - 1] != '\n')
{
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: username name too long or missing password\n", path);
fclose(fp);
return -1;
}
while (len)
{
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
{
buf[len - 1] = '\0';
len--;
}
else
break;
}
if (!len)
{
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: empty line where username expected\n", path);
fclose(fp);
return -1;
}
u = strdup(buf);
if (!u)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for username while reading %s\n", path);
fclose(fp);
return -1;
}
p = fgets(buf, sizeof(buf), fp);
fclose(fp);
if (!p)
{
DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: no password\n", path);
free(u);
return -1;
}
len = strlen(p);
while (len)
{
if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
{
buf[len - 1] = '\0';
len--;
}
else
break;
}
p = strdup(buf);
if (!p)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for password while reading %s\n", path);
free(u);
return -1;
}
DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", u);
*username = u;
*password = p;
return 0;
}
/* -------------------------- PLAYLIST HELPERS ------------------------- */ /* -------------------------- PLAYLIST HELPERS ------------------------- */
/* Should only be called from within the spotify thread */ /* Should only be called from within the spotify thread */
@ -1945,12 +1841,9 @@ spotify_uri_register(const char *uri)
/* Thread: library */ /* Thread: library */
void void
spotify_login(char *path) spotify_login(char **arglist)
{ {
sp_error err; sp_error err;
char *username;
char *password;
int ret;
if (!g_sess) if (!g_sess)
{ {
@ -1982,20 +1875,14 @@ spotify_login(char *path)
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck)); CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
} }
DPRINTF(E_INFO, L_SPOTIFY, "Logging into Spotify\n"); if (arglist)
if (path)
{ {
ret = spotify_file_read(path, &username, &password); DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", arglist[0]);
if (ret < 0) err = fptr_sp_session_login(g_sess, arglist[0], arglist[1], 1, NULL);
return;
err = fptr_sp_session_login(g_sess, username, password, 1, NULL);
free(username);
free(password);
} }
else else
{ {
DPRINTF(E_INFO, L_SPOTIFY, "Relogin to Spotify\n");
err = fptr_sp_session_relogin(g_sess); err = fptr_sp_session_relogin(g_sess);
} }

View File

@ -37,7 +37,7 @@ void
spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri); spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri);
void void
spotify_login(char *path); spotify_login(char **arglist);
int int
spotify_init(void); spotify_init(void);