/* Thread: filescanner */ int lastfm_login(char *path) { struct keyval *kv; char *username; char *password; int ret; DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n"); // Delete any existing session key if (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) { free(username); free(password); return -1; } ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) && (keyval_add(kv, "username", username) == 0) && (keyval_add(kv, "password", password) == 0) ); free(username); free(password); // Send the login request ret = request_post("auth.getMobileSession", kv, 1); keyval_clear(kv); free(kv); return ret; }
static int request_post(char *method, struct keyval *kv, int auth) { struct https_client_ctx ctx; char *body; int ret; ret = keyval_add(kv, "method", method); if (ret < 0) return -1; if (!auth) ret = keyval_add(kv, "sk", lastfm_session_key); if (ret < 0) return -1; // API requires that we MD5 sign sorted param (without "format" param) keyval_sort(kv); ret = param_sign(kv); if (ret < 0) { DPRINTF(E_LOG, L_LASTFM, "Aborting request, param_sign failed\n"); return -1; } ret = body_print(&body, kv); if (ret < 0) { DPRINTF(E_LOG, L_LASTFM, "Aborting request, body_print failed\n"); return -1; } memset(&ctx, 0, sizeof(struct https_client_ctx)); ctx.url = auth ? auth_url : api_url; ctx.body = body; ret = https_client_request(&ctx); return ret; }
/* Creates an md5 signature of the concatenated parameters and adds it to keyval */ static int param_sign(struct keyval *kv) { struct onekeyval *okv; char hash[33]; char ebuf[64]; uint8_t *hash_bytes; size_t hash_len; gcry_md_hd_t md_hdl; gpg_error_t gc_err; int ret; int i; gc_err = gcry_md_open(&md_hdl, GCRY_MD_MD5, 0); if (gc_err != GPG_ERR_NO_ERROR) { gpg_strerror_r(gc_err, ebuf, sizeof(ebuf)); DPRINTF(E_LOG, L_LASTFM, "Could not open MD5: %s\n", ebuf); return -1; } for (okv = kv->head; okv; okv = okv->next) { gcry_md_write(md_hdl, okv->name, strlen(okv->name)); gcry_md_write(md_hdl, okv->value, strlen(okv->value)); } gcry_md_write(md_hdl, lastfm_secret, strlen(lastfm_secret)); hash_bytes = gcry_md_read(md_hdl, GCRY_MD_MD5); if (!hash_bytes) { DPRINTF(E_LOG, L_LASTFM, "Could not read MD5 hash\n"); return -1; } hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5); for (i = 0; i < hash_len; i++) sprintf(hash + (2 * i), "%02x", hash_bytes[i]); ret = keyval_add(kv, "api_sig", hash); gcry_md_close(md_hdl); return ret; }
/* Copies headers we are searching for from one keyval struct to another * */ static void headers_save(struct keyval *kv, struct evkeyvalq *headers) { const char *value; int i; if (!kv || !headers) return; for (i = 0; i < (sizeof(header_list) / sizeof(header_list[0])); i++) { if ( (value = evhttp_find_header(headers, header_list[i])) ) keyval_add(kv, header_list[i], value); } }
static int scrobble(int id) { struct media_file_info *mfi; struct keyval *kv; char duration[4]; char trackNumber[4]; char timestamp[16]; int ret; mfi = db_file_fetch_byid(id); if (!mfi) { DPRINTF(E_LOG, L_LASTFM, "Scrobble failed, track id %d is unknown\n", id); return -1; } // Don't scrobble songs which are shorter than 30 sec if (mfi->song_length < 30000) goto noscrobble; // Don't scrobble non-music and radio stations if ((mfi->media_kind != MEDIA_KIND_MUSIC) || (mfi->data_kind == DATA_KIND_URL)) goto noscrobble; // Don't scrobble songs with unknown artist if (strcmp(mfi->artist, "Unknown artist") == 0) goto noscrobble; kv = keyval_alloc(); if (!kv) goto noscrobble; snprintf(duration, sizeof(duration), "%" PRIu32, mfi->song_length); snprintf(trackNumber, sizeof(trackNumber), "%" PRIu32, mfi->track); snprintf(timestamp, sizeof(timestamp), "%" PRIi64, (int64_t)time(NULL)); ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) && (keyval_add(kv, "sk", lastfm_session_key) == 0) && (keyval_add(kv, "artist", mfi->artist) == 0) && (keyval_add(kv, "track", mfi->title) == 0) && (keyval_add(kv, "album", mfi->album) == 0) && (keyval_add(kv, "albumArtist", mfi->album_artist) == 0) && (keyval_add(kv, "duration", duration) == 0) && (keyval_add(kv, "trackNumber", trackNumber) == 0) && (keyval_add(kv, "timestamp", timestamp) == 0) ); free_mfi(mfi, 0); if (!ret) { keyval_clear(kv); free(kv); return -1; } DPRINTF(E_INFO, L_LASTFM, "Scrobbling '%s' by '%s'\n", keyval_get(kv, "track"), keyval_get(kv, "artist")); ret = request_post("track.scrobble", kv, 0); keyval_clear(kv); free(kv); return ret; noscrobble: free_mfi(mfi, 0); return -1; }
static void browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtocol proto, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *hostname, const AvahiAddress *addr, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { AvahiRecordBrowser *rb; struct mdns_browser *mb; struct mdns_record_browser *rb_data; char *key; char *value; uint16_t dns_type; int family; int ret; mb = (struct mdns_browser *)userdata; if (event != AVAHI_RESOLVER_FOUND) { if (event == AVAHI_RESOLVER_FAILURE) DPRINTF(E_LOG, L_MDNS, "Avahi Resolver failure: service '%s' type '%s' proto %d: %s\n", name, type, proto, MDNSERR); else DPRINTF(E_LOG, L_MDNS, "Avahi Resolver empty callback\n"); family = avahi_proto_to_af(proto); if (family != AF_UNSPEC) mb->cb(name, type, domain, NULL, family, NULL, -1, NULL); // We don't clean up resolvers because we want a notification from them if // the service reappears (e.g. if device was switched off and then on) return; } DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d, host %s\n", name, type, proto, hostname); CHECK_NULL(L_MDNS, rb_data = calloc(1, sizeof(struct mdns_record_browser))); rb_data->name = strdup(name); rb_data->domain = strdup(domain); rb_data->mb = mb; rb_data->port = port; while (txt) { ret = avahi_string_list_get_pair(txt, &key, &value, NULL); txt = avahi_string_list_get_next(txt); if (ret < 0) continue; if (value) { keyval_add(&rb_data->txt_kv, key, value); avahi_free(value); } avahi_free(key); } if (proto == AVAHI_PROTO_INET6) dns_type = AVAHI_DNS_TYPE_AAAA; else dns_type = AVAHI_DNS_TYPE_A; // We need to implement a record browser because the announcement from some // devices (e.g. ApEx 1 gen) will include multiple records, and we need to // filter out those records that won't work (notably link-local). The value of // *addr given by browse_resolve_callback is just the first record. rb = avahi_record_browser_new(mdns_client, intf, proto, hostname, AVAHI_DNS_CLASS_IN, dns_type, 0, browse_record_callback, rb_data); if (!rb) DPRINTF(E_LOG, L_MDNS, "Could not create record browser for host %s: %s\n", hostname, MDNSERR); }
/* Thread: filescanner */ void lastfm_login(char *path) { struct lastfm_command *cmd; struct keyval *kv; char *username; char *password; int ret; DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n"); // Delete any existing session key if (g_session_key) free(g_session_key); g_session_key = NULL; db_admin_delete("lastfm_sk"); // Read the credentials file ret = credentials_read(path, &username, &password); if (ret < 0) return; // Enable LastFM now that we got a login attempt g_disabled = 0; kv = keyval_alloc(); if (!kv) { free(username); free(password); return; } ret = ( (keyval_add(kv, "api_key", g_api_key) == 0) && (keyval_add(kv, "username", username) == 0) && (keyval_add(kv, "password", password) == 0) ); free(username); free(password); if (!ret) { keyval_clear(kv); return; } // Spawn thread ret = lastfm_init(); if (ret < 0) { g_disabled = 1; return; } g_initialized = 1; // Send login command to the thread cmd = (struct lastfm_command *)malloc(sizeof(struct lastfm_command)); if (!cmd) { DPRINTF(E_LOG, L_LASTFM, "Could not allocate lastfm_command\n"); return; } memset(cmd, 0, sizeof(struct lastfm_command)); cmd->nonblock = 1; cmd->func = login; cmd->arg.kv = kv; nonblock_command(cmd); return; }