static int login(struct lastfm_command *cmd) { request_post("auth.getMobileSession", cmd->arg.kv, 1); keyval_clear(cmd->arg.kv); return 0; }
static void browse_record_callback(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto, AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type, const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) { struct mdns_record_browser *rb_data; AvahiAddress addr; char address[AVAHI_ADDRESS_STR_MAX]; int family; int ret; rb_data = (struct mdns_record_browser *)userdata; if (event == AVAHI_BROWSER_CACHE_EXHAUSTED) DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): no more results (CACHE_EXHAUSTED)\n", hostname, proto); else if (event == AVAHI_BROWSER_ALL_FOR_NOW) DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): no more results (ALL_FOR_NOW)\n", hostname, proto); else if (event == AVAHI_BROWSER_FAILURE) DPRINTF(E_LOG, L_MDNS, "Avahi Record Browser (%s, proto %d) failure: %s\n", hostname, proto, MDNSERR); else if (event == AVAHI_BROWSER_REMOVE) return; // Not handled - record browser lifetime too short for this to happen if (event != AVAHI_BROWSER_NEW) goto out_free_record_browser; ret = avahi_address_make(&addr, proto, rdata, size); // Not an avahi function despite the name if (ret < 0) return; family = avahi_proto_to_af(proto); avahi_address_snprint(address, sizeof(address), &addr); // Avahi will sometimes give us link-local addresses in 169.254.0.0/16 or // fe80::/10, which (most of the time) are useless // - see also https://lists.freedesktop.org/archives/avahi/2012-September/002183.html if ((proto == AVAHI_PROTO_INET && is_v4ll(&addr.data.ipv4)) || (proto == AVAHI_PROTO_INET6 && is_v6ll(&addr.data.ipv6))) { DPRINTF(E_WARN, L_MDNS, "Ignoring announcement from %s, address %s is link-local\n", hostname, address); return; } DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s, proto %d): NEW record %s for service type '%s'\n", hostname, proto, address, rb_data->mb->type); // Execute callback (mb->cb) with all the data rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, family, address, rb_data->port, &rb_data->txt_kv); // Stop record browser out_free_record_browser: keyval_clear(&rb_data->txt_kv); free(rb_data->name); free(rb_data->domain); free(rb_data); avahi_record_browser_free(b); }
static int mdns_addr_lookup_free(struct mdns_addr_lookup *lu) { if (! lu) return -1; if(lu->sdref) DNSServiceRefDeallocate(lu->sdref); keyval_clear(&lu->txt_kv); free(lu); return -1; }
/* 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 spawn_record_browser(AvahiClient *c, AvahiIfIndex intf, AvahiProtocol proto, const char *hostname, const char *domain, uint16_t type, struct mdns_browser *mb, const char *name, uint16_t port, AvahiStringList *txt) { AvahiRecordBrowser *rb; struct mdns_record_browser *rb_data; char *key; char *value; char *ptr; size_t len; int ret; rb_data = (struct mdns_record_browser *)malloc(sizeof(struct mdns_record_browser)); if (!rb_data) { DPRINTF(E_LOG, L_MDNS, "Out of memory for record browser data\n"); return -1; } memset(rb_data, 0, sizeof(struct mdns_record_browser)); rb_data->mb = mb; rb_data->port = port; rb_data->name = strdup(name); if (!rb_data->name) { DPRINTF(E_LOG, L_MDNS, "Out of memory for service name\n"); goto out_free_rb; } rb_data->domain = strdup(domain); if (!rb_data->domain) { DPRINTF(E_LOG, L_MDNS, "Out of memory for service domain\n"); goto out_free_name; } while (txt) { len = avahi_string_list_get_size(txt); key = (char *)avahi_string_list_get_text(txt); ptr = memchr(key, '=', len); if (!ptr) { value = ""; len = 0; } else { *ptr = '\0'; value = ptr + 1; len -= strlen(key) + 1; } ret = keyval_add_size(&rb_data->txt_kv, key, value, len); if (ptr) *ptr = '='; if (ret < 0) { DPRINTF(E_LOG, L_MDNS, "Could not build TXT record keyval\n"); goto out_free_keyval; } txt = avahi_string_list_get_next(txt); } rb = NULL; switch (type) { case AVAHI_DNS_TYPE_A: rb = avahi_record_browser_new(c, intf, proto, hostname, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A, 0, browse_record_callback_v4, rb_data); if (!rb) DPRINTF(E_LOG, L_MDNS, "Could not create v4 record browser for host %s: %s\n", hostname, avahi_strerror(avahi_client_errno(c))); break; case AVAHI_DNS_TYPE_AAAA: rb = avahi_record_browser_new(c, intf, proto, hostname, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA, 0, browse_record_callback_v6, rb_data); if (!rb) DPRINTF(E_LOG, L_MDNS, "Could not create v4 record browser for host %s: %s\n", hostname, avahi_strerror(avahi_client_errno(c))); break; } if (!rb) goto out_free_keyval; return 0; out_free_keyval: keyval_clear(&rb_data->txt_kv); free(rb_data->domain); out_free_name: free(rb_data->name); out_free_rb: free(rb_data); return -1; }
static void browse_record_callback_v6(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto, AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type, const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) { char address[INET6_ADDRSTRLEN + IF_NAMESIZE + 1]; char ifname[IF_NAMESIZE]; struct in6_addr addr; struct mdns_record_browser *rb_data; int ll; int len; int ret; rb_data = (struct mdns_record_browser *)userdata; switch (event) { case AVAHI_BROWSER_NEW: if (size != sizeof(addr.s6_addr)) { DPRINTF(E_WARN, L_MDNS, "Got RR type AAAA size %ld (should be %ld)\n", (long)size, (long)sizeof(addr.s6_addr)); return; } memcpy(&addr.s6_addr, rdata, sizeof(addr.s6_addr)); ll = is_v6ll(&addr); if (ll && !(rb_data->mb->flags & MDNS_WANT_V6LL)) { DPRINTF(E_DBG, L_MDNS, "Discarding IPv6 LL, not interested (service %s)\n", rb_data->name); return; } else if (!ll && !(rb_data->mb->flags & MDNS_WANT_V6)) { DPRINTF(E_DBG, L_MDNS, "Discarding IPv6, not interested (service %s)\n", rb_data->name); return; } if (!inet_ntop(AF_INET6, &addr.s6_addr, address, sizeof(address))) { DPRINTF(E_LOG, L_MDNS, "Could not print IPv6 address: %s\n", strerror(errno)); return; } if (ll) { if (!if_indextoname(intf, ifname)) { DPRINTF(E_LOG, L_MDNS, "Could not map interface index %d to a name\n", intf); return; } len = strlen(address); ret = snprintf(address + len, sizeof(address) - len, "%%%s", ifname); if ((ret < 0) || (ret > sizeof(address) - len)) { DPRINTF(E_LOG, L_MDNS, "Buffer too short for scoped IPv6 LL\n"); return; } } DPRINTF(E_DBG, L_MDNS, "Service %s, hostname %s resolved to %s\n", rb_data->name, hostname, address); /* Execute callback (mb->cb) with all the data */ rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, AF_INET6, address, rb_data->port, &rb_data->txt_kv); /* Got a suitable address, stop record browser */ break; case AVAHI_BROWSER_REMOVE: /* Not handled - record browser lifetime too short for this to happen */ return; case AVAHI_BROWSER_CACHE_EXHAUSTED: case AVAHI_BROWSER_ALL_FOR_NOW: DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s v6): no more results (%s)\n", hostname, (event == AVAHI_BROWSER_CACHE_EXHAUSTED) ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); break; case AVAHI_BROWSER_FAILURE: DPRINTF(E_LOG, L_MDNS, "Avahi Record Browser (%s v6) failure: %s\n", hostname, avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b)))); break; } /* Cleanup when done/error */ keyval_clear(&rb_data->txt_kv); free(rb_data->name); free(rb_data->domain); free(rb_data); avahi_record_browser_free(b); }
static void browse_record_callback_v4(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto, AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type, const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) { char address[INET_ADDRSTRLEN]; struct in_addr addr; struct mdns_record_browser *rb_data; int ll; rb_data = (struct mdns_record_browser *)userdata; switch (event) { case AVAHI_BROWSER_NEW: if (size != sizeof(addr.s_addr)) { DPRINTF(E_WARN, L_MDNS, "Got RR type A size %ld (should be %ld)\n", (long)size, (long)sizeof(addr.s_addr)); return; } memcpy(&addr.s_addr, rdata, sizeof(addr.s_addr)); ll = is_v4ll(&addr); if (ll && !(rb_data->mb->flags & MDNS_WANT_V4LL)) { DPRINTF(E_DBG, L_MDNS, "Discarding IPv4 LL, not interested (service %s)\n", rb_data->name); return; } else if (!ll && !(rb_data->mb->flags & MDNS_WANT_V4)) { DPRINTF(E_DBG, L_MDNS, "Discarding IPv4, not interested (service %s)\n", rb_data->name); return; } if (!inet_ntop(AF_INET, &addr.s_addr, address, sizeof(address))) { DPRINTF(E_LOG, L_MDNS, "Could not print IPv4 address: %s\n", strerror(errno)); return; } DPRINTF(E_DBG, L_MDNS, "Service %s, hostname %s resolved to %s\n", rb_data->name, hostname, address); /* Execute callback (mb->cb) with all the data */ rb_data->mb->cb(rb_data->name, rb_data->mb->type, rb_data->domain, hostname, AF_INET, address, rb_data->port, &rb_data->txt_kv); /* Got a suitable address, stop record browser */ break; case AVAHI_BROWSER_REMOVE: /* Not handled - record browser lifetime too short for this to happen */ return; case AVAHI_BROWSER_CACHE_EXHAUSTED: case AVAHI_BROWSER_ALL_FOR_NOW: DPRINTF(E_DBG, L_MDNS, "Avahi Record Browser (%s v4): no more results (%s)\n", hostname, (event == AVAHI_BROWSER_CACHE_EXHAUSTED) ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); break; case AVAHI_BROWSER_FAILURE: DPRINTF(E_LOG, L_MDNS, "Avahi Record Browser (%s v4) failure: %s\n", hostname, avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b)))); break; } keyval_clear(&rb_data->txt_kv); free(rb_data->name); free(rb_data->domain); free(rb_data); avahi_record_browser_free(b); }
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) { char address[AVAHI_ADDRESS_STR_MAX + IF_NAMESIZE + 2]; char ifname[IF_NAMESIZE]; struct keyval txt_kv; struct mdns_browser *mb; char *key; char *value; size_t len; int family; int ll; int ret; mb = (struct mdns_browser *)userdata; switch (event) { case AVAHI_RESOLVER_FAILURE: DPRINTF(E_LOG, L_MDNS, "Avahi Resolver failure: service '%s' type '%s': %s\n", name, type, avahi_strerror(avahi_client_errno(mdns_client))); break; case AVAHI_RESOLVER_FOUND: DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d\n", name, type, proto); // KK - if I reject 192.168.* my AirTunes get rejected. // How does avahi passes multiple IP addresses? // ll = is_link_local(addr); ll = 0; switch (proto) { case AVAHI_PROTO_INET: avahi_record_browser_new() if (ll) { family = AF_UNSPEC; DPRINTF(E_DBG, L_MDNS, "Discarding IPv4 LL address\n"); break; } family = AF_INET; avahi_address_snprint(address, sizeof(address), addr); break; case AVAHI_PROTO_INET6: avahi_address_snprint(address, sizeof(address), addr); if (ll) { DPRINTF(E_DBG, L_MDNS, "Appending interface name to IPv6 LL address\n"); if (!if_indextoname(intf, ifname)) { DPRINTF(E_LOG, L_MDNS, "Could not map interface index %d to a name\n", intf); family = AF_UNSPEC; break; } len = strlen(address); ret = snprintf(address + len, sizeof(address) - len, "%%%s", ifname); if ((ret < 0) || (ret > sizeof(address) - len)) { DPRINTF(E_LOG, L_MDNS, "Buffer too short for scoped IPv6 LL\n"); family = AF_UNSPEC; break; } DPRINTF(E_DBG, L_MDNS, "Scoped IPv6 LL: %s\n", address); } family = AF_INET6; break; default: DPRINTF(E_INFO, L_MDNS, "Avahi Resolver: unknown protocol %d\n", proto); family = AF_UNSPEC; break; } if (family == AF_UNSPEC) break; memset(&txt_kv, 0, sizeof(struct keyval)); while (txt) { len = avahi_string_list_get_size(txt); key = (char *)avahi_string_list_get_text(txt); value = memchr(key, '=', len); if (!value) { value = ""; len = 0; } else { *value = '\0'; value++; len -= strlen(key) + 1; } ret = keyval_add_size(&txt_kv, key, value, len); if (ret < 0) { DPRINTF(E_LOG, L_MDNS, "Could not build TXT record keyval\n"); goto out_clear_txt_kv; } txt = avahi_string_list_get_next(txt); } /* Execute callback (mb->cb) with all the data */ mb->cb(name, type, domain, hostname, family, address, port, &txt_kv); out_clear_txt_kv: keyval_clear(&txt_kv); break; } avahi_service_resolver_free(r); }
/* 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; }
/* Earlier versions of ffmpeg/libav do not seem to allow access to the http * headers, so we must instead open the stream ourselves to get the metadata. * Sorry about the extra connections, you radio streaming people! * * It is not possible to get the packet metadata with these versions of ffmpeg */ struct http_icy_metadata * http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only) { struct http_icy_metadata *metadata; struct http_client_ctx ctx; struct keyval *kv; const char *value; int got_header; int ret; /* Can only get header metadata */ if (packet_only) return NULL; kv = keyval_alloc(); if (!kv) return NULL; memset(&ctx, 0, sizeof(struct http_client_ctx)); ctx.url = fmtctx->filename; ctx.headers = kv; ctx.headers_only = 1; ctx.body = NULL; ret = http_client_request(&ctx); if (ret < 0) { DPRINTF(E_LOG, L_HTTP, "Error fetching %s\n", fmtctx->filename); free(kv); return NULL; } metadata = malloc(sizeof(struct http_icy_metadata)); if (!metadata) return NULL; memset(metadata, 0, sizeof(struct http_icy_metadata)); got_header = 0; if ( (value = keyval_get(ctx.headers, "icy-name")) ) { metadata->name = strdup(value); got_header = 1; } if ( (value = keyval_get(ctx.headers, "icy-description")) ) { metadata->description = strdup(value); got_header = 1; } if ( (value = keyval_get(ctx.headers, "icy-genre")) ) { metadata->genre = strdup(value); got_header = 1; } keyval_clear(kv); free(kv); if (!got_header) { free(metadata); return NULL; } /* DPRINTF(E_DBG, L_HTTP, "Found ICY: N %s, D %s, G %s, T %s, A %s, U %s, I %" PRIu32 "\n", metadata->name, metadata->description, metadata->genre, metadata->title, metadata->artist, metadata->artwork_url, metadata->hash );*/ return metadata; }