From 283df8aa7230de90b4c7efc2a949dfe43061c495 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 14 Jun 2017 23:49:18 +0200 Subject: [PATCH 01/18] [misc] Consolidate control file reader (e.g. for pairing credentials) into misc.c - also remove requirement to enter device name in .remote file --- src/lastfm.c | 140 +++----------------------------------- src/lastfm.h | 4 +- src/library/filescanner.c | 35 +++++++++- src/misc.c | 51 ++++++++++++++ src/misc.h | 3 + src/remote_pairing.c | 123 +++------------------------------ src/remote_pairing.h | 5 +- src/spotify.c | 123 ++------------------------------- src/spotify.h | 2 +- 9 files changed, 114 insertions(+), 372 deletions(-) diff --git a/src/lastfm.c b/src/lastfm.c index 67dac08c..b6838516 100644 --- a/src/lastfm.c +++ b/src/lastfm.c @@ -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 */ diff --git a/src/lastfm.h b/src/lastfm.h index 6a3fa03e..7c6d9fe2 100644 --- a/src/lastfm.h +++ b/src/lastfm.h @@ -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); diff --git a/src/library/filescanner.c b/src/library/filescanner.c index 861af7dc..1d8c30b6 100644 --- a/src/library/filescanner.c +++ b/src/library/filescanner.c @@ -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 diff --git a/src/misc.c b/src/misc.c index 401da7df..c87bb63f 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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 (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) diff --git a/src/misc.h b/src/misc.h index 916ab774..210748c5 100644 --- a/src/misc.h +++ b/src/misc.h @@ -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); diff --git a/src/remote_pairing.c b/src/remote_pairing.c index 8381b1cf..b8547334 100644 --- a/src/remote_pairing.c +++ b/src/remote_pairing.c @@ -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 diff --git a/src/remote_pairing.h b/src/remote_pairing.h index 824b9a0d..6a34d29f 100644 --- a/src/remote_pairing.h +++ b/src/remote_pairing.h @@ -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); diff --git a/src/spotify.c b/src/spotify.c index 87b3ef03..9dc47b99 100644 --- a/src/spotify.c +++ b/src/spotify.c @@ -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); } diff --git a/src/spotify.h b/src/spotify.h index ca028e14..0d1f0907 100644 --- a/src/spotify.h +++ b/src/spotify.h @@ -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); From beafa84dd3826e6dd364eca660b9d84aeedf986b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 14 Jun 2017 23:54:16 +0200 Subject: [PATCH 02/18] [scripts] Update pairing helper to only write pin --- scripts/pairinghelper.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pairinghelper.sh b/scripts/pairinghelper.sh index 6a096f0c..f844c1b3 100755 --- a/scripts/pairinghelper.sh +++ b/scripts/pairinghelper.sh @@ -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 From 50822abc5cccec5d2bf7f2fbd22222bcae72e93b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 14 Jun 2017 23:59:10 +0200 Subject: [PATCH 03/18] [docs] Update README with revised pairing instructions --- README.md | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6bff09cc..288aa7b4 100644 --- a/README.md +++ b/README.md @@ -128,16 +128,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 +146,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 +182,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 From 0642171de00e406be19ff6913dc2ca67196e4c5d Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 15 Jun 2017 00:00:05 +0200 Subject: [PATCH 04/18] [mpd] Adjust so "sendmessage pairing" now gives an arglist to remote_pairing_kickoff() --- src/mpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mpd.c b/src/mpd.c index 687306a7..c3f4d77b 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -3520,7 +3520,7 @@ channel_outputvolume(const char *message) static void channel_pairing(const char *message) { - remote_pairing_kickoff_bypin(message); + remote_pairing_kickoff((char **)&message); } struct mpd_channel From 0e9bca9e3d77b0f73e690fb0c2f0d3be996e11f2 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 18 Jun 2017 01:29:40 +0200 Subject: [PATCH 05/18] [db] Add column for auth key to speakers table (incl methods for saving/retrieving) --- src/db.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ src/db.h | 6 +++++ src/db_init.c | 3 ++- src/db_init.h | 2 +- src/db_upgrade.c | 24 +++++++++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/db.c b/src/db.c index 31a968c5..644308c5 100644 --- a/src/db.c +++ b/src/db.c @@ -3893,6 +3893,73 @@ db_speaker_get(uint64_t id, int *selected, int *volume) #undef Q_TMPL } +int +db_speaker_auth_save(uint64_t id, const char *authkey) +{ +#define Q_TMPL "UPDATE speakers SET authkey = '%q' WHERE id = %" PRIi64 ";" + char *query; + + query = sqlite3_mprintf(Q_TMPL, authkey, id); + + return db_query_run(query, 1, 0); +#undef Q_TMPL +} + +char * +db_speaker_auth_get(uint64_t id) +{ +#define Q_TMPL "SELECT authkey FROM speakers WHERE id = %" PRIi64 ";" + sqlite3_stmt *stmt; + char *query; + char *out; + int ret; + + out = NULL; + + query = sqlite3_mprintf(Q_TMPL, id); + if (!query) + { + DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); + return NULL; + } + + DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); + + ret = db_blocking_prepare_v2(query, -1, &stmt, NULL); + if (ret != SQLITE_OK) + { + DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); + goto out; + } + + ret = db_blocking_step(stmt); + if (ret != SQLITE_ROW) + { + if (ret != SQLITE_DONE) + DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); + + sqlite3_finalize(stmt); + goto out; + } + + out = (char *)sqlite3_column_text(stmt, 0); + if (out) + out = strdup(out); + +#ifdef DB_PROFILE + while (db_blocking_step(stmt) == SQLITE_ROW) + ; /* EMPTY */ +#endif + + sqlite3_finalize(stmt); + + out: + sqlite3_free(query); + return out; + +#undef Q_TMPL +} + void db_speaker_clear_all(void) { diff --git a/src/db.h b/src/db.h index 15d844b6..418a229a 100644 --- a/src/db.h +++ b/src/db.h @@ -682,6 +682,12 @@ db_speaker_save(uint64_t id, int selected, int volume, const char *name); int db_speaker_get(uint64_t id, int *selected, int *volume); +int +db_speaker_auth_save(uint64_t id, const char *authkey); + +char * +db_speaker_auth_get(uint64_t id); + void db_speaker_clear_all(void); diff --git a/src/db_init.c b/src/db_init.c index ad8a8a82..c38bd75b 100644 --- a/src/db_init.c +++ b/src/db_init.c @@ -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," \ + " authkey VARCHAR(2048) DEFAULT NULL" \ ");" #define T_INOTIFY \ diff --git a/src/db_init.h b/src/db_init.h index 84237d8a..bff24b8e 100644 --- a/src/db_init.h +++ b/src/db_init.h @@ -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); diff --git a/src/db_upgrade.c b/src/db_upgrade.c index 40db0d9c..7abcc52f 100644 --- a/src/db_upgrade.c +++ b/src/db_upgrade.c @@ -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 authkey 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 authkey" }, + + { 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: From 736979a9a2e93d6b60733ecef2ad2408c8ad39de Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 18 Jun 2017 01:31:23 +0200 Subject: [PATCH 06/18] [evrtsp] Add POST rtsp method - used for device verification --- src/evrtsp/evrtsp.h | 2 ++ src/evrtsp/rtsp.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/evrtsp/evrtsp.h b/src/evrtsp/evrtsp.h index c7cd275c..7ee1f5ca 100644 --- a/src/evrtsp/evrtsp.h +++ b/src/evrtsp/evrtsp.h @@ -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 }; diff --git a/src/evrtsp/rtsp.c b/src/evrtsp/rtsp.c index e623b165..8019f539 100644 --- a/src/evrtsp/rtsp.c +++ b/src/evrtsp/rtsp.c @@ -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; From f63d103753c0526444a78ee8ff9419efd660ee13 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Jun 2017 21:52:01 +0200 Subject: [PATCH 07/18] [raop] Add support for Apple TV device verification, required by tvOS 10.2 (fix for issue #377) - also change how speakers are saved/retrieved from the db - add generic authorization methods in outputs.c and player.c - let filescanner read *.verification files (containing PIN) - configure options to enable and disable, since libsodium is required --- configure.ac | 31 +- src/Makefile.am | 7 +- src/db.c | 92 +-- src/db.h | 13 +- src/db_init.c | 2 +- src/db_upgrade.c | 4 +- src/library/filescanner.c | 4 +- src/main.c | 3 + src/outputs.c | 31 +- src/outputs.h | 10 +- src/outputs/raop.c | 706 ++++++++++++---- src/outputs/raop_verification.c | 1344 +++++++++++++++++++++++++++++++ src/outputs/raop_verification.h | 66 ++ src/player.c | 62 +- src/player.h | 3 + 15 files changed, 2090 insertions(+), 288 deletions(-) create mode 100644 src/outputs/raop_verification.c create mode 100644 src/outputs/raop_verification.h diff --git a/configure.ac b/configure.ac index 897c4833..5e2173ff 100644 --- a/configure.ac +++ b/configure.ac @@ -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], diff --git a/src/Makefile.am b/src/Makefile.am index 9c333486..b27e68db 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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) \ diff --git a/src/db.c b/src/db.c index 644308c5..2ea77abc 100644 --- a/src/db.c +++ b/src/db.c @@ -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) @@ -3893,73 +3896,6 @@ db_speaker_get(uint64_t id, int *selected, int *volume) #undef Q_TMPL } -int -db_speaker_auth_save(uint64_t id, const char *authkey) -{ -#define Q_TMPL "UPDATE speakers SET authkey = '%q' WHERE id = %" PRIi64 ";" - char *query; - - query = sqlite3_mprintf(Q_TMPL, authkey, id); - - return db_query_run(query, 1, 0); -#undef Q_TMPL -} - -char * -db_speaker_auth_get(uint64_t id) -{ -#define Q_TMPL "SELECT authkey FROM speakers WHERE id = %" PRIi64 ";" - sqlite3_stmt *stmt; - char *query; - char *out; - int ret; - - out = NULL; - - query = sqlite3_mprintf(Q_TMPL, id); - if (!query) - { - DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); - return NULL; - } - - DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query); - - ret = db_blocking_prepare_v2(query, -1, &stmt, NULL); - if (ret != SQLITE_OK) - { - DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl)); - goto out; - } - - ret = db_blocking_step(stmt); - if (ret != SQLITE_ROW) - { - if (ret != SQLITE_DONE) - DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl)); - - sqlite3_finalize(stmt); - goto out; - } - - out = (char *)sqlite3_column_text(stmt, 0); - if (out) - out = strdup(out); - -#ifdef DB_PROFILE - while (db_blocking_step(stmt) == SQLITE_ROW) - ; /* EMPTY */ -#endif - - sqlite3_finalize(stmt); - - out: - sqlite3_free(query); - return out; - -#undef Q_TMPL -} - void db_speaker_clear_all(void) { diff --git a/src/db.h b/src/db.h index 418a229a..0d7510d1 100644 --- a/src/db.h +++ b/src/db.h @@ -8,6 +8,7 @@ #include +#include "outputs.h" enum index_type { I_NONE, @@ -675,18 +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); - -int -db_speaker_auth_save(uint64_t id, const char *authkey); - -char * -db_speaker_auth_get(uint64_t id); +db_speaker_get(struct output_device *device, uint64_t id); void db_speaker_clear_all(void); diff --git a/src/db_init.c b/src/db_init.c index c38bd75b..06201626 100644 --- a/src/db_init.c +++ b/src/db_init.c @@ -141,7 +141,7 @@ " selected INTEGER NOT NULL," \ " volume INTEGER NOT NULL," \ " name VARCHAR(255) DEFAULT NULL," \ - " authkey VARCHAR(2048) DEFAULT NULL" \ + " auth_key VARCHAR(2048) DEFAULT NULL" \ ");" #define T_INOTIFY \ diff --git a/src/db_upgrade.c b/src/db_upgrade.c index 7abcc52f..0a209603 100644 --- a/src/db_upgrade.c +++ b/src/db_upgrade.c @@ -1548,7 +1548,7 @@ static const struct db_upgrade_query db_upgrade_v1903_queries[] = #define U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY \ - "ALTER TABLE speakers ADD COLUMN authkey VARCHAR(2048) DEFAULT NULL;" + "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';" @@ -1557,7 +1557,7 @@ static const struct db_upgrade_query db_upgrade_v1903_queries[] = static const struct db_upgrade_query db_upgrade_v1904_queries[] = { - { U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY, "alter table speakers add column authkey" }, + { 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" }, diff --git a/src/library/filescanner.c b/src/library/filescanner.c index 1d8c30b6..c49635ce 100644 --- a/src/library/filescanner.c +++ b/src/library/filescanner.c @@ -551,8 +551,8 @@ process_file(char *file, struct stat *sb, int type, int flags, int dir_id) 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); + else + kickoff(player_raop_verification_kickoff, file, 1); break; case FILE_CTRL_LASTFM: diff --git a/src/main.c b/src/main.c index 269e7dfc..c957d2c8 100644 --- a/src/main.c +++ b/src/main.c @@ -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 diff --git a/src/outputs.c b/src/outputs.c index 871798fd..8dd54498 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -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) { diff --git a/src/outputs.h b/src/outputs.h index 380a9d0a..7e8b1e05 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -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); diff --git a/src/outputs/raop.c b/src/outputs/raop.c index e78b3422..39f792ef 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012-2017 Espen Jürgensen * Copyright (C) 2010-2011 Julien BLACHE * * RAOP AirTunes v2 @@ -27,15 +28,12 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* TODO: - * - Support RTSP authentication in all requests (only OPTIONS so far) - */ - #ifdef HAVE_CONFIG_H # include #endif #include +#include #include #include #include @@ -77,6 +75,10 @@ #include "dmap_common.h" #include "outputs.h" +#ifdef RAOP_VERIFICATION +#include "raop_verification.h" +#endif + #ifndef MIN # define MIN(a, b) ((a < b) ? a : b) #endif @@ -146,6 +148,8 @@ enum raop_state { RAOP_STATE_FAILED = -1, // Password issue: unknown password or bad password RAOP_STATE_PASSWORD = -2, + // Device requires verification, pending PIN from user + RAOP_STATE_UNVERIFIED= -3, }; // Info about the device, which is not required by the player, only internally @@ -153,8 +157,8 @@ struct raop_extra { enum raop_devtype devtype; - unsigned encrypt:1; - unsigned wants_metadata:1; + bool encrypt; + bool wants_metadata; }; struct raop_session @@ -162,11 +166,13 @@ struct raop_session struct evrtsp_connection *ctrl; enum raop_state state; - unsigned req_has_auth:1; - unsigned encrypt:1; - unsigned auth_quirk_itunes:1; - unsigned wants_metadata:1; - unsigned keep_alive:1; + bool req_has_auth; + bool encrypt; + bool auth_quirk_itunes; + bool wants_metadata; + bool keep_alive; + + bool only_probe; struct event *deferredev; @@ -196,6 +202,12 @@ struct raop_session unsigned short control_port; unsigned short timing_port; +#ifdef RAOP_VERIFICATION + /* Device verification, see raop_verification.h */ + struct verification_verify_context *verification_verify_ctx; + struct verification_setup_context *verification_setup_ctx; +#endif + int server_fd; union sockaddr_all sa; @@ -1648,7 +1660,6 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb) if (!req) { DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for OPTIONS\n"); - return -1; } @@ -1663,6 +1674,44 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb) if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "Could not make OPTIONS request\n"); + return -1; + } + + rs->reqs_in_flight++; + + evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL); + + return 0; +} + +#ifdef RAOP_VERIFICATION +static int +raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb) +{ + struct evrtsp_request *req; + int ret; + + req = evrtsp_request_new(cb, rs); + if (!req) + { + DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for pair-pin-start\n"); + + return -1; + } + + ret = raop_add_headers(rs, req, EVRTSP_REQ_POST); + if (ret < 0) + { + evrtsp_request_free(req); + return -1; + } + + DPRINTF(E_LOG, L_RAOP, "Starting device verification for '%s', please submit PIN via a *.verification file\n", rs->devname); + + ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, "/pair-pin-start"); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not make pair-pin-start request\n"); return -1; } @@ -1673,6 +1722,15 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb) return 0; } +#else +static int +raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb) +{ + DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname); + + return -1; +} +#endif /* Maps our internal state to the generic output state and then makes a callback * to the player to tell that state @@ -1685,7 +1743,7 @@ raop_status(struct raop_session *rs) switch (rs->state) { - case RAOP_STATE_PASSWORD: + case RAOP_STATE_UNVERIFIED ... RAOP_STATE_PASSWORD: state = OUTPUT_STATE_PASSWORD; break; case RAOP_STATE_FAILED: @@ -1798,9 +1856,7 @@ raop_session_failure(struct raop_session *rs) static void raop_deferredev_cb(int fd, short what, void *arg) { - struct raop_session *rs; - - rs = (struct raop_session *)arg; + struct raop_session *rs = arg; DPRINTF(E_DBG, L_RAOP, "Cleaning up failed session (deferred) on device '%s'\n", rs->devname); @@ -1810,10 +1866,8 @@ raop_deferredev_cb(int fd, short what, void *arg) static void raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg) { + struct raop_session *rs = arg; struct timeval tv; - struct raop_session *rs; - - rs = (struct raop_session *)arg; DPRINTF(E_LOG, L_RAOP, "Device '%s' closed RTSP connection\n", rs->devname); @@ -1824,7 +1878,7 @@ raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg) } static struct raop_session * -raop_session_make(struct output_device *rd, int family, output_status_cb cb) +raop_session_make(struct output_device *rd, int family, output_status_cb cb, bool only_probe) { struct output_session *os; struct raop_session *rs; @@ -1879,6 +1933,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb) rs->output_session = os; rs->state = RAOP_STATE_STOPPED; + rs->only_probe = only_probe; rs->reqs_in_flight = 0; rs->cseq = 1; @@ -1887,6 +1942,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb) rs->server_fd = -1; rs->password = rd->password; + rs->wants_metadata = re->wants_metadata; switch (re->devtype) @@ -2020,9 +2076,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb) static void raop_session_failure_cb(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; - - rs = (struct raop_session *)arg; + struct raop_session *rs = arg; raop_session_failure(rs); } @@ -2032,11 +2086,9 @@ raop_session_failure_cb(struct evrtsp_request *req, void *arg) static void raop_cb_metadata(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -2388,11 +2440,9 @@ raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb) static void raop_cb_set_volume(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -2452,11 +2502,9 @@ raop_set_volume_one(struct output_device *rd, output_status_cb cb) static void raop_cb_flush(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -2512,6 +2560,41 @@ raop_cb_keep_alive(struct evrtsp_request *req, void *arg) raop_session_failure(rs); } +static void +raop_cb_pin_start(struct evrtsp_request *req, void *arg) +{ + struct raop_session *rs = arg; + int ret; + + rs->reqs_in_flight--; + + if (!req) + goto error; + + if (req->response_code != RTSP_OK) + { + DPRINTF(E_LOG, L_RAOP, "Request for starting PIN verification failed: %d %s\n", req->response_code, req->response_code_line); + + goto error; + } + + ret = raop_check_cseq(rs, req); + if (ret < 0) + goto error; + + rs->state = RAOP_STATE_UNVERIFIED; + + raop_status(rs); + + // TODO If the user never verifies the session will remain stale + + return; + + error: + raop_session_failure(rs); +} + + // Forward static void raop_device_stop(struct output_session *session); @@ -3498,11 +3581,9 @@ raop_startup_cancel(struct raop_session *rs) static void raop_cb_startup_volume(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -3544,12 +3625,10 @@ raop_cb_startup_volume(struct evrtsp_request *req, void *arg) static void raop_cb_startup_record(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; const char *param; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -3587,7 +3666,7 @@ raop_cb_startup_record(struct evrtsp_request *req, void *arg) static void raop_cb_startup_setup(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; const char *param; char *transport; char *token; @@ -3595,8 +3674,6 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg) int tmp; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -3737,11 +3814,9 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg) static void raop_cb_startup_announce(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -3774,11 +3849,9 @@ raop_cb_startup_announce(struct evrtsp_request *req, void *arg) static void raop_cb_startup_options(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req || !req->response_code) @@ -3796,9 +3869,9 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) goto cleanup; } - if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED)) + if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED) && (req->response_code != RTSP_FORBIDDEN)) { - DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed starting '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line); + DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line); goto cleanup; } @@ -3825,7 +3898,18 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "Could not re-run OPTIONS request with authentication\n"); + goto cleanup; + } + return; + } + + if (req->response_code == RTSP_FORBIDDEN) + { + ret = raop_send_req_pin_start(rs, raop_cb_pin_start); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not request PIN for device verification\n"); goto cleanup; } @@ -3834,26 +3918,37 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) rs->state = RAOP_STATE_OPTIONS; - /* Send ANNOUNCE */ - ret = raop_send_req_announce(rs, raop_cb_startup_announce); - if (ret < 0) - goto cleanup; + if (rs->only_probe) + { + /* Device probed successfully, tell our user */ + raop_status(rs); + + /* We're not going further with this session */ + raop_session_cleanup(rs); + } + else + { + /* Send ANNOUNCE */ + ret = raop_send_req_announce(rs, raop_cb_startup_announce); + if (ret < 0) + goto cleanup; + } return; cleanup: - raop_startup_cancel(rs); + if (rs->only_probe) + raop_session_failure(rs); + else + raop_startup_cancel(rs); } - static void raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg) { - struct raop_session *rs; + struct raop_session *rs = arg; int ret; - rs = (struct raop_session *)arg; - rs->reqs_in_flight--; if (!req) @@ -3883,81 +3978,352 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg) raop_session_failure(rs); } + +/* tvOS device verification - e.g. for the ATV4 (read it from the bottom and up) */ + +#ifdef RAOP_VERIFICATION +static int +raop_verification_response_process(int step, struct evrtsp_request *req, struct raop_session *rs) +{ + uint8_t *response; + const char *errmsg; + size_t len; + int ret; + + rs->reqs_in_flight--; + + if (!req) + { + DPRINTF(E_LOG, L_RAOP, "Verification step %d to '%s' failed, empty callback\n", step, rs->devname); + return -1; + } + + if (req->response_code != RTSP_OK) + { + DPRINTF(E_LOG, L_RAOP, "Verification step %d to '%s' failed with error code %d: %s\n", step, rs->devname, req->response_code, req->response_code_line); + return -1; + } + + response = evbuffer_pullup(req->input_buffer, -1); + len = evbuffer_get_length(req->input_buffer); + + switch (step) + { + case 1: + ret = verification_setup_response1(rs->verification_setup_ctx, response, len); + errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + break; + case 2: + ret = verification_setup_response2(rs->verification_setup_ctx, response, len); + errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + break; + case 3: + ret = verification_setup_response3(rs->verification_setup_ctx, response, len); + errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + break; + case 4: + ret = verification_verify_response1(rs->verification_verify_ctx, response, len); + errmsg = verification_verify_errmsg(rs->verification_verify_ctx); + break; + case 5: + ret = 0; + break; + default: + ret = -1; + errmsg = "Bug! Bad step number"; + } + + if (ret < 0) + DPRINTF(E_LOG, L_RAOP, "Verification step %d response from '%s' error: %s\n", step, rs->devname, errmsg); + + return ret; +} + +static int +raop_verification_request_send(int step, struct raop_session *rs, void (*cb)(struct evrtsp_request *, void *)) +{ + struct evrtsp_request *req; + uint8_t *body; + uint32_t len; + const char *errmsg; + const char *url; + const char *ctype; + int ret; + + switch (step) + { + case 1: + body = verification_setup_request1(&len, rs->verification_setup_ctx); + errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + url = "/pair-setup-pin"; + ctype = "application/x-apple-binary-plist"; + break; + case 2: + body = verification_setup_request2(&len, rs->verification_setup_ctx); + errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + url = "/pair-setup-pin"; + ctype = "application/x-apple-binary-plist"; + break; + case 3: + body = verification_setup_request3(&len, rs->verification_setup_ctx); + errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + url = "/pair-setup-pin"; + ctype = "application/x-apple-binary-plist"; + break; + case 4: + body = verification_verify_request1(&len, rs->verification_verify_ctx); + errmsg = verification_verify_errmsg(rs->verification_verify_ctx); + url = "/pair-verify"; + ctype = "application/octet-stream"; + break; + case 5: + body = verification_verify_request2(&len, rs->verification_verify_ctx); + errmsg = verification_verify_errmsg(rs->verification_verify_ctx); + url = "/pair-verify"; + ctype = "application/octet-stream"; + break; + default: + body = NULL; + errmsg = "Bug! Bad step number"; + } + + if (!body) + { + DPRINTF(E_LOG, L_RAOP, "Verification step %d request error: %s\n", step, errmsg); + return -1; + } + + req = evrtsp_request_new(cb, rs); + if (!req) + { + DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for verification step %d\n", step); + return -1; + } + + evbuffer_add(req->output_buffer, body, len); + free(body); + + ret = raop_add_headers(rs, req, EVRTSP_REQ_POST); + if (ret < 0) + { + evrtsp_request_free(req); + return -1; + } + + evrtsp_add_header(req->output_headers, "Content-Type", ctype); + + DPRINTF(E_INFO, L_RAOP, "Making verification request step %d to '%s'\n", step, rs->devname); + + ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, url); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Verification request step %d to '%s' failed\n", step, rs->devname); + return -1; + } + + rs->reqs_in_flight++; + + evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL); + + return 0; +} + static void -raop_cb_probe_options(struct evrtsp_request *req, void *arg) +raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg) +{ + struct raop_session *rs = arg; + int ret; + + verification_verify_free(rs->verification_verify_ctx); + + ret = raop_verification_response_process(5, req, rs); + if (ret < 0) + goto error; + + DPRINTF(E_LOG, L_RAOP, "Verification of '%s' completed succesfully\n", rs->devname); + + rs->state = RAOP_STATE_STARTUP; + + raop_send_req_options(rs, raop_cb_startup_options); + + return; + + error: + rs->state = RAOP_STATE_UNVERIFIED; + raop_status(rs); +} + +static void +raop_cb_verification_verify_step1(struct evrtsp_request *req, void *arg) +{ + struct raop_session *rs = arg; + int ret; + + ret = raop_verification_response_process(4, req, rs); + if (ret < 0) + goto error; + + ret = raop_verification_request_send(5, rs, raop_cb_verification_verify_step2); + if (ret < 0) + goto error; + + return; + + error: + rs->state = RAOP_STATE_UNVERIFIED; + raop_status(rs); + + verification_verify_free(rs->verification_verify_ctx); + rs->verification_verify_ctx = NULL; +} + +static int +raop_verification_verify(struct raop_session *rs) +{ + int ret; + + rs->verification_verify_ctx = verification_verify_new(rs->device->auth_key); // Naughty boy is dereferencing device + if (!rs->verification_verify_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for verification verify context\n"); + return -1; + } + + ret = raop_verification_request_send(4, rs, raop_cb_verification_verify_step1); + if (ret < 0) + goto error; + + return 0; + + error: + rs->state = RAOP_STATE_UNVERIFIED; + raop_status(rs); + + verification_verify_free(rs->verification_verify_ctx); + rs->verification_verify_ctx = NULL; + return -1; +} + + +static void +raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg) +{ + struct raop_session *rs = arg; + const char *authorization_key; + int ret; + + ret = raop_verification_response_process(3, req, rs); + if (ret < 0) + goto error; + + ret = verification_setup_result(&authorization_key, rs->verification_setup_ctx); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", verification_setup_errmsg(rs->verification_setup_ctx)); + goto error; + } + + DPRINTF(E_INFO, L_RAOP, "Verification setup stage complete, saving authorization key\n"); + + free(rs->device->auth_key); + rs->device->auth_key = strdup(authorization_key); + + // The player considers this session failed, so we don't need it any more + raop_session_cleanup(rs); + + /* Fallthrough */ + + error: + verification_setup_free(rs->verification_setup_ctx); + rs->verification_setup_ctx = NULL; +} + +static void +raop_cb_verification_setup_step2(struct evrtsp_request *req, void *arg) +{ + struct raop_session *rs = arg; + int ret; + + ret = raop_verification_response_process(2, req, rs); + if (ret < 0) + goto error; + + ret = raop_verification_request_send(3, rs, raop_cb_verification_setup_step3); + if (ret < 0) + goto error; + + return; + + error: + verification_setup_free(rs->verification_setup_ctx); + rs->verification_setup_ctx = NULL; +} + +static void +raop_cb_verification_setup_step1(struct evrtsp_request *req, void *arg) +{ + struct raop_session *rs = arg; + int ret; + + ret = raop_verification_response_process(1, req, rs); + if (ret < 0) + goto error; + + ret = raop_verification_request_send(2, rs, raop_cb_verification_setup_step2); + if (ret < 0) + goto error; + + return; + + error: + verification_setup_free(rs->verification_setup_ctx); + rs->verification_setup_ctx = NULL; +} + +static void +raop_verification_setup(const char *pin) { struct raop_session *rs; int ret; - rs = (struct raop_session *)arg; - - rs->reqs_in_flight--; - - if (!req || !req->response_code) + for (rs = sessions; rs; rs = rs->next) { - DPRINTF(E_LOG, L_RAOP, "No response from '%s' (%s) to OPTIONS request\n", rs->devname, rs->address); - - if (rs->device->v4_address && (rs->sa.ss.ss_family == AF_INET6)) - { - DPRINTF(E_LOG, L_RAOP, "Falling back to ipv4, the ipv6 address is not responding\n"); - - free(rs->device->v6_address); - rs->device->v6_address = NULL; - } - - goto cleanup; + if (rs->state == RAOP_STATE_UNVERIFIED) + break; } - if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED)) + if (!rs) { - DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed probing '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line); - - goto cleanup; - } - - ret = raop_check_cseq(rs, req); - if (ret < 0) - goto cleanup; - - if (req->response_code == RTSP_UNAUTHORIZED) - { - if (rs->req_has_auth) - { - DPRINTF(E_LOG, L_RAOP, "Bad password for device '%s'\n", rs->devname); - - rs->state = RAOP_STATE_PASSWORD; - goto cleanup; - } - - ret = raop_parse_auth(rs, req); - if (ret < 0) - goto cleanup; - - ret = raop_send_req_options(rs, raop_cb_probe_options); - if (ret < 0) - { - DPRINTF(E_LOG, L_RAOP, "Could not re-run OPTIONS request with authentication\n"); - - goto cleanup; - } - + DPRINTF(E_LOG, L_RAOP, "Got a PIN for device verification, but no device is awaiting verification\n"); return; } - rs->state = RAOP_STATE_OPTIONS; + rs->verification_setup_ctx = verification_setup_new(pin); + if (!rs->verification_setup_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n"); + return; + } - /* Device probed successfully, tell our user */ - raop_status(rs); - - /* We're not going further with this session */ - raop_session_cleanup(rs); + ret = raop_verification_request_send(1, rs, raop_cb_verification_setup_step1); + if (ret < 0) + goto error; return; - cleanup: - raop_session_failure(rs); + error: + verification_setup_free(rs->verification_setup_ctx); + rs->verification_setup_ctx = NULL; } +#else +static int +raop_verification_verify(struct raop_session *rs) +{ + DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname); + return -1; +} +#endif /* RAOP_VERIFICATION */ /* RAOP devices discovery - mDNS callback */ /* Thread: main (mdns) */ @@ -3995,6 +4361,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha char *at_name; char *password; uint64_t id; + uint64_t sf; int ret; ret = safe_hextou64(name, &id); @@ -4125,6 +4492,16 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha rd->password = password; + /* Device verification */ + p = keyval_get(txt, "sf"); + if (p && (safe_hextou64(p, &sf) == 0)) + { + if (sf & (1 << 9)) + rd->requires_auth = 1; + + // Note: device_add() in player.c will get the auth key from the db if available + } + /* Device type */ re->devtype = RAOP_DEV_OTHER; p = keyval_get(txt, "am"); @@ -4163,15 +4540,15 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha case AF_INET: rd->v4_address = strdup(address); rd->v4_port = port; - DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s, address %s:%d\n", - name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port); + DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, verification: %u, encrypt: %u, metadata: %u, type %s, address %s:%d\n", + name, rd->has_password, rd->requires_auth, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port); break; case AF_INET6: rd->v6_address = strdup(address); rd->v6_port = port; - DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s, address [%s]:%d\n", - name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port); + DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, verification: %u, encrypt: %u, metadata: %u, type %s, address [%s]:%d\n", + name, rd->has_password, rd->requires_auth, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port); break; default: @@ -4190,38 +4567,53 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha } static int -raop_device_probe(struct output_device *rd, output_status_cb cb) +raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_t rtptime, bool only_probe) { struct raop_session *rs; int ret; - /* Send an OPTIONS request to test our ability to connect to the device, - * including the need for and/or validity of the password + /* Send an OPTIONS request to establish the connection. If device verification + * is required we start with that. After that, we can determine our local + * address and build our session URL for all subsequent requests. */ - rs = raop_session_make(rd, AF_INET6, cb); + rs = raop_session_make(rd, AF_INET6, cb, only_probe); if (rs) { - ret = raop_send_req_options(rs, raop_cb_probe_options); + rs->start_rtptime = rtptime; + + if (rd->auth_key) + ret = raop_verification_verify(rs); + else if (rd->requires_auth) + ret = raop_send_req_pin_start(rs, raop_cb_pin_start); + else + ret = raop_send_req_options(rs, raop_cb_startup_options); + if (ret == 0) return 0; else { - DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv6 (probe)\n"); - + DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n"); raop_session_cleanup(rs); } } - rs = raop_session_make(rd, AF_INET, cb); + rs = raop_session_make(rd, AF_INET, cb, only_probe); if (!rs) return -1; - ret = raop_send_req_options(rs, raop_cb_probe_options); + rs->start_rtptime = rtptime; + + if (rd->auth_key) + ret = raop_verification_verify(rs); + else if (rd->requires_auth) + ret = raop_send_req_pin_start(rs, raop_cb_pin_start); + else + ret = raop_send_req_options(rs, raop_cb_startup_options); + if (ret < 0) { - DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv4 (probe)\n"); - + DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv4\n"); raop_session_cleanup(rs); return -1; } @@ -4229,49 +4621,16 @@ raop_device_probe(struct output_device *rd, output_status_cb cb) return 0; } +static int +raop_device_probe(struct output_device *rd, output_status_cb cb) +{ + return raop_device_start_generic(rd, cb, 0, 1); +} + static int raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime) { - struct raop_session *rs; - int ret; - - /* Send an OPTIONS request to establish the connection - * After that, we can determine our local address and build our session URL - * for all subsequent requests. - */ - - rs = raop_session_make(rd, AF_INET6, cb); - if (rs) - { - rs->start_rtptime = rtptime; - - ret = raop_send_req_options(rs, raop_cb_startup_options); - if (ret == 0) - return 0; - else - { - DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv6 (start)\n"); - - raop_session_cleanup(rs); - } - } - - rs = raop_session_make(rd, AF_INET, cb); - if (!rs) - return -1; - - rs->start_rtptime = rtptime; - - ret = raop_send_req_options(rs, raop_cb_startup_options); - if (ret < 0) - { - DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv4 (start)\n"); - - raop_session_cleanup(rs); - return -1; - } - - return 0; + return raop_device_start_generic(rd, cb, rtptime, 0); } static void @@ -4289,7 +4648,9 @@ raop_device_stop(struct output_session *session) static void raop_device_free_extra(struct output_device *device) { - free(device->extra_device_info); + struct raop_extra *re = device->extra_device_info; + + free(re); } static void @@ -4534,4 +4895,7 @@ struct output_definition output_raop = .metadata_send = raop_metadata_send, .metadata_purge = raop_metadata_purge, .metadata_prune = raop_metadata_prune, +#ifdef RAOP_VERIFICATION + .authorize = raop_verification_setup, +#endif }; diff --git a/src/outputs/raop_verification.c b/src/outputs/raop_verification.c new file mode 100644 index 00000000..c9975dce --- /dev/null +++ b/src/outputs/raop_verification.c @@ -0,0 +1,1344 @@ +/* + * + * The Secure Remote Password 6a implementation included here is by + * - Tom Cocagne + * + * + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include + +#include +#include + +#include "raop_verification.h" + +#define CONFIG_GCRYPT 1 + +/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */ +/* partly borrowed from ffmpeg (rtmpdh.c) */ + +#if CONFIG_GCRYPT +#include +#define SHA512_DIGEST_LENGTH 64 +#define bnum_new(bn) \ + do { \ + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \ + if (!gcry_check_version("1.5.4")) \ + abort(); \ + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); \ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); \ + } \ + bn = gcry_mpi_new(1); \ + } while (0) +#define bnum_free(bn) gcry_mpi_release(bn) +#define bnum_num_bytes(bn) (gcry_mpi_get_nbits(bn) + 7) / 8 +#define bnum_is_zero(bn) (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0) +#define bnum_bn2bin(bn, buf, len) gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn) +#define bnum_bin2bn(bn, buf, len) gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL) +#define bnum_hex2bn(bn, buf) gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0) +#define bnum_random(bn, num_bits) gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM) +#define bnum_add(bn, a, b) gcry_mpi_add(bn, a, b) +#define bnum_sub(bn, a, b) gcry_mpi_sub(bn, a, b) +#define bnum_mul(bn, a, b) gcry_mpi_mul(bn, a, b) +typedef gcry_mpi_t bnum; +static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) +{ + gcry_mpi_powm(bn, y, q, p); +} +#elif CONFIG_OPENSSL +#include +#include +#include +#include +#include +#define bnum_new(bn) bn = BN_new() +#define bnum_free(bn) BN_free(bn) +#define bnum_num_bytes(bn) BN_num_bytes(bn) +#define bnum_is_zero(bn) BN_is_zero(bn) +#define bnum_bn2bin(bn, buf, len) BN_bn2bin(bn, buf) +#define bnum_bin2bn(bn, buf, len) bn = BN_bin2bn(buf, len, 0) +#define bnum_hex2bn(bn, buf) BN_hex2bn(&bn, buf) +#define bnum_random(bn, num_bits) BN_rand(bn, num_bits, 0, 0) +#define bnum_add(bn, a, b) BN_add(bn, a, b) +#define bnum_sub(bn, a, b) BN_sub(bn, a, b) +typedef BIGNUM* bnum; +static void bnum_mul(bnum bn, bnum a, bnum b) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mul(bn, a, b, ctx); + BN_CTX_free(ctx); +} +static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mod_exp(bn, y, q, p, ctx); + BN_CTX_free(ctx); +} +#endif + + +/* ----------------------------- DEFINES ETC ------------------------------- */ + +#define USERNAME "12:34:56:78:90:AB" +#define EPK_LENGTH 32 +#define AUTHTAG_LENGTH 16 +#define AES_SETUP_KEY "Pair-Setup-AES-Key" +#define AES_SETUP_IV "Pair-Setup-AES-IV" +#define AES_VERIFY_KEY "Pair-Verify-AES-Key" +#define AES_VERIFY_IV "Pair-Verify-AES-IV" + +#ifdef CONFIG_OPENSSL +enum hash_alg +{ + HASH_SHA1, + HASH_SHA224, + HASH_SHA256, + HASH_SHA384, + HASH_SHA512, +}; +#elif CONFIG_GCRYPT +enum hash_alg +{ + HASH_SHA1 = GCRY_MD_SHA1, + HASH_SHA224 = GCRY_MD_SHA224, + HASH_SHA256 = GCRY_MD_SHA256, + HASH_SHA384 = GCRY_MD_SHA384, + HASH_SHA512 = GCRY_MD_SHA512, +}; +#endif + +struct verification_setup_context +{ + struct SRPUser *user; + + char pin[4]; + + const uint8_t *pkA; + int pkA_len; + + uint8_t *pkB; + uint64_t pkB_len; + + const uint8_t *M1; + int M1_len; + + uint8_t *M2; + uint64_t M2_len; + + uint8_t *salt; + uint64_t salt_len; + uint8_t public_key[crypto_sign_PUBLICKEYBYTES]; + uint8_t private_key[crypto_sign_SECRETKEYBYTES]; + // Hex-formatet concatenation of public + private, 0-terminated + char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1]; + + // We don't actually use the server's epk and authtag for anything + uint8_t *epk; + uint64_t epk_len; + uint8_t *authtag; + uint64_t authtag_len; + + const char *errmsg; +}; + +struct verification_verify_context +{ + uint8_t server_eph_public_key[32]; + uint8_t server_public_key[64]; + + uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; + uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; + + uint8_t client_eph_public_key[32]; + uint8_t client_eph_private_key[32]; + + const char *errmsg; +}; + + +/* ---------------------------------- SRP ---------------------------------- */ + +typedef enum +{ + SRP_NG_2048, + SRP_NG_CUSTOM +} SRP_NGType; + +typedef struct +{ + bnum N; + bnum g; +} NGConstant; + +#if CONFIG_OPENSSL +typedef union +{ + SHA_CTX sha; + SHA256_CTX sha256; + SHA512_CTX sha512; +} HashCTX; +#elif CONFIG_GCRYPT +typedef gcry_md_hd_t HashCTX; +#endif + +struct SRPUser +{ + enum hash_alg alg; + NGConstant *ng; + + bnum a; + bnum A; + bnum S; + + const unsigned char *bytes_A; + int authenticated; + + const char *username; + const unsigned char *password; + int password_len; + + unsigned char M [SHA512_DIGEST_LENGTH]; + unsigned char H_AMK [SHA512_DIGEST_LENGTH]; + unsigned char session_key [2 * SHA512_DIGEST_LENGTH]; // See hash_session_key() + int session_key_len; +}; + +struct NGHex +{ + const char *n_hex; + const char *g_hex; +}; + +// We only need 2048 right now, but keep the array in case we want to add others later +// All constants here were pulled from Appendix A of RFC 5054 +static struct NGHex global_Ng_constants[] = +{ + { /* 2048 */ + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", + "2" + }, + {0,0} /* null sentinel */ +}; + + +static NGConstant * +new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) +{ + NGConstant *ng = calloc(1, sizeof(NGConstant)); + if(!ng) + return NULL; + + if ( ng_type != SRP_NG_CUSTOM ) + { + n_hex = global_Ng_constants[ ng_type ].n_hex; + g_hex = global_Ng_constants[ ng_type ].g_hex; + } + + bnum_hex2bn(ng->N, n_hex); + bnum_hex2bn(ng->g, g_hex); + + return ng; +} + +static void +free_ng(NGConstant * ng) +{ + if (!ng) + return; + + bnum_free(ng->N); + bnum_free(ng->g); + free(ng); +} + +static int +hash_init(enum hash_alg alg, HashCTX *c) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Init(&c->sha); + case HASH_SHA224: return SHA224_Init(&c->sha256); + case HASH_SHA256: return SHA256_Init(&c->sha256); + case HASH_SHA384: return SHA384_Init(&c->sha512); + case HASH_SHA512: return SHA512_Init(&c->sha512); + default: + return -1; + }; +#elif CONFIG_GCRYPT + return gcry_md_open(c, alg, 0); +#endif +} + +static int +hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Update(&c->sha, data, len); + case HASH_SHA224: return SHA224_Update(&c->sha256, data, len); + case HASH_SHA256: return SHA256_Update(&c->sha256, data, len); + case HASH_SHA384: return SHA384_Update(&c->sha512, data, len); + case HASH_SHA512: return SHA512_Update(&c->sha512, data, len); + default: + return -1; + }; +#elif CONFIG_GCRYPT + gcry_md_write(*c, data, len); + return 0; +#endif +} + +static int +hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Final(md, &c->sha); + case HASH_SHA224: return SHA224_Final(md, &c->sha256); + case HASH_SHA256: return SHA256_Final(md, &c->sha256); + case HASH_SHA384: return SHA384_Final(md, &c->sha512); + case HASH_SHA512: return SHA512_Final(md, &c->sha512); + default: + return -1; + }; +#elif CONFIG_GCRYPT + unsigned char *buf = gcry_md_read(*c, alg); + if (!buf) + return -1; + + memcpy(md, buf, gcry_md_get_algo_dlen(alg)); + gcry_md_close(*c); + return 0; +#endif +} + +static unsigned char * +hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1(d, n, md); + case HASH_SHA224: return SHA224(d, n, md); + case HASH_SHA256: return SHA256(d, n, md); + case HASH_SHA384: return SHA384(d, n, md); + case HASH_SHA512: return SHA512(d, n, md); + default: + return NULL; + }; +#elif CONFIG_GCRYPT + gcry_md_hash_buffer(alg, md, d, n); + return md; +#endif +} + +static int +hash_length(enum hash_alg alg) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA_DIGEST_LENGTH; + case HASH_SHA224: return SHA224_DIGEST_LENGTH; + case HASH_SHA256: return SHA256_DIGEST_LENGTH; + case HASH_SHA384: return SHA384_DIGEST_LENGTH; + case HASH_SHA512: return SHA512_DIGEST_LENGTH; + default: + return -1; + }; +#elif CONFIG_GCRYPT + return gcry_md_get_algo_dlen(alg); +#endif +} + +static int +hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len) +{ + HashCTX ctx; + + hash_init(alg, &ctx); + hash_update(alg, &ctx, m1, m1_len); + hash_update(alg, &ctx, m2, m2_len); + return hash_final(alg, &ctx, md); +} + +static bnum +H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2) +{ + bnum bn; + unsigned char *bin; + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n1 = bnum_num_bytes(n1); + int len_n2 = bnum_num_bytes(n2); + int nbytes = 2 * len_n1; + + if ((len_n2 < 1) || (len_n2 > len_n1)) + return 0; + + bin = calloc( 1, nbytes ); + if (!bin) + return 0; + + bnum_bn2bin(n1, bin, len_n1); + bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2); + hash( alg, bin, nbytes, buff ); + free(bin); + bnum_bin2bn(bn, buff, hash_length(alg)); + return bn; +} + +static bnum +H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes) +{ + bnum bn; + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n = bnum_num_bytes(n); + int nbytes = len_n + len_bytes; + unsigned char *bin = malloc(nbytes); + if (!bin) + return 0; + + bnum_bn2bin(n, bin, len_n); + memcpy( bin + len_n, bytes, len_bytes ); + hash( alg, bin, nbytes, buff ); + free(bin); + bnum_bin2bn(bn, buff, hash_length(alg)); + return bn; +} + +static bnum +calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len) +{ + unsigned char ucp_hash[SHA512_DIGEST_LENGTH]; + HashCTX ctx; + + hash_init( alg, &ctx ); + hash_update( alg, &ctx, username, strlen(username) ); + hash_update( alg, &ctx, ":", 1 ); + hash_update( alg, &ctx, password, password_len ); + hash_final( alg, &ctx, ucp_hash ); + + return H_ns( alg, salt, ucp_hash, hash_length(alg) ); +} + +static void +update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n) +{ + unsigned long len = bnum_num_bytes(n); + unsigned char *n_bytes = malloc(len); + if (!n_bytes) + return; + bnum_bn2bin(n, n_bytes, len); + hash_update(alg, ctx, n_bytes, len); + free(n_bytes); +} + +static void +hash_num(enum hash_alg alg, const bnum n, unsigned char *dest) +{ + int nbytes = bnum_num_bytes(n); + unsigned char *bin = malloc(nbytes); + if(!bin) + return; + bnum_bn2bin(n, bin, nbytes); + hash( alg, bin, nbytes, dest ); + free(bin); +} + +static int +hash_session_key(enum hash_alg alg, const bnum n, unsigned char *dest) +{ + int nbytes = bnum_num_bytes(n); + unsigned char *bin = malloc(nbytes); + unsigned char fourbytes[4] = { 0 }; // Only God knows the reason for this, and perhaps some poor soul at Apple + if(!bin) + return 0; + bnum_bn2bin(n, bin, nbytes); + + hash_ab(alg, dest, bin, nbytes, fourbytes, sizeof(fourbytes)); + + fourbytes[3] = 1; // Again, only ... + + hash_ab(alg, dest + hash_length(alg), bin, nbytes, fourbytes, sizeof(fourbytes)); + + free(bin); + + return (2 * hash_length(alg)); +} + +static void +calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s, + const bnum A, const bnum B, const unsigned char *K, int K_len) +{ + unsigned char H_N[ SHA512_DIGEST_LENGTH ]; + unsigned char H_g[ SHA512_DIGEST_LENGTH ]; + unsigned char H_I[ SHA512_DIGEST_LENGTH ]; + unsigned char H_xor[ SHA512_DIGEST_LENGTH ]; + HashCTX ctx; + int i = 0; + int hash_len = hash_length(alg); + + hash_num( alg, ng->N, H_N ); + hash_num( alg, ng->g, H_g ); + + hash(alg, (const unsigned char *)I, strlen(I), H_I); + + for (i=0; i < hash_len; i++ ) + H_xor[i] = H_N[i] ^ H_g[i]; + + hash_init( alg, &ctx ); + + hash_update( alg, &ctx, H_xor, hash_len ); + hash_update( alg, &ctx, H_I, hash_len ); + update_hash_n( alg, &ctx, s ); + update_hash_n( alg, &ctx, A ); + update_hash_n( alg, &ctx, B ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static void +calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len) +{ + HashCTX ctx; + + hash_init( alg, &ctx ); + + update_hash_n( alg, &ctx, A ); + hash_update( alg, &ctx, M, hash_length(alg) ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static struct SRPUser * +srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_password, int len_password, + const char *n_hex, const char *g_hex) +{ + struct SRPUser *usr = calloc(1, sizeof(struct SRPUser)); + int ulen = strlen(username) + 1; + + if (!usr) + goto err_exit; + + usr->alg = alg; + usr->ng = new_ng( ng_type, n_hex, g_hex ); + + bnum_new(usr->a); + bnum_new(usr->A); + bnum_new(usr->S); + + if (!usr->ng || !usr->a || !usr->A || !usr->S) + goto err_exit; + + usr->username = (const char *) malloc(ulen); + usr->password = (const unsigned char *) malloc(len_password); + usr->password_len = len_password; + + if (!usr->username || !usr->password) + goto err_exit; + + memcpy((char *)usr->username, username, ulen); + memcpy((char *)usr->password, bytes_password, len_password); + + usr->authenticated = 0; + usr->bytes_A = 0; + + return usr; + + err_exit: + if (!usr) + return NULL; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + if (usr->username) + free((void*)usr->username); + if (usr->password) + { + memset((void*)usr->password, 0, usr->password_len); + free((void*)usr->password); + } + free(usr); + + return NULL; +} + +static void +srp_user_delete(struct SRPUser *usr) +{ + if(!usr) + return; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free_ng(usr->ng); + + memset((void*)usr->password, 0, usr->password_len); + + free((char *)usr->username); + free((char *)usr->password); + + if (usr->bytes_A) + free( (char *)usr->bytes_A ); + + memset(usr, 0, sizeof(*usr)); + free(usr); +} + +static int +srp_user_is_authenticated(struct SRPUser *usr) +{ + return usr->authenticated; +} + +static const unsigned char * +srp_user_get_session_key(struct SRPUser *usr, int *key_length) +{ + if (key_length) + *key_length = usr->session_key_len; + return usr->session_key; +} + +/* Output: username, bytes_A, len_A */ +static void +srp_user_start_authentication(struct SRPUser *usr, const char **username, + const unsigned char **bytes_A, int *len_A) +{ + bnum_random(usr->a, 256); + bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N); + + *len_A = bnum_num_bytes(usr->A); + *bytes_A = malloc(*len_A); + + if (!*bytes_A) + { + *len_A = 0; + *bytes_A = 0; + *username = 0; + return; + } + + bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A); + + usr->bytes_A = *bytes_A; + *username = usr->username; +} + +/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */ +static void +srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_B, int len_B, + const unsigned char **bytes_M, int *len_M ) +{ + bnum s, B, k, v; + bnum tmp1, tmp2, tmp3; + bnum u, x; + + *len_M = 0; + *bytes_M = 0; + + bnum_bin2bn(s, bytes_s, len_s); + bnum_bin2bn(B, bytes_B, len_B); + k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g); + bnum_new(v); + bnum_new(tmp1); + bnum_new(tmp2); + bnum_new(tmp3); + + if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3) + goto cleanup1; + + u = H_nn_pad(usr->alg, usr->A, B); + x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len); + if (!u || !x) + goto cleanup2; + + // SRP-6a safety check + if (!bnum_is_zero(B) && !bnum_is_zero(u)) + { + bnum_modexp(v, usr->ng->g, x, usr->ng->N); + + // S = (B - k*(g^x)) ^ (a + ux) + bnum_mul(tmp1, u, x); + bnum_add(tmp2, usr->a, tmp1); // tmp2 = (a + ux) + bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N); + bnum_mul(tmp3, k, tmp1); // tmp3 = k*(g^x) + bnum_sub(tmp1, B, tmp3); // tmp1 = (B - K*(g^x)) + bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N); + + usr->session_key_len = hash_session_key(usr->alg, usr->S, usr->session_key); + + calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len); + calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len); + + *bytes_M = usr->M; + if (len_M) + *len_M = hash_length(usr->alg); + } + else + { + *bytes_M = NULL; + if (len_M) + *len_M = 0; + } + + cleanup2: + bnum_free(x); + bnum_free(u); + cleanup1: + bnum_free(tmp3); + bnum_free(tmp2); + bnum_free(tmp1); + bnum_free(v); + bnum_free(k); + bnum_free(B); + bnum_free(s); +} + +static void +srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) +{ + if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0) + usr->authenticated = 1; +} + + +/* -------------------------------- HELPERS -------------------------------- */ + +static int +encrypt_gcm(unsigned char *ciphertext, int ciphertext_len, unsigned char *tag, unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, const char **errmsg) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + *errmsg = NULL; + + if ( !(ctx = EVP_CIPHER_CTX_new()) || + (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) || + (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) != 1) || + (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) ) + { + *errmsg = "Error initialising AES 128 GCM encryption"; + goto error; + } + + if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1) + { + *errmsg = "Error GCM encrypting"; + goto error; + } + + if (len > ciphertext_len) + { + *errmsg = "Bug! Buffer overflow"; + goto error; + } + + if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) + { + *errmsg = "Error finalising GCM encryption"; + goto error; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTHTAG_LENGTH, tag) != 1) + { + *errmsg = "Error getting authtag"; + goto error; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + int ret; + + ret = gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0); + if (ret < 0) + { + *errmsg = "Error initialising AES 128 GCM encryption"; + return -1; + } + + if ( (gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128)) < 0) || + (gcry_cipher_setiv(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128)) < 0)) + { + *errmsg = "Could not set key or iv for AES 128 GCM"; + goto error; + } + + ret = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext, plaintext_len); + if (ret < 0) + { + *errmsg = "Error GCM encrypting"; + goto error; + } + + ret = gcry_cipher_gettag(hd, tag, AUTHTAG_LENGTH); + if (ret < 0) + { + *errmsg = "Error getting authtag"; + goto error; + } + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#endif +} + +static int +encrypt_ctr(unsigned char *ciphertext, int ciphertext_len, + unsigned char *plaintext1, int plaintext1_len, unsigned char *plaintext2, int plaintext2_len, + unsigned char *key, unsigned char *iv, const char **errmsg) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + *errmsg = NULL; + + if ( !(ctx = EVP_CIPHER_CTX_new()) || (EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv) != 1) ) + { + *errmsg = "Error initialising AES 128 CTR encryption"; + goto error; + } + + if ( (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext1, plaintext1_len) != 1) || + (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext2, plaintext2_len) != 1) ) + { + *errmsg = "Error CTR encrypting"; + goto error; + } + + if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) + { + *errmsg = "Error finalising encryption"; + goto error; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + + if (gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0) < 0) + { + *errmsg = "Error initialising AES 128 CTR encryption"; + return -1; + } + + if ( (gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128)) < 0) || + (gcry_cipher_setctr(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128)) < 0) ) + { + *errmsg = "Could not set key or iv for AES 128 CTR"; + goto error; + } + + if ( (gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext1, plaintext1_len) < 0) || + (gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext2, plaintext2_len) < 0) ) + { + *errmsg = "Error CTR encrypting"; + goto error; + } + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#endif +} + + +/* ---------------------------------- API ---------------------------------- */ + +struct verification_setup_context * +verification_setup_new(const char *pin) +{ + struct verification_setup_context *sctx; + + if (sodium_init() == -1) + return NULL; + + sctx = calloc(1, sizeof(struct verification_setup_context)); + if (!sctx) + return NULL; + + memcpy(sctx->pin, pin, sizeof(sctx->pin)); + + return sctx; +} + +void +verification_setup_free(struct verification_setup_context *sctx) +{ + if (!sctx) + return; + + srp_user_delete(sctx->user); + + free(sctx->pkB); + free(sctx->M2); + free(sctx->salt); + free(sctx->epk); + free(sctx->authtag); + + free(sctx); +} + +const char * +verification_setup_errmsg(struct verification_setup_context *sctx) +{ + return sctx->errmsg; +} + +uint8_t * +verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx) +{ + plist_t dict; + plist_t method; + plist_t user; + char *data = NULL; // Necessary to initialize because plist_to_bin() uses value + + sctx->user = srp_user_new(HASH_SHA1, SRP_NG_2048, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0); + + dict = plist_new_dict(); + + method = plist_new_string("pin"); + user = plist_new_string(USERNAME); + + plist_dict_set_item(dict, "method", method); + plist_dict_set_item(dict, "user", user); + plist_to_bin(dict, &data, len); + plist_free(dict); + + return (uint8_t *)data; +} + +uint8_t * +verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx) +{ + plist_t dict; + plist_t pk; + plist_t proof; + const char *auth_username = NULL; + char *data = NULL; + + // Calculate A + srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len); + + // Calculate M1 (client proof) + srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len); + + pk = plist_new_data((char *)sctx->pkA, sctx->pkA_len); + proof = plist_new_data((char *)sctx->M1, sctx->M1_len); + + dict = plist_new_dict(); + plist_dict_set_item(dict, "pk", pk); + plist_dict_set_item(dict, "proof", proof); + plist_to_bin(dict, &data, len); + plist_free(dict); + + return (uint8_t *)data; +} + +uint8_t * +verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx) +{ + plist_t dict; + plist_t epk; + plist_t authtag; + char *data = NULL; + const unsigned char *session_key; + int session_key_len; + unsigned char key[SHA512_DIGEST_LENGTH]; + unsigned char iv[SHA512_DIGEST_LENGTH]; + unsigned char encrypted[128]; // Alloc a bit extra - should only need 2*16 + unsigned char tag[16]; + const char *errmsg; + int ret; + + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + sctx->errmsg = "Setup request 3: No valid session key"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_SETUP_KEY, strlen(AES_SETUP_KEY), session_key, session_key_len); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: Hashing of key string and shared secret failed"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_SETUP_IV, strlen(AES_SETUP_IV), session_key, session_key_len); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: Hashing of iv string and shared secret failed"; + return NULL; + } + + iv[15]++; // Magic +/* + if (iv[15] == 0x00 || iv[15] == 0xff) + printf("- note that value of last byte is %d!\n", iv[15]); +*/ + crypto_sign_keypair(sctx->public_key, sctx->private_key); + + ret = encrypt_gcm(encrypted, sizeof(encrypted), tag, sctx->public_key, sizeof(sctx->public_key), key, iv, &errmsg); + if (ret < 0) + { + sctx->errmsg = errmsg; + return NULL; + } + + epk = plist_new_data((char *)encrypted, EPK_LENGTH); + authtag = plist_new_data((char *)tag, AUTHTAG_LENGTH); + + dict = plist_new_dict(); + plist_dict_set_item(dict, "epk", epk); + plist_dict_set_item(dict, "authTag", authtag); + plist_to_bin(dict, &data, len); + plist_free(dict); + + return (uint8_t *)data; +} + +int +verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + plist_t dict; + plist_t pk; + plist_t salt; + + plist_from_bin((const char *)data, data_len, &dict); + + pk = plist_dict_get_item(dict, "pk"); + salt = plist_dict_get_item(dict, "salt"); + if (!pk || !salt) + { + sctx->errmsg = "Setup response 1: Missing pk or salt"; + plist_free(dict); + return -1; + } + + plist_get_data_val(pk, (char **)&sctx->pkB, &sctx->pkB_len); // B + plist_get_data_val(salt, (char **)&sctx->salt, &sctx->salt_len); + + plist_free(dict); + + return 0; +} + +int +verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + plist_t dict; + plist_t proof; + + plist_from_bin((const char *)data, data_len, &dict); + + proof = plist_dict_get_item(dict, "proof"); + if (!proof) + { + sctx->errmsg = "Setup response 2: Missing proof"; + plist_free(dict); + return -1; + } + + plist_get_data_val(proof, (char **)&sctx->M2, &sctx->M2_len); // M2 + + plist_free(dict); + + // Check M2 + srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2); + if (!srp_user_is_authenticated(sctx->user)) + { + sctx->errmsg = "Setup response 2: Server authentication failed"; + return -1; + } + + return 0; +} + +int +verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + plist_t dict; + plist_t epk; + plist_t authtag; + + plist_from_bin((const char *)data, data_len, &dict); + + epk = plist_dict_get_item(dict, "epk"); + if (!epk) + { + sctx->errmsg = "Setup response 3: Missing epk"; + plist_free(dict); + return -1; + } + + plist_get_data_val(epk, (char **)&sctx->epk, &sctx->epk_len); + + authtag = plist_dict_get_item(dict, "authTag"); + if (!authtag) + { + sctx->errmsg = "Setup response 3: Missing authTag"; + plist_free(dict); + return -1; + } + + plist_get_data_val(authtag, (char **)&sctx->authtag, &sctx->authtag_len); + + plist_free(dict); + + return 0; +} + +int +verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx) +{ + struct verification_verify_context *vctx; + char *ptr; + int i; + + if (sizeof(vctx->client_public_key) != sizeof(sctx->public_key) || sizeof(vctx->client_private_key) != sizeof(sctx->private_key)) + { + sctx->errmsg = "Setup result: Bug!"; + return -1; + } + + // Fills out the auth_key with public + private in hex. It seems that the private + // key actually includes the public key (last 32 bytes), so we could in + // principle just export the private key + ptr = sctx->auth_key; + for (i = 0; i < sizeof(sctx->public_key); i++) + ptr += sprintf(ptr, "%02x", sctx->public_key[i]); + for (i = 0; i < sizeof(sctx->private_key); i++) + ptr += sprintf(ptr, "%02x", sctx->private_key[i]); + *ptr = '\0'; + + *authorisation_key = sctx->auth_key; + return 0; +} + + +struct verification_verify_context * +verification_verify_new(const char *authorisation_key) +{ + struct verification_verify_context *vctx; + char hex[] = { 0, 0, 0 }; + const char *ptr; + int i; + + if (sodium_init() == -1) + return NULL; + + vctx = calloc(1, sizeof(struct verification_verify_context)); + if (!vctx) + return NULL; + + if (strlen(authorisation_key) != 2 * (sizeof(vctx->client_public_key) + sizeof(vctx->client_private_key))) + return NULL; + + ptr = authorisation_key; + for (i = 0; i < sizeof(vctx->client_public_key); i++, ptr+=2) + { + hex[0] = ptr[0]; + hex[1] = ptr[1]; + vctx->client_public_key[i] = strtol(hex, NULL, 16); + } + for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2) + { + hex[0] = ptr[0]; + hex[1] = ptr[1]; + vctx->client_private_key[i] = strtol(hex, NULL, 16); + } + + return vctx; +} + +void +verification_verify_free(struct verification_verify_context *vctx) +{ + if (!vctx) + return; + + free(vctx); +} + +const char * +verification_verify_errmsg(struct verification_verify_context *vctx) +{ + return vctx->errmsg; +} + +uint8_t * +verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx) +{ + const uint8_t basepoint[32] = {9}; + uint8_t *data; + int ret; + + ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint); + if (ret < 0) + { + vctx->errmsg = "Verify request 1: Curve 25519 returned an error"; + return NULL; + } + + *len = 4 + sizeof(vctx->client_eph_public_key) + sizeof(vctx->client_public_key); + data = calloc(1, *len); + if (!data) + { + vctx->errmsg = "Verify request 1: Out of memory"; + return NULL; + } + + data[0] = 1; // Magic + memcpy(data + 4, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + memcpy(data + 4 + sizeof(vctx->client_eph_public_key), vctx->client_public_key, sizeof(vctx->client_public_key)); + + return data; +} + +uint8_t * +verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx) +{ + uint8_t shared_secret[crypto_scalarmult_BYTES]; + uint8_t key[SHA512_DIGEST_LENGTH]; + uint8_t iv[SHA512_DIGEST_LENGTH]; + uint8_t encrypted[128]; // Alloc a bit extra, should only really need size of public key len + uint8_t signature[crypto_sign_BYTES]; + uint8_t *data; + int ret; + const char *errmsg; + + *len = sizeof(vctx->client_eph_public_key) + sizeof(vctx->server_eph_public_key); + data = calloc(1, *len); + if (!data) + { + vctx->errmsg = "Verify request 2: Out of memory"; + return NULL; + } + + memcpy(data, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + memcpy(data + sizeof(vctx->client_eph_public_key), vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key)); + + crypto_sign_detached(signature, NULL, data, *len, vctx->client_private_key); + + free(data); + + ret = crypto_scalarmult(shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: Curve 25519 returned an error"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_VERIFY_KEY, strlen(AES_VERIFY_KEY), shared_secret, sizeof(shared_secret)); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: Hashing of key string and shared secret failed"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_VERIFY_IV, strlen(AES_VERIFY_IV), shared_secret, sizeof(shared_secret)); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: Hashing of iv string and shared secret failed"; + return NULL; + } + + ret = encrypt_ctr(encrypted, sizeof(encrypted), vctx->server_public_key, sizeof(vctx->server_public_key), signature, sizeof(signature), key, iv, &errmsg); + if (ret < 0) + { + vctx->errmsg = errmsg; + return NULL; + } + + *len = 4 + sizeof(vctx->server_public_key); + data = calloc(1, *len); + if (!data) + { + vctx->errmsg = "Verify request 2: Out of memory"; + return NULL; + } + + memcpy(data + 4, encrypted, sizeof(vctx->server_public_key)); + + return data; +} + +int +verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len) +{ + uint32_t wanted; + + wanted = sizeof(vctx->server_eph_public_key) + sizeof(vctx->server_public_key); + if (data_len < wanted) + { + vctx->errmsg = "Verify response 2: Unexpected response (too short)"; + return -1; + } + + memcpy(vctx->server_eph_public_key, data, sizeof(vctx->server_eph_public_key)); + memcpy(vctx->server_public_key, data + sizeof(vctx->server_eph_public_key), sizeof(vctx->server_public_key)); + + return 0; +} diff --git a/src/outputs/raop_verification.h b/src/outputs/raop_verification.h new file mode 100644 index 00000000..e175a087 --- /dev/null +++ b/src/outputs/raop_verification.h @@ -0,0 +1,66 @@ +#ifndef __VERIFICATION_H__ +#define __VERIFICATION_H__ + +#include + +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__ */ diff --git a/src/player.c b/src/player.c index 8f3d96b6..1b835134 100644 --- a/src/player.c +++ b/src/player.c @@ -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) { @@ -3020,6 +3045,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 +3107,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); } diff --git a/src/player.h b/src/player.h index 6960a69d..b69ab51f 100644 --- a/src/player.h +++ b/src/player.h @@ -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); From 0c30cab5579b7ec7c9997a1851c3c6fcdbc7f926 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Jun 2017 22:15:48 +0200 Subject: [PATCH 08/18] [docs] Update README and INSTALL with info about Apple TV device verification --- INSTALL | 42 +++++++++++++++++++++++++----------------- README.md | 8 ++++++++ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/INSTALL b/INSTALL index c11baa37..fb77711d 100644 --- a/INSTALL +++ b/INSTALL @@ -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 - - libplist 0.16+ (optional - iTunes XML support) + - libplist 0.16+ (optional - iTunes XML support and Apple TV device verification) from + - libsodium (optional - Apple TV device verification) + from - libspotify (optional - Spotify support) from - 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. diff --git a/README.md b/README.md index 288aa7b4..3ee24535 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,14 @@ 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. 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. + +For troubleshooting, see [using Remote](#using-remote). + ## Chromecast From d169ad3141bb436db5f7493c8947d9aa1a20e6be Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Jun 2017 23:45:39 +0200 Subject: [PATCH 09/18] [docs] Remove remark from README about tvOS 10.2 being unsupported --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 3ee24535..5b78ad30 100644 --- a/README.md +++ b/README.md @@ -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. From 2300116d53d170534ddbbc6e39dc313278f29ea5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 19 Jun 2017 23:46:10 +0200 Subject: [PATCH 10/18] Update .travis.yml with libsodium and --disable-verification configure option --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1db565be..a0105f97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,9 @@ env: matrix: - CFG="" - CFG="--enable-lastfm" - - CFG="--enable-itunes" - CFG="--enable-spotify" - CFG="--enable-chromecast" + - CFG="--disable-verification" - CFG="--with-pulseaudio" script: @@ -19,7 +19,7 @@ before_install: - wget -q -O - https://apt.mopidy.com/mopidy.gpg | sudo apt-key add - - sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/jessie.list - sudo apt-get -qq update - - sudo apt-get install -y build-essential clang git autotools-dev autoconf libtool gettext gawk gperf 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 libplist-dev libcurl4-openssl-dev libjson-c-dev libspotify-dev libgnutls-dev libprotobuf-c0-dev libpulse-dev + - sudo apt-get install -y build-essential clang git autotools-dev autoconf libtool gettext gawk gperf 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 libplist-dev libcurl4-openssl-dev libjson-c-dev libspotify-dev libgnutls-dev libprotobuf-c0-dev libpulse-dev libsodium-dev # Disable email notification notifications: From 7626b6c53508cc0957a197c81e5759e44b18faa5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 20:00:05 +0200 Subject: [PATCH 11/18] [raop] Fix bad enum --- src/outputs/raop.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 39f792ef..c30ab471 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -125,31 +125,33 @@ enum raop_devtype { }; // Session is starting up -#define RAOP_STATE_F_STARTUP (1 << 14) +#define RAOP_STATE_F_STARTUP (1 << 13) // Streaming is up (connection established) -#define RAOP_STATE_F_CONNECTED (1 << 15) +#define RAOP_STATE_F_CONNECTED (1 << 14) +// Couldn't start device +#define RAOP_STATE_F_FAILED (1 << 15) enum raop_state { // Device is stopped (no session) RAOP_STATE_STOPPED = 0, // Session startup - RAOP_STATE_STARTUP = RAOP_STATE_F_STARTUP, - RAOP_STATE_OPTIONS = RAOP_STATE_F_STARTUP | 0x01, - RAOP_STATE_ANNOUNCE = RAOP_STATE_F_STARTUP | 0x02, - RAOP_STATE_SETUP = RAOP_STATE_F_STARTUP | 0x03, - RAOP_STATE_RECORD = RAOP_STATE_F_STARTUP | 0x04, + RAOP_STATE_STARTUP = RAOP_STATE_F_STARTUP | 0x01, + RAOP_STATE_OPTIONS = RAOP_STATE_F_STARTUP | 0x02, + RAOP_STATE_ANNOUNCE = RAOP_STATE_F_STARTUP | 0x03, + RAOP_STATE_SETUP = RAOP_STATE_F_STARTUP | 0x04, + RAOP_STATE_RECORD = RAOP_STATE_F_STARTUP | 0x05, // Session established // - streaming ready (RECORD sent and acked, connection established) // - commands (SET_PARAMETER) are possible - RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED, + RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED | 0x01, // Media data is being sent - RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x01, + RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x02, // Session is failed, couldn't startup or error occurred - RAOP_STATE_FAILED = -1, + RAOP_STATE_FAILED = RAOP_STATE_F_FAILED | 0x01, // Password issue: unknown password or bad password - RAOP_STATE_PASSWORD = -2, + RAOP_STATE_PASSWORD = RAOP_STATE_F_FAILED | 0x02, // Device requires verification, pending PIN from user - RAOP_STATE_UNVERIFIED= -3, + RAOP_STATE_UNVERIFIED= RAOP_STATE_F_FAILED | 0x03, }; // Info about the device, which is not required by the player, only internally @@ -1743,7 +1745,7 @@ raop_status(struct raop_session *rs) switch (rs->state) { - case RAOP_STATE_UNVERIFIED ... RAOP_STATE_PASSWORD: + case RAOP_STATE_PASSWORD ... RAOP_STATE_UNVERIFIED: state = OUTPUT_STATE_PASSWORD; break; case RAOP_STATE_FAILED: From 5e48a6852502bb32c3e111cdf319e993157cce75 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 20:58:46 +0200 Subject: [PATCH 12/18] [player] device_restart_cb() should also handle failures due to device verification --- src/player.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/player.c b/src/player.c index 1b835134..50bd49bc 100644 --- a/src/player.c +++ b/src/player.c @@ -1676,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) { @@ -1688,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); @@ -1698,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; } @@ -1707,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 */ From 299a8f9e4bbfea6ea0443fc816299a001e21a8b5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 21:01:55 +0200 Subject: [PATCH 13/18] [mpd] Add support for "mpc sendmessage verification [pincode]" --- src/mpd.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mpd.c b/src/mpd.c index c3f4d77b..e9dee23a 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -3523,6 +3523,12 @@ channel_pairing(const char *message) remote_pairing_kickoff((char **)&message); } +static void +channel_verification(const char *message) +{ + player_raop_verification_kickoff((char **)&message); +} + struct mpd_channel { /* The channel name */ @@ -3546,6 +3552,10 @@ static struct mpd_channel mpd_channels[] = .channel = "pairing", .handler = channel_pairing }, + { + .channel = "verification", + .handler = channel_verification + }, { .channel = NULL, .handler = NULL From 9369e97753cb5fdadfeb3970b398862b07dcd26a Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 21:17:17 +0200 Subject: [PATCH 14/18] [raop] Lower log level of verification success slightly --- src/outputs/raop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outputs/raop.c b/src/outputs/raop.c index c30ab471..07459058 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -4142,7 +4142,7 @@ raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg) if (ret < 0) goto error; - DPRINTF(E_LOG, L_RAOP, "Verification of '%s' completed succesfully\n", rs->devname); + DPRINTF(E_INFO, L_RAOP, "Verification of '%s' completed succesfully\n", rs->devname); rs->state = RAOP_STATE_STARTUP; From 1d77cdd9d000bad33c366a90dcbf62560b5452bf Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 21:21:06 +0200 Subject: [PATCH 15/18] [docs] Another README update --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b78ad30..59e420a3 100644 --- a/README.md +++ b/README.md @@ -206,9 +206,13 @@ 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. 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. +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). From aa20a268bbccb8ef4f59b1c4cb8c0909e4c0d595 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 21:29:38 +0200 Subject: [PATCH 16/18] [travis] Travis doesn't have libsodium, so disable verification --- .travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a0105f97..511e546c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,11 @@ sudo: required dist: trusty env: matrix: - - CFG="" - - CFG="--enable-lastfm" - - CFG="--enable-spotify" - - CFG="--enable-chromecast" - CFG="--disable-verification" - - CFG="--with-pulseaudio" + - CFG="--enable-lastfm --disable-verification" + - CFG="--enable-spotify --disable-verification" + - CFG="--enable-chromecast --disable-verification" + - CFG="--with-pulseaudio --disable-verification" script: - autoreconf -fi @@ -19,7 +18,7 @@ before_install: - wget -q -O - https://apt.mopidy.com/mopidy.gpg | sudo apt-key add - - sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/jessie.list - sudo apt-get -qq update - - sudo apt-get install -y build-essential clang git autotools-dev autoconf libtool gettext gawk gperf 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 libplist-dev libcurl4-openssl-dev libjson-c-dev libspotify-dev libgnutls-dev libprotobuf-c0-dev libpulse-dev libsodium-dev + - sudo apt-get install -y build-essential clang git autotools-dev autoconf libtool gettext gawk gperf 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 libplist-dev libcurl4-openssl-dev libjson-c-dev libspotify-dev libgnutls-dev libprotobuf-c0-dev libpulse-dev # Disable email notification notifications: From 20128e2235c30f2cc3a7d090638b1800a9750eb8 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 21:36:21 +0200 Subject: [PATCH 17/18] [raop] Raise log level of verification setup complete message --- src/outputs/raop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 07459058..317cacd1 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -4225,7 +4225,7 @@ raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg) goto error; } - DPRINTF(E_INFO, L_RAOP, "Verification setup stage complete, saving authorization key\n"); + DPRINTF(E_LOG, L_RAOP, "Verification setup stage complete, saving authorization key\n"); free(rs->device->auth_key); rs->device->auth_key = strdup(authorization_key); From 01308f7f83dc21e4f426478656371c86d46a65ab Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 20 Jun 2017 22:07:49 +0200 Subject: [PATCH 18/18] Misc fixup to keep scan-build happy --- src/misc.c | 2 +- src/outputs/raop_verification.c | 22 +++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/misc.c b/src/misc.c index c87bb63f..aa1e77bf 100644 --- a/src/misc.c +++ b/src/misc.c @@ -565,7 +565,7 @@ m_readfile(const char *path, int num_lines) } lines[i] = trimwhitespace(line); - if (strlen(lines[i]) == 0) + if (!lines[i] || (strlen(lines[i]) == 0)) { DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path); goto error; diff --git a/src/outputs/raop_verification.c b/src/outputs/raop_verification.c index c9975dce..e697211d 100644 --- a/src/outputs/raop_verification.c +++ b/src/outputs/raop_verification.c @@ -257,8 +257,6 @@ static NGConstant * new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) { NGConstant *ng = calloc(1, sizeof(NGConstant)); - if(!ng) - return NULL; if ( ng_type != SRP_NG_CUSTOM ) { @@ -411,8 +409,6 @@ H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2) return 0; bin = calloc( 1, nbytes ); - if (!bin) - return 0; bnum_bn2bin(n1, bin, len_n1); bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2); @@ -430,8 +426,6 @@ H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes) int len_n = bnum_num_bytes(n); int nbytes = len_n + len_bytes; unsigned char *bin = malloc(nbytes); - if (!bin) - return 0; bnum_bn2bin(n, bin, len_n); memcpy( bin + len_n, bytes, len_bytes ); @@ -461,8 +455,6 @@ update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n) { unsigned long len = bnum_num_bytes(n); unsigned char *n_bytes = malloc(len); - if (!n_bytes) - return; bnum_bn2bin(n, n_bytes, len); hash_update(alg, ctx, n_bytes, len); free(n_bytes); @@ -473,8 +465,6 @@ hash_num(enum hash_alg alg, const bnum n, unsigned char *dest) { int nbytes = bnum_num_bytes(n); unsigned char *bin = malloc(nbytes); - if(!bin) - return; bnum_bn2bin(n, bin, nbytes); hash( alg, bin, nbytes, dest ); free(bin); @@ -486,8 +476,7 @@ hash_session_key(enum hash_alg alg, const bnum n, unsigned char *dest) int nbytes = bnum_num_bytes(n); unsigned char *bin = malloc(nbytes); unsigned char fourbytes[4] = { 0 }; // Only God knows the reason for this, and perhaps some poor soul at Apple - if(!bin) - return 0; + bnum_bn2bin(n, bin, nbytes); hash_ab(alg, dest, bin, nbytes, fourbytes, sizeof(fourbytes)); @@ -518,7 +507,7 @@ calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char * hash(alg, (const unsigned char *)I, strlen(I), H_I); - for (i=0; i < hash_len; i++ ) + for (i = 0; i < hash_len; i++) H_xor[i] = H_N[i] ^ H_g[i]; hash_init( alg, &ctx ); @@ -1189,13 +1178,16 @@ verification_verify_new(const char *authorisation_key) if (sodium_init() == -1) return NULL; - vctx = calloc(1, sizeof(struct verification_verify_context)); - if (!vctx) + if (!authorisation_key) return NULL; if (strlen(authorisation_key) != 2 * (sizeof(vctx->client_public_key) + sizeof(vctx->client_private_key))) return NULL; + vctx = calloc(1, sizeof(struct verification_verify_context)); + if (!vctx) + return NULL; + ptr = authorisation_key; for (i = 0; i < sizeof(vctx->client_public_key); i++, ptr+=2) {