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
env:
matrix:
- CFG=""
- CFG="--enable-lastfm"
- CFG="--enable-itunes"
- CFG="--enable-spotify"
- CFG="--enable-chromecast"
- CFG="--with-pulseaudio"
- CFG="--disable-verification"
- CFG="--enable-lastfm --disable-verification"
- CFG="--enable-spotify --disable-verification"
- CFG="--enable-chromecast --disable-verification"
- CFG="--with-pulseaudio --disable-verification"
script:
- autoreconf -fi

42
INSTALL
View File

@ -31,16 +31,17 @@ libraries:
antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
libevent-dev
libevent-dev libplist-dev libsodium-dev
Optional packages:
Feature | Configure argument | Packages
-----------|---------------------|---------------------------------------------
Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev
iTunes XML | --enable-itunes | libplist-dev
Pulseaudio | --with-pulseaudio | libpulse-dev
Feature | Configure argument | Packages
--------------------|------------------------|---------------------------------------------
Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
LastFM | --enable-lastfm | libcurl4-gnutls-dev OR libcurl4-openssl-dev
iTunes XML | --disable-itunes | libplist-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
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 \
git automake autoconf gettext-devel gperf gawk libtool \
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:
@ -137,12 +139,13 @@ Add the following to .bashrc:
Optional features require the following additional ports:
Feature | Configure argument | Ports
-----------|---------------------|--------------------------------------------
Chromecast | --enable-chromecast | json-c gnutls protobuf-c
LastFM | --enable-lastfm | curl
iTunes XML | --enable-itunes | libplist
Pulseaudio | --with-pulseaudio | pulseaudio
Feature | Configure argument | Ports
--------------------|------------------------|--------------------------------------------
Chromecast | --enable-chromecast | json-c gnutls protobuf-c
LastFM | --enable-lastfm | curl
iTunes XML | --disable-itunes | libplist
Device verification | --disable-verification | libplist libsodium
Pulseaudio | --with-pulseaudio | pulseaudio
Clone the forked-daapd repo:
git clone https://github.com/ejurgensen/forked-daapd.git
@ -219,8 +222,10 @@ Libraries:
often already installed as part of your distro
- libpulse (optional - Pulseaudio local audio)
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>
- libsodium (optional - Apple TV device verification)
from <https://download.libsodium.org/doc/>
- libspotify (optional - Spotify support)
from <https://developer.spotify.com>
- libcurl (optional - LastFM support)
@ -278,12 +283,15 @@ disabled).
Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this
feature.
Support for iTunes Music Library XML format is optional. Use --enable-itunes
to enable this feature.
Support for iTunes Music Library XML format is optional. Use --disable-itunes
to disable this feature.
Support for the MPD protocol is optional. Use --disable-mpd to disable this
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
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.
**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
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.
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
two lines: the first line is the name of your iPod/iPhone/iPad, the second
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
```
doesn't matter, only the .remote ending does. This first line in the file
must contain the 4-digit pairing code displayed by Remote.
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
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
that is *not* 0.
Solution: Copy-paste the name to be sure to get specials characters right. You
can also try the pairinghelper script located in the scripts-folder of the
source.
Solution: Try again. You can also try the pairinghelper script located in the
scripts-folder of the source or the mpc method described above.
#### No response from Remote, possibly a network issue
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.
The name of your iPod/iPhone/iPad is the value of the DvNm field above. In this
example, the correct value is Foobar. To check for network issues you can try to
connect to address and port with telnet.
To check for network issues you can try to connect to address and port with
telnet.
### 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
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

View File

@ -273,6 +273,14 @@ dnl Build with libcurl
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
[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
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support],
[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.]])])])
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
FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
[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_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
FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD])
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
AC_ARG_WITH([daapd_user],
[AS_HELP_STRING([--with-daapd-user=USER],

View File

@ -95,7 +95,7 @@ if [ -z "$pin" ]; then
fi
echo "Writing pair.remote to $library_path..."
printf "$remote\n$pin" > "$rf"
printf "$pin" > "$rf"
if [ ! -f "$rf" ]; then
echo "Unable to create '$rf' - check directory permissions"
exit 1
@ -106,7 +106,7 @@ n=20
echo "Waiting for pairing to complete (up to $n secs)..."
while [ $n -gt 0 ]; do
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
sleep 1
done

View File

@ -25,6 +25,10 @@ if COND_MPD
MPD_SRC=mpd.c mpd.h
endif
if COND_RAOP_VERIFICATION
RAOP_VERIFICATION_SRC=outputs/raop_verification.c outputs/raop_verification.h
endif
if COND_ALSA
ALSA_SRC=outputs/alsa.c
endif
@ -112,7 +116,8 @@ forked_daapd_SOURCES = main.c \
input.h input.c \
inputs/file_http.c inputs/pipe.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) \
evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
$(SPOTIFY_SRC) \

View File

@ -3824,21 +3824,21 @@ db_admin_delete(const char *key)
/* Speakers */
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;
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);
#undef Q_TMPL
}
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;
char *query;
int ret;
@ -3847,7 +3847,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return -1;
}
@ -3857,7 +3856,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
ret = -1;
goto out;
}
@ -3867,15 +3865,20 @@ db_speaker_get(uint64_t id, int *selected, int *volume)
{
if (ret != SQLITE_DONE)
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
sqlite3_finalize(stmt);
ret = -1;
goto out;
}
*selected = sqlite3_column_int(stmt, 0);
*volume = sqlite3_column_int(stmt, 1);
device->id = id;
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
while (db_blocking_step(stmt) == SQLITE_ROW)

View File

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

View File

@ -140,7 +140,8 @@
" id INTEGER PRIMARY KEY NOT NULL," \
" selected 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 \

View File

@ -26,7 +26,7 @@
* is a major upgrade. In other words minor version upgrades permit downgrading
* forked-daapd after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 19
#define SCHEMA_VERSION_MINOR 03
#define SCHEMA_VERSION_MINOR 04
int
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
db_upgrade(sqlite3 *hdl, int db_ver)
{
@ -1678,6 +1695,13 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0)
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;
default:

View File

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

View File

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

View File

@ -63,108 +63,6 @@ static char *lastfm_session_key = NULL;
/* --------------------------------- 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 */
static int
@ -417,59 +315,39 @@ scrobble(int id)
/* ---------------------------- Our lastfm API --------------------------- */
/* Thread: filescanner */
int
lastfm_login(char *path)
void
lastfm_login(char **arglist)
{
struct keyval *kv;
char *username;
char *password;
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
if (lastfm_session_key)
free(lastfm_session_key);
free(lastfm_session_key);
lastfm_session_key = NULL;
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
lastfm_disabled = 0;
kv = keyval_alloc();
if (!kv)
{
ret = -1;
goto out_free_credentials;
}
return;
ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
(keyval_add(kv, "username", username) == 0) &&
(keyval_add(kv, "password", password) == 0) );
(keyval_add(kv, "username", arglist[0]) == 0) &&
(keyval_add(kv, "password", arglist[1]) == 0) );
if (!ret)
{
ret = -1;
goto out_free_kv;
}
goto out_free_kv;
// Send the login request
ret = request_post("auth.getMobileSession", kv, 1);
request_post("auth.getMobileSession", kv, 1);
out_free_kv:
keyval_clear(kv);
free(kv);
out_free_credentials:
free(username);
free(password);
return ret;
}
/* Thread: worker */

View File

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

View File

@ -92,6 +92,7 @@ enum file_type {
FILE_ITUNES,
FILE_ARTWORK,
FILE_CTRL_REMOTE,
FILE_CTRL_RAOP_VERIFICATION,
FILE_CTRL_LASTFM,
FILE_CTRL_SPOTIFY,
FILE_CTRL_INITSCAN,
@ -318,6 +319,9 @@ file_type_get(const char *path) {
if (strcasecmp(ext, ".remote") == 0)
return FILE_CTRL_REMOTE;
if (strcasecmp(ext, ".verification") == 0)
return FILE_CTRL_RAOP_VERIFICATION;
if (strcasecmp(ext, ".lastfm") == 0)
return FILE_CTRL_LASTFM;
@ -356,6 +360,25 @@ process_playlist(char *file, time_t mtime, int dir_id)
#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 */
static void
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)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
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;
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)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else
lastfm_login(file);
kickoff(lastfm_login, file, 2);
#else
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file);
#endif
@ -542,7 +571,7 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
if (flags & F_SCAN_BULK)
DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
else
spotify_login(file);
kickoff(spotify_login, file, 2);
#else
DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
#endif

View File

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

View File

@ -533,6 +533,57 @@ m_realpath(const char *pathname)
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 *
unicode_fixup_string(char *str, const char *fromcode)

View File

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

View File

@ -3520,7 +3520,13 @@ channel_outputvolume(const char *message)
static void
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
@ -3546,6 +3552,10 @@ static struct mpd_channel mpd_channels[] =
.channel = "pairing",
.handler = channel_pairing
},
{
.channel = "verification",
.handler = channel_verification
},
{
.channel = NULL,
.handler = NULL

View File

@ -100,23 +100,22 @@ outputs_device_probe(struct output_device *device, output_status_cb cb)
void
outputs_device_free(struct output_device *device)
{
if (!device)
return;
if (outputs[device->type]->disabled)
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)
outputs[device->type]->device_free_extra(device);
if (device->name)
free(device->name);
if (device->v4_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->name);
free(device->auth_key);
free(device->v4_address);
free(device->v6_address);
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
outputs_priority(struct output_device *device)
{

View File

@ -102,9 +102,11 @@ struct output_device
unsigned advertised:1;
unsigned has_password:1;
unsigned has_video:1;
unsigned requires_auth:1;
// Password if relevant
// Credentials if relevant
const char *password;
char *auth_key;
// Device volume
int volume;
@ -187,6 +189,9 @@ struct output_definition
// Flush all sessions, the return must be number of sessions pending the flush
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
void (*status_cb)(struct output_session *session, output_status_cb cb);
@ -242,6 +247,9 @@ outputs_metadata_prune(uint64_t rtptime);
void
outputs_metadata_free(struct output_metadata *omd);
void
outputs_authorize(enum output_types type, const char *pin);
int
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 speaker_auth_param
{
enum output_types type;
char pin[5];
};
union player_arg
{
struct volume_param vol_param;
@ -150,6 +156,7 @@ union player_arg
uint32_t *id_ptr;
struct speaker_set_param speaker_set_param;
enum repeat_mode mode;
struct speaker_auth_param auth;
uint32_t id;
int intval;
};
@ -1235,7 +1242,7 @@ device_remove(struct output_device *remove)
return;
/* Save device volume */
ret = db_speaker_save(remove->id, remove->selected, remove->volume, remove->name);
ret = db_speaker_save(remove);
if (ret < 0)
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;
struct output_device *add;
struct output_device *device;
int selected;
char *keep_name;
int ret;
cmdarg = arg;
@ -1290,15 +1297,21 @@ device_add(void *arg, int *retval)
{
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)
{
selected = 0;
device->selected = 0;
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);
else
device->selected = 0;
device->next = dev_list;
dev_list = device;
@ -1403,6 +1416,18 @@ device_remove_family(void *arg, int *retval)
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
metadata_send(void *arg, int *retval)
{
@ -1651,10 +1676,12 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
static void
device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{
int retval;
int ret;
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);
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_device_stop(session);
if (retval != -2)
retval = -1;
goto out;
}
if (status == OUTPUT_STATE_PASSWORD)
{
status = OUTPUT_STATE_FAILED;
retval = -2;
}
if (status == OUTPUT_STATE_FAILED)
{
speaker_deselect_output(device);
@ -1673,6 +1708,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
if (!device->advertised)
device_remove(device);
if (retval != -2)
retval = -1;
goto out;
}
@ -1682,7 +1719,7 @@ device_restart_cb(struct output_device *device, struct output_session *session,
outputs_status_cb(session, device_streaming_cb);
out:
commands_exec_end(cmdbase, 0);
commands_exec_end(cmdbase, retval);
}
/* Internal abort routine */
@ -3020,6 +3057,31 @@ player_device_remove(void *device)
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 */
static void
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)
{
ret = db_speaker_save(device->id, device->selected, device->volume, device->name);
ret = db_speaker_save(device);
if (ret < 0)
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
player_device_remove(void *device);
void
player_raop_verification_kickoff(char **arglist);
struct player_history *
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 */
void
remote_pairing_kickoff_bypin(const char *pin)
remote_pairing_kickoff(char **arglist)
{
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));
ret = add_remote_pin_data(pin);
ret = add_remote_pin_data(arglist[0]);
if (ret == 0)
kickoff_pairing();
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 */
int

View File

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

View File

@ -406,110 +406,6 @@ webapi_pl_remove(void *arg, int *ret);
static void
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 ------------------------- */
/* Should only be called from within the spotify thread */
@ -1945,12 +1841,9 @@ spotify_uri_register(const char *uri)
/* Thread: library */
void
spotify_login(char *path)
spotify_login(char **arglist)
{
sp_error err;
char *username;
char *password;
int ret;
if (!g_sess)
{
@ -1982,20 +1875,14 @@ spotify_login(char *path)
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
}
DPRINTF(E_INFO, L_SPOTIFY, "Logging into Spotify\n");
if (path)
if (arglist)
{
ret = spotify_file_read(path, &username, &password);
if (ret < 0)
return;
err = fptr_sp_session_login(g_sess, username, password, 1, NULL);
free(username);
free(password);
DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", arglist[0]);
err = fptr_sp_session_login(g_sess, arglist[0], arglist[1], 1, NULL);
}
else
{
DPRINTF(E_INFO, L_SPOTIFY, "Relogin to Spotify\n");
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);
void
spotify_login(char *path);
spotify_login(char **arglist);
int
spotify_init(void);