dmap-share.c 56.7 KB
Newer Older
1
/* Implmentation of DMAP (e.g., iTunes Music or iPhoto Picture) sharing
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 *
 * Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
 *
 * Modifications Copyright (C) 2008 W. Michael Petullo <mike@flyn.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "config.h"

#include <time.h>
#include <string.h>
#include <stdlib.h>

#include <glib/gi18n.h>

31
#include <libdmapsharing/dmap.h>
32 33 34 35 36 37 38 39
#include <libdmapsharing/dmap-structure.h>

#define TYPE_OF_SERVICE "_daap._tcp"
#define STANDARD_DAAP_PORT 3689
#define STANDARD_DPAP_PORT 8770
#define DMAP_VERSION 2.0
#define DAAP_VERSION 3.0
#define DMAP_TIMEOUT 1800
40
#define DMAP_STATUS_OK 200
41

W. Michael Petullo's avatar
W. Michael Petullo committed
42 43 44
typedef enum
{
	DMAP_SHARE_AUTH_METHOD_NONE = 0,
45
	DMAP_SHARE_AUTH_METHOD_NAME_AND_PASSWORD = 1,
W. Michael Petullo's avatar
W. Michael Petullo committed
46
	DMAP_SHARE_AUTH_METHOD_PASSWORD = 2
47 48
} DMAPShareAuthMethod;

W. Michael Petullo's avatar
W. Michael Petullo committed
49 50
enum
{
51
	PROP_0,
52 53 54
	/* I'd be nice to unify these, once libsoup supports it. See:
	 * http://mail.gnome.org/archives/libsoup-list/2011-January/msg00000.html
	 */
55 56
	PROP_SERVER_IPV4,
	PROP_SERVER_IPV6,
57 58 59 60
	PROP_NAME,
	PROP_PASSWORD,
	PROP_REVISION_NUMBER,
	PROP_AUTH_METHOD,
61 62
	PROP_DB,
	PROP_CONTAINER_DB,
63 64
	PROP_TRANSCODE_MIMETYPE,
	PROP_TXT_RECORDS
65 66
};

W. Michael Petullo's avatar
W. Michael Petullo committed
67 68
struct DMAPSharePrivate
{
69 70 71
	gchar *name;
	guint port;
	char *password;
72 73 74 75

	/* FIXME: eventually, this should be determined dynamically, based
	 * on what client has connected and its supported mimetypes.
	 */
76
	char *transcode_mimetype;
77

78 79
	DMAPShareAuthMethod auth_method;

80
	/* mDNS/DNS-SD publishing things */
81 82
	gboolean server_active;
	gboolean published;
83
	DMAPMdnsPublisher *publisher;
84

85
	/* HTTP server things */
86 87
	SoupServer *server_ipv4;
	SoupServer *server_ipv6;
88 89
	guint revision_number;

90 91 92
	/* The media database */
	DMAPDb *db;
	DMAPContainerDb *container_db;
W. Michael Petullo's avatar
W. Michael Petullo committed
93

94 95
	/* TXT-RECORDS published by mDNS */
	gchar **txt_records;
96

97 98 99
	GHashTable *session_ids;
};

100 101 102
typedef void (*ShareBitwiseDestroyFunc) (void *);
typedef DMAPRecord *(*ShareBitwiseLookupByIdFunc) (void *db, guint id);

103
/* FIXME: name this something else, as it is more than just share/bitwise now */
W. Michael Petullo's avatar
W. Michael Petullo committed
104 105 106
struct share_bitwise_t
{
	SoupServer *server;	/* Also in share, but we need to know whether server_ipv6 or _ipv4. */
107
	struct MLCL_Bits mb;
108
	GSList *id_list;
109
	guint32 size;
110 111 112 113 114 115 116

	/* FIXME: ick, void * is DMAPDDb * or GHashTable * 
	 * in next two fields:*/
	void *db;
	DMAPRecord *(*lookup_by_id) (void *db, guint id);

	void (*destroy) (void *);
117 118
};

W. Michael Petullo's avatar
W. Michael Petullo committed
119 120
static void dmap_share_init (DMAPShare * share);
static void dmap_share_class_init (DMAPShareClass * klass);
121 122 123

G_DEFINE_ABSTRACT_TYPE (DMAPShare, dmap_share, G_TYPE_OBJECT)
#define DMAP_SHARE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
124 125
				   DMAP_TYPE_SHARE, DMAPSharePrivate));

W. Michael Petullo's avatar
W. Michael Petullo committed
126 127 128 129 130 131
     static gboolean
	     _dmap_share_soup_auth_callback (SoupAuthDomain * auth_domain,
					     SoupMessage * msg,
					     const char *username,
					     gpointer password,
					     DMAPShare * share)
132
{
W. Michael Petullo's avatar
W. Michael Petullo committed
133
	gboolean allowed;
134 135 136 137 138 139 140 141 142 143 144
	const char *path;

	path = soup_message_get_uri (msg)->path;
	g_debug ("Auth request for %s, user %s", path, username);

	allowed = !strcmp (password, share->priv->password);
	g_debug ("Auth request: %s", allowed ? "ALLOWED" : "DENIED");

	return allowed;
}

W. Michael Petullo's avatar
W. Michael Petullo committed
145 146 147 148 149 150
static void
server_info_adapter (SoupServer * server,
		     SoupMessage * message,
		     const char *path,
		     GHashTable * query,
		     SoupClientContext * context, DMAPShare * share)
151 152 153 154
{
	DMAP_SHARE_GET_CLASS (share)->server_info (share,
						   server,
						   message,
W. Michael Petullo's avatar
W. Michael Petullo committed
155
						   path, query, context);
156 157
}

W. Michael Petullo's avatar
W. Michael Petullo committed
158 159 160 161 162 163
static void
content_codes_adapter (SoupServer * server,
		       SoupMessage * message,
		       const char *path,
		       GHashTable * query,
		       SoupClientContext * context, DMAPShare * share)
164 165 166 167
{
	DMAP_SHARE_GET_CLASS (share)->content_codes (share,
						     server,
						     message,
W. Michael Petullo's avatar
W. Michael Petullo committed
168
						     path, query, context);
169 170
}

W. Michael Petullo's avatar
W. Michael Petullo committed
171 172 173 174 175 176
static void
login_adapter (SoupServer * server,
	       SoupMessage * message,
	       const char *path,
	       GHashTable * query,
	       SoupClientContext * context, DMAPShare * share)
177 178 179
{
	DMAP_SHARE_GET_CLASS (share)->login (share,
					     server,
W. Michael Petullo's avatar
W. Michael Petullo committed
180
					     message, path, query, context);
181 182
}

W. Michael Petullo's avatar
W. Michael Petullo committed
183 184 185 186 187 188
static void
logout_adapter (SoupServer * server,
		SoupMessage * message,
		const char *path,
		GHashTable * query,
		SoupClientContext * context, DMAPShare * share)
189 190 191
{
	DMAP_SHARE_GET_CLASS (share)->logout (share,
					      server,
W. Michael Petullo's avatar
W. Michael Petullo committed
192
					      message, path, query, context);
193 194
}

W. Michael Petullo's avatar
W. Michael Petullo committed
195 196 197 198 199 200
static void
update_adapter (SoupServer * server,
		SoupMessage * message,
		const char *path,
		GHashTable * query,
		SoupClientContext * context, DMAPShare * share)
201 202 203
{
	DMAP_SHARE_GET_CLASS (share)->update (share,
					      server,
W. Michael Petullo's avatar
W. Michael Petullo committed
204
					      message, path, query, context);
205 206
}

W. Michael Petullo's avatar
W. Michael Petullo committed
207 208 209 210 211 212
static void
databases_adapter (SoupServer * server,
		   SoupMessage * message,
		   const char *path,
		   GHashTable * query,
		   SoupClientContext * context, DMAPShare * share)
213 214 215 216
{
	DMAP_SHARE_GET_CLASS (share)->databases (share,
						 server,
						 message,
W. Michael Petullo's avatar
W. Michael Petullo committed
217
						 path, query, context);
218 219
}

W. Michael Petullo's avatar
W. Michael Petullo committed
220 221 222 223 224 225
static void
ctrl_int_adapter (SoupServer * server,
		  SoupMessage * message,
		  const char *path,
		  GHashTable * query,
		  SoupClientContext * context, DMAPShare * share)
226 227
{
	DMAP_SHARE_GET_CLASS (share)->ctrl_int (share,
W. Michael Petullo's avatar
W. Michael Petullo committed
228 229 230
						server,
						message,
						path, query, context);
231 232
}

233
static void
W. Michael Petullo's avatar
W. Michael Petullo committed
234
_dmap_share_server_setup_handlers (DMAPShare * share, SoupServer * server)
235
{
W. Michael Petullo's avatar
W. Michael Petullo committed
236 237
	gboolean password_required =
		(share->priv->auth_method != DMAP_SHARE_AUTH_METHOD_NONE);
238 239 240 241

	if (password_required) {
		SoupAuthDomain *auth_domain;

W. Michael Petullo's avatar
W. Michael Petullo committed
242 243 244 245 246 247 248 249 250 251 252 253
		auth_domain =
			soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM,
						    "Music Sharing",
						    SOUP_AUTH_DOMAIN_ADD_PATH,
						    "/login",
						    SOUP_AUTH_DOMAIN_ADD_PATH,
						    "/update",
						    SOUP_AUTH_DOMAIN_ADD_PATH,
						    "/database",
						    SOUP_AUTH_DOMAIN_FILTER,
						    _dmap_share_soup_auth_filter,
						    NULL);
254
		soup_auth_domain_basic_set_auth_callback (auth_domain,
W. Michael Petullo's avatar
W. Michael Petullo committed
255 256 257 258
							  (SoupAuthDomainBasicAuthCallback)
							  _dmap_share_soup_auth_callback,
							  g_object_ref
							  (share),
259
							  g_object_unref);
260
		soup_server_add_auth_domain (server, auth_domain);
261 262
	}

263
	soup_server_add_handler (server, "/server-info",
264 265
				 (SoupServerCallback) server_info_adapter,
				 share, NULL);
266
	soup_server_add_handler (server, "/content-codes",
267 268
				 (SoupServerCallback) content_codes_adapter,
				 share, NULL);
269
	soup_server_add_handler (server, "/login",
270 271
				 (SoupServerCallback) login_adapter,
				 share, NULL);
272
	soup_server_add_handler (server, "/logout",
273 274
				 (SoupServerCallback) logout_adapter,
				 share, NULL);
275
	soup_server_add_handler (server, "/update",
276 277
				 (SoupServerCallback) update_adapter,
				 share, NULL);
278
	soup_server_add_handler (server, "/databases",
279 280
				 (SoupServerCallback) databases_adapter,
				 share, NULL);
281
	soup_server_add_handler (server, "/ctrl-int",
282 283
				 (SoupServerCallback) ctrl_int_adapter,
				 share, NULL);
284 285 286 287 288
	soup_server_run_async (server);

}

gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
289
_dmap_share_server_start (DMAPShare * share)
290 291 292 293 294
{
	SoupAddress *addr;
	guint port = DMAP_SHARE_GET_CLASS (share)->get_desired_port (share);

	addr = soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV6, port);
W. Michael Petullo's avatar
W. Michael Petullo committed
295 296
	share->priv->server_ipv6 =
		soup_server_new (SOUP_SERVER_INTERFACE, addr, NULL);
297 298 299 300 301 302 303
	g_object_unref (addr);

	/* NOTE: On Linux, opening a socket may give a IPv6-wrapped IPv4 address.
	 * in this case, the server_ipv6 object will service requests from both
	 * IPv6 and IPv4 clients.
	 */
	if (share->priv->server_ipv6 == NULL) {
304
		g_debug
W. Michael Petullo's avatar
W. Michael Petullo committed
305 306 307 308 309 310
			("Unable to start music sharing server on port %d, trying any open port",
			 port);
		addr = soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV6,
					     SOUP_ADDRESS_ANY_PORT);
		share->priv->server_ipv6 =
			soup_server_new (SOUP_SERVER_INTERFACE, addr, NULL);
311 312 313 314 315 316 317
		g_object_unref (addr);
	}

	if (share->priv->server_ipv6 != NULL) {
		/* Use same port for IPv4 as IPv6. */
		port = soup_server_get_port (share->priv->server_ipv6);
	} else {
318
		g_debug ("Unable to start music sharing server (IPv6)");
319 320 321 322 323 324 325
	}

	/* NOTE: In the case mentioned above, this will fail as the server_ipv6 is already
	 * servicing IPv4 requests. In this case server_ipv6 handles both IPv6 and IPv4
	 * and server_ipv4 is NULL.
	 */
	addr = soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV4, port);
W. Michael Petullo's avatar
W. Michael Petullo committed
326 327
	share->priv->server_ipv4 =
		soup_server_new (SOUP_SERVER_INTERFACE, addr, NULL);
328 329 330 331
	g_object_unref (addr);

	/* Don't try any port on IPv4 unless IPv6 failed. We don't want to listen to one
	 * port on IPv6 and another on IPv4 */
W. Michael Petullo's avatar
W. Michael Petullo committed
332 333
	if (share->priv->server_ipv6 == NULL
	    && share->priv->server_ipv4 == NULL) {
334
		g_debug
W. Michael Petullo's avatar
W. Michael Petullo committed
335 336 337 338 339 340
			("Unable to start music sharing server on port %d, trying IPv4 only, any open port",
			 port);
		addr = soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV4,
					     SOUP_ADDRESS_ANY_PORT);
		share->priv->server_ipv4 =
			soup_server_new (SOUP_SERVER_INTERFACE, addr, NULL);
341 342 343 344
		g_object_unref (addr);
	}

	if (share->priv->server_ipv4 == NULL) {
345
		g_debug ("Unable to start music sharing server (IPv4)");
346
		if (share->priv->server_ipv6 == NULL) {
347
			g_warning ("Unable to start music sharing server (both IPv4 and IPv6 failed)");
348 349 350 351 352
			return FALSE;
		}
	}

	if (share->priv->server_ipv6)
W. Michael Petullo's avatar
W. Michael Petullo committed
353 354 355
		share->priv->port =
			(guint) soup_server_get_port (share->priv->
						      server_ipv6);
356
	else
W. Michael Petullo's avatar
W. Michael Petullo committed
357 358 359
		share->priv->port =
			(guint) soup_server_get_port (share->priv->
						      server_ipv4);
360 361 362 363 364

	g_debug ("Started DMAP server on port %u (IPv6: %s, explicit IPv4: %s)",
	          share->priv->port,
		  share->priv->server_ipv6 ? "yes" : "no",
		  share->priv->server_ipv4 ? "yes" : "no");
365 366

	if (share->priv->server_ipv6)
W. Michael Petullo's avatar
W. Michael Petullo committed
367 368
		_dmap_share_server_setup_handlers (share,
						   share->priv->server_ipv6);
369 370

	if (share->priv->server_ipv4)
W. Michael Petullo's avatar
W. Michael Petullo committed
371 372
		_dmap_share_server_setup_handlers (share,
						   share->priv->server_ipv4);
373 374

	/* using direct since there is no g_uint_hash or g_uint_equal */
W. Michael Petullo's avatar
W. Michael Petullo committed
375 376 377
	share->priv->session_ids =
		g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
				       g_free);
378 379 380 381 382 383 384

	share->priv->server_active = TRUE;

	return TRUE;
}

static gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
385
_dmap_share_server_stop (DMAPShare * share)
386
{
W. Michael Petullo's avatar
W. Michael Petullo committed
387 388
	g_debug ("Stopping music sharing server on port %d",
		 share->priv->port);
389

390 391 392 393 394 395 396 397 398 399
	if (share->priv->server_ipv4) {
		soup_server_quit (share->priv->server_ipv4);
		g_object_unref (share->priv->server_ipv4);
		share->priv->server_ipv4 = NULL;
	}

	if (share->priv->server_ipv6) {
		soup_server_quit (share->priv->server_ipv6);
		g_object_unref (share->priv->server_ipv6);
		share->priv->server_ipv6 = NULL;
400 401 402 403 404 405 406 407 408 409 410 411 412
	}

	if (share->priv->session_ids) {
		g_hash_table_destroy (share->priv->session_ids);
		share->priv->session_ids = NULL;
	}

	share->priv->server_active = FALSE;

	return TRUE;
}

gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
413
_dmap_share_publish_start (DMAPShare * share)
414 415
{
	gchar *nameprop;
W. Michael Petullo's avatar
W. Michael Petullo committed
416
	GError *error;
417 418 419 420 421 422
	gboolean res;
	gboolean password_required;

	/* FIXME: this is done throughout dmap-share.c. Is this the best way? */
	g_object_get ((gpointer) share, "name", &nameprop, NULL);

W. Michael Petullo's avatar
W. Michael Petullo committed
423 424
	password_required =
		(share->priv->auth_method != DMAP_SHARE_AUTH_METHOD_NONE);
425 426 427

	error = NULL;
	res = dmap_mdns_publisher_publish (share->priv->publisher,
W. Michael Petullo's avatar
W. Michael Petullo committed
428 429 430 431 432 433
					   nameprop,
					   share->priv->port,
					   DMAP_SHARE_GET_CLASS (share)->
					   get_type_of_service (share),
					   password_required,
					   share->priv->txt_records, &error);
434 435 436

	if (res == FALSE) {
		if (error != NULL) {
W. Michael Petullo's avatar
W. Michael Petullo committed
437 438 439
			g_warning
				("Unable to notify network of media sharing: %s",
				 error->message);
440 441
			g_error_free (error);
		} else {
W. Michael Petullo's avatar
W. Michael Petullo committed
442 443
			g_warning
				("Unable to notify network of media sharing");
444 445 446 447 448 449 450 451 452 453 454 455
		}
		return FALSE;
	} else {
		g_debug ("Published DMAP server information to mdns");
	}

	g_free (nameprop);

	return TRUE;
}

static gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
456
_dmap_share_publish_stop (DMAPShare * share)
457 458 459
{
	if (share->priv->publisher) {
		gboolean res;
W. Michael Petullo's avatar
W. Michael Petullo committed
460 461
		GError *error;

462
		error = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
463 464 465
		res = dmap_mdns_publisher_withdraw (share->priv->publisher,
						    share->priv->port,
						    &error);
466
		if (error != NULL) {
W. Michael Petullo's avatar
W. Michael Petullo committed
467 468 469
			g_warning
				("Unable to withdraw music sharing service: %s",
				 error->message);
470 471 472 473 474 475 476 477 478 479
			g_error_free (error);
		}
		return res;
	}

	share->priv->published = FALSE;
	return TRUE;
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
480
_dmap_share_restart (DMAPShare * share)
481 482 483
{
	gboolean res;

484 485
	_dmap_share_server_stop (share);
	res = _dmap_share_server_start (share);
486 487
	if (res) {
		/* To update information just publish again */
488
		_dmap_share_publish_start (share);
489
	} else {
490
		_dmap_share_publish_stop (share);
491 492 493 494
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
495
_dmap_share_maybe_restart (DMAPShare * share)
496 497
{
	if (share->priv->published) {
498
		_dmap_share_restart (share);
499 500 501 502
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
503
_dmap_share_set_name (DMAPShare * share, const char *name)
504 505 506 507 508 509 510 511
{
	GError *error;

	g_return_if_fail (share != NULL);

	g_free (share->priv->name);
	share->priv->name = g_strdup (name);

512 513
	if (share->priv->published) {
		error = NULL;
514 515 516 517 518
		dmap_mdns_publisher_rename_at_port (share->priv->
						    publisher,
						    share->priv->port,
						    name,
						    &error);
519 520 521 522 523
		if (error != NULL) {
			g_warning ("Unable to change MDNS service name: %s",
				   error->message);
			g_error_free (error);
		}
524 525 526 527
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
528
_dmap_share_set_password (DMAPShare * share, const char *password)
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
{
	g_return_if_fail (share != NULL);

	if (share->priv->password && password &&
	    strcmp (password, share->priv->password) == 0) {
		return;
	}

	g_free (share->priv->password);
	share->priv->password = g_strdup (password);
	if (password != NULL) {
		share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_PASSWORD;
	} else {
		share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_NONE;
	}

545
	_dmap_share_maybe_restart (share);
546 547 548
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
549 550 551
_dmap_share_set_property (GObject * object,
			  guint prop_id,
			  const GValue * value, GParamSpec * pspec)
552 553 554 555 556
{
	DMAPShare *share = DMAP_SHARE (object);

	switch (prop_id) {
	case PROP_NAME:
557
		_dmap_share_set_name (share, g_value_get_string (value));
558 559
		break;
	case PROP_PASSWORD:
560
		_dmap_share_set_password (share, g_value_get_string (value));
561
		break;
562
	case PROP_DB:
W. Michael Petullo's avatar
W. Michael Petullo committed
563 564 565 566 567 568 569
		share->priv->db = (DMAPDb *) g_value_get_pointer (value);
		break;
	case PROP_CONTAINER_DB:
		share->priv->container_db =
			(DMAPContainerDb *) g_value_get_pointer (value);
		break;
	case PROP_TRANSCODE_MIMETYPE:
570
		/* FIXME: get or dup? */
W. Michael Petullo's avatar
W. Michael Petullo committed
571 572 573 574 575
		share->priv->transcode_mimetype = g_value_dup_string (value);
		break;
	case PROP_TXT_RECORDS:
		share->priv->txt_records = g_value_dup_boxed (value);
		break;
576 577 578 579 580 581 582
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
583 584
_dmap_share_get_property (GObject * object,
			  guint prop_id, GValue * value, GParamSpec * pspec)
585 586 587 588
{
	DMAPShare *share = DMAP_SHARE (object);

	switch (prop_id) {
589 590 591 592 593
	case PROP_SERVER_IPV4:
		g_value_set_object (value, share->priv->server_ipv4);
		return;
	case PROP_SERVER_IPV6:
		g_value_set_object (value, share->priv->server_ipv6);
594
		return;
595 596 597 598 599 600 601 602
	case PROP_NAME:
		g_value_set_string (value, share->priv->name);
		break;
	case PROP_PASSWORD:
		g_value_set_string (value, share->priv->password);
		break;
	case PROP_REVISION_NUMBER:
		g_value_set_uint (value,
603
				  _dmap_share_get_revision_number
W. Michael Petullo's avatar
W. Michael Petullo committed
604
				  (DMAP_SHARE (object)));
605 606 607
		break;
	case PROP_AUTH_METHOD:
		g_value_set_uint (value,
608
				  _dmap_share_get_auth_method
W. Michael Petullo's avatar
W. Michael Petullo committed
609
				  (DMAP_SHARE (object)));
610
		break;
611
	case PROP_DB:
W. Michael Petullo's avatar
W. Michael Petullo committed
612 613 614 615 616 617 618 619 620 621 622
		g_value_set_pointer (value, share->priv->db);
		break;
	case PROP_CONTAINER_DB:
		g_value_set_pointer (value, share->priv->container_db);
		break;
	case PROP_TRANSCODE_MIMETYPE:
		g_value_set_string (value, share->priv->transcode_mimetype);
		break;
	case PROP_TXT_RECORDS:
		g_value_set_boxed (value, share->priv->txt_records);
		break;
623 624 625 626 627 628 629
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
630
_dmap_share_finalize (GObject * object)
631 632 633
{
	DMAPShare *share = DMAP_SHARE (object);

634 635
	g_debug ("Finalizing DMAPShare");

636
	if (share->priv->published) {
637
		_dmap_share_publish_stop (share);
638 639 640
	}

	if (share->priv->server_active) {
641
		_dmap_share_server_stop (share);
642 643 644
	}

	g_free (share->priv->name);
645 646 647 648
	g_free (share->priv->password);

	g_object_unref (share->priv->db);
	g_object_unref (share->priv->container_db);
W. Michael Petullo's avatar
W. Michael Petullo committed
649

650
	g_strfreev (share->priv->txt_records);
651 652 653 654 655

	if (share->priv->publisher) {
		g_object_unref (share->priv->publisher);
	}

656
	G_OBJECT_CLASS (dmap_share_parent_class)->finalize (object);
657 658 659
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
660
dmap_share_class_init (DMAPShareClass * klass)
661 662 663
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

664 665
	object_class->get_property = _dmap_share_get_property;
	object_class->set_property = _dmap_share_set_property;
W. Michael Petullo's avatar
W. Michael Petullo committed
666
	object_class->finalize = _dmap_share_finalize;
667 668

	/* Pure virtual methods: */
W. Michael Petullo's avatar
W. Michael Petullo committed
669 670
	klass->get_desired_port = NULL;
	klass->get_type_of_service = NULL;
671
	klass->message_add_standard_headers = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
672 673
	klass->get_meta_data_map = NULL;
	klass->add_entry_to_mlcl = NULL;
674
	klass->databases_browse_xxx = NULL;
W. Michael Petullo's avatar
W. Michael Petullo committed
675
	klass->databases_items_xxx = NULL;
676 677

	/* Virtual methods: */
W. Michael Petullo's avatar
W. Michael Petullo committed
678 679 680 681 682
	klass->content_codes = _dmap_share_content_codes;
	klass->login = _dmap_share_login;
	klass->logout = _dmap_share_logout;
	klass->update = _dmap_share_update;
	klass->published = _dmap_share_published;
683
	klass->name_collision = _dmap_share_name_collision;
W. Michael Petullo's avatar
W. Michael Petullo committed
684 685
	klass->databases = _dmap_share_databases;
	klass->ctrl_int = _dmap_share_ctrl_int;
686

687
	g_object_class_install_property (object_class,
688 689 690 691 692 693
					 PROP_SERVER_IPV4,
					 g_param_spec_object ("server-ipv4",
							      "Soup Server",
							      "Soup server",
							      SOUP_TYPE_SERVER,
							      G_PARAM_READABLE));
W. Michael Petullo's avatar
W. Michael Petullo committed
694

695 696 697
	g_object_class_install_property (object_class,
					 PROP_SERVER_IPV6,
					 g_param_spec_object ("server-ipv6",
698 699 700 701
							      "Soup Server",
							      "Soup server",
							      SOUP_TYPE_SERVER,
							      G_PARAM_READABLE));
W. Michael Petullo's avatar
W. Michael Petullo committed
702

703 704 705
	g_object_class_install_property (object_class,
					 PROP_NAME,
					 g_param_spec_string ("name",
W. Michael Petullo's avatar
W. Michael Petullo committed
706 707 708 709
							      "Name",
							      "Share Name",
							      NULL,
							      G_PARAM_READWRITE));
710 711 712
	g_object_class_install_property (object_class,
					 PROP_PASSWORD,
					 g_param_spec_string ("password",
W. Michael Petullo's avatar
W. Michael Petullo committed
713 714 715 716
							      "Authentication password",
							      "Authentication password",
							      NULL,
							      G_PARAM_READWRITE));
717 718 719 720

	g_object_class_install_property (object_class,
					 PROP_REVISION_NUMBER,
					 g_param_spec_uint ("revision_number",
W. Michael Petullo's avatar
W. Michael Petullo committed
721
							    "Revision number",
722 723 724 725 726 727 728 729 730
							    "Revision number",
							    0,
							    G_MAXINT,
							    0,
							    G_PARAM_READWRITE));

	g_object_class_install_property (object_class,
					 PROP_AUTH_METHOD,
					 g_param_spec_uint ("auth_method",
W. Michael Petullo's avatar
W. Michael Petullo committed
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
							    "Authentication method",
							    "Authentication method",
							    DMAP_SHARE_AUTH_METHOD_NONE,
							    DMAP_SHARE_AUTH_METHOD_PASSWORD,
							    0,
							    G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_DB,
					 g_param_spec_pointer ("db",
							       "DB",
							       "DB object",
							       G_PARAM_READWRITE
							       |
							       G_PARAM_CONSTRUCT_ONLY));

746
	g_object_class_install_property (object_class,
W. Michael Petullo's avatar
W. Michael Petullo committed
747 748 749 750 751 752 753
					 PROP_CONTAINER_DB,
					 g_param_spec_pointer ("container-db",
							       "Container DB",
							       "Container DB object",
							       G_PARAM_READWRITE
							       |
							       G_PARAM_CONSTRUCT_ONLY));
754

755
	g_object_class_install_property (object_class,
W. Michael Petullo's avatar
W. Michael Petullo committed
756 757 758 759 760 761 762 763
					 PROP_TRANSCODE_MIMETYPE,
					 g_param_spec_string
					 ("transcode-mimetype",
					  "Transcode mimetype",
					  "Set mimetype of stream after transcoding",
					  NULL,
					  G_PARAM_READWRITE |
					  G_PARAM_CONSTRUCT_ONLY));
764 765

	g_object_class_install_property (object_class,
W. Michael Petullo's avatar
W. Michael Petullo committed
766 767 768 769 770 771
					 PROP_TXT_RECORDS,
					 g_param_spec_boxed ("txt-records",
							     "TXT-Records",
							     "Set TXT-Records used for MDNS publishing",
							     G_TYPE_STRV,
							     G_PARAM_READWRITE));
772 773 774 775

	g_type_class_add_private (klass, sizeof (DMAPSharePrivate));
}

W. Michael Petullo's avatar
W. Michael Petullo committed
776 777 778
static void
published_adapter (DMAPMdnsPublisher * publisher,
		   const char *name, DMAPShare * share)
779 780 781 782
{
	DMAP_SHARE_GET_CLASS (share)->published (share, publisher, name);
}

W. Michael Petullo's avatar
W. Michael Petullo committed
783 784 785
static void
name_collision_adapter (DMAPMdnsPublisher * publisher,
			const char *name, DMAPShare * share)
786 787 788 789 790
{
	DMAP_SHARE_GET_CLASS (share)->name_collision (share, publisher, name);
}

static void
W. Michael Petullo's avatar
W. Michael Petullo committed
791
dmap_share_init (DMAPShare * share)
792 793 794 795 796 797 798 799 800
{
	share->priv = DMAP_SHARE_GET_PRIVATE (share);

	share->priv->revision_number = 5;
	share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_NONE;
	share->priv->publisher = dmap_mdns_publisher_new ();

	g_signal_connect_object (share->priv->publisher,
				 "published",
W. Michael Petullo's avatar
W. Michael Petullo committed
801
				 G_CALLBACK (published_adapter), share, 0);
802 803 804 805 806 807 808
	g_signal_connect_object (share->priv->publisher,
				 "name-collision",
				 G_CALLBACK (name_collision_adapter),
				 share, 0);
}

guint
W. Michael Petullo's avatar
W. Michael Petullo committed
809
_dmap_share_get_auth_method (DMAPShare * share)
810 811 812 813 814
{
	return share->priv->auth_method;
}

guint
W. Michael Petullo's avatar
W. Michael Petullo committed
815
_dmap_share_get_revision_number (DMAPShare * share)
816 817 818 819 820
{
	return share->priv->revision_number;
}

static gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
821
get_session_id (GHashTable * query, guint32 * id)
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
{
	char *session_id_str;
	guint32 session_id;

	session_id_str = g_hash_table_lookup (query, "session-id");
	if (session_id_str == NULL) {
		g_warning ("Session id not found.");
		return FALSE;
	}

	session_id = (guint32) strtoul (session_id_str, NULL, 10);
	if (id != NULL) {
		*id = session_id;
	}
	return TRUE;
}

gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
840 841
_dmap_share_get_revision_number_from_query (GHashTable * query,
					    guint * number)
842 843 844 845 846 847
{
	char *revision_number_str;
	guint revision_number;

	revision_number_str = g_hash_table_lookup (query, "revision-number");
	if (revision_number_str == NULL) {
W. Michael Petullo's avatar
W. Michael Petullo committed
848 849
		g_warning
			("Client asked for an update without a rev. number");
850 851 852 853 854 855 856 857 858 859 860
		return FALSE;
	}

	revision_number = strtoul (revision_number_str, NULL, 10);
	if (number != NULL) {
		*number = revision_number;
	}
	return TRUE;
}

gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
861 862 863 864
_dmap_share_session_id_validate (DMAPShare * share,
				 SoupClientContext * context,
				 SoupMessage * message,
				 GHashTable * query, guint32 * id)
865
{
W. Michael Petullo's avatar
W. Michael Petullo committed
866 867
	guint32 session_id;
	gboolean res;
868 869 870 871 872 873 874 875
	const char *addr;
	const char *remote_address;

	if (id) {
		*id = 0;
	}

	res = get_session_id (query, &session_id);
W. Michael Petullo's avatar
W. Michael Petullo committed
876
	if (!res) {
877 878 879 880 881 882 883 884
		g_warning ("Validation failed: Unable to parse session id");
		return FALSE;
	}

	/* check hash for remote address */
	addr = g_hash_table_lookup (share->priv->session_ids,
				    GUINT_TO_POINTER (session_id));
	if (addr == NULL) {
W. Michael Petullo's avatar
W. Michael Petullo committed
885 886 887
		g_warning
			("Validation failed: Unable to lookup session id %u",
			 session_id);
888 889 890 891 892
		return FALSE;
	}

	remote_address = soup_client_context_get_host (context);
	g_debug ("Validating session id %u from %s matches %s",
W. Michael Petullo's avatar
W. Michael Petullo committed
893
		 session_id, remote_address, addr);
894
	if (remote_address == NULL || strcmp (addr, remote_address) != 0) {
W. Michael Petullo's avatar
W. Michael Petullo committed
895 896
		g_warning
			("Validation failed: Remote address does not match stored address");
897 898 899 900 901 902 903 904 905 906 907
		return FALSE;
	}

	if (id) {
		*id = session_id;
	}

	return TRUE;
}

static guint32
W. Michael Petullo's avatar
W. Michael Petullo committed
908
session_id_generate (DMAPShare * share, SoupClientContext * context)
909 910 911 912 913 914 915 916 917
{
	guint32 id;

	id = g_random_int ();

	return id;
}

guint32
W. Michael Petullo's avatar
W. Michael Petullo committed
918
_dmap_share_session_id_create (DMAPShare * share, SoupClientContext * context)
919
{
W. Michael Petullo's avatar
W. Michael Petullo committed
920
	guint32 id;
921
	const char *addr;
W. Michael Petullo's avatar
W. Michael Petullo committed
922
	char *remote_address;
923 924 925 926 927 928 929 930 931

	do {
		/* create a unique session id */
		id = session_id_generate (share, context);
		g_debug ("Generated session id %u", id);

		/* if already used, try again */
		addr = g_hash_table_lookup (share->priv->session_ids,
					    GUINT_TO_POINTER (id));
W. Michael Petullo's avatar
W. Michael Petullo committed
932
	} while (addr != NULL);
933 934

	/* store session id and remote address */
935 936 937 938
	/* FIXME, warning, this fails against libsoup-2.33.90-1.fc15.x86_64 with:
	 * (dmapd:12917): libsoup-CRITICAL **: soup_address_get_physical: assertion `SOUP_IS_ADDRESS (addr)' failed
	 * Is this a bug in libsoup or libdmapsharing?
	 */
939 940 941 942 943 944 945 946
	remote_address = g_strdup (soup_client_context_get_host (context));
	g_hash_table_insert (share->priv->session_ids, GUINT_TO_POINTER (id),
			     remote_address);

	return id;
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
947 948
_dmap_share_session_id_remove (DMAPShare * share,
			       SoupClientContext * context, guint32 id)
949 950 951 952 953
{
	g_hash_table_remove (share->priv->session_ids, GUINT_TO_POINTER (id));
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
954 955 956
_dmap_share_message_set_from_dmap_structure (DMAPShare * share,
					     SoupMessage * message,
					     GNode * structure)
957 958 959 960 961 962 963 964 965 966 967 968 969 970
{
	gchar *resp;
	guint length;

	resp = dmap_structure_serialize (structure, &length);

	if (resp == NULL) {
		g_warning ("Serialize gave us null?\n");
		return;
	}

	soup_message_set_response (message, "application/x-dmap-tagged",
				   SOUP_MEMORY_TAKE, resp, length);

971 972
	DMAP_SHARE_GET_CLASS (share)->message_add_standard_headers (share,
								    message);
973 974 975 976 977

	soup_message_set_status (message, SOUP_STATUS_OK);
}

gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
978
_dmap_share_client_requested (bitwise bits, gint field)
979 980 981 982 983
{
	return 0 != (bits & (((bitwise) 1) << field));
}

gboolean
984
_dmap_share_uri_is_local (const char *text_uri)
985
{
W. Michael Petullo's avatar
W. Michael Petullo committed
986
	return g_str_has_prefix (text_uri, "file://");
987 988 989
}

gboolean
W. Michael Petullo's avatar
W. Michael Petullo committed
990 991
_dmap_share_soup_auth_filter (SoupAuthDomain * auth_domain,
			      SoupMessage * msg, gpointer user_data)
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
{
	const char *path;

	path = soup_message_get_uri (msg)->path;
	if (g_str_has_prefix (path, "/databases/")) {
		/* Subdirectories of /databases don't actually require
		 * authentication
		 */
		return FALSE;
	} else {
		/* Everything else in auth_domain's paths, including
		 * /databases itself, does require auth.
		 */
		return TRUE;
	}
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
1010 1011
_dmap_share_published (DMAPShare * share,
		       DMAPMdnsPublisher * publisher, const char *name)
1012 1013 1014 1015 1016
{
	gchar *nameprop;

	g_object_get ((gpointer) share, "name", &nameprop, NULL);

W. Michael Petullo's avatar
W. Michael Petullo committed
1017
	if (nameprop == NULL || name == NULL) {
1018
		g_free (nameprop);
W. Michael Petullo's avatar
W. Michael Petullo committed
1019 1020
		return;
	}
1021

W. Michael Petullo's avatar
W. Michael Petullo committed
1022 1023 1024 1025
	if (strcmp (nameprop, name) == 0) {
		g_debug ("mDNS publish successful");
		share->priv->published = TRUE;
	}
1026 1027 1028 1029 1030

	g_free (nameprop);
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
1031 1032
_dmap_share_name_collision (DMAPShare * share,
			    DMAPMdnsPublisher * publisher, const char *name)
1033 1034
{
	gchar *nameprop;
W. Michael Petullo's avatar
W. Michael Petullo committed
1035
	char *new_name = "FIXME";
1036 1037 1038

	g_object_get ((gpointer) share, "name", &nameprop, NULL);

W. Michael Petullo's avatar
W. Michael Petullo committed
1039
	if (nameprop == NULL || name == NULL) {
1040
		g_free (nameprop);
W. Michael Petullo's avatar
W. Michael Petullo committed
1041 1042
		return;
	}
1043

W. Michael Petullo's avatar
W. Michael Petullo committed
1044 1045
	if (strcmp (nameprop, name) == 0) {
		g_warning ("Duplicate share name on mDNS");
1046

W. Michael Petullo's avatar
W. Michael Petullo committed
1047 1048 1049
		_dmap_share_set_name (DMAP_SHARE (share), new_name);
		g_free (new_name);
	}
1050 1051 1052

	g_free (nameprop);

W. Michael Petullo's avatar
W. Michael Petullo committed
1053
	return;
1054 1055 1056
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
1057 1058 1059 1060 1061
_dmap_share_content_codes (DMAPShare * share,
			   SoupServer * server,
			   SoupMessage * message,
			   const char *path,
			   GHashTable * query, SoupClientContext * context)
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
{
/* MCCR content codes response
 * 	MSTT status
 * 	MDCL dictionary
 * 		MCNM content codes number
 * 		MCNA content codes name
 * 		MCTY content codes type
 * 	MDCL dictionary
 * 	...
 */
	const DMAPContentCodeDefinition *defs;
	guint num_defs = 0;
	guint i;
	GNode *mccr;

	g_debug ("Path is %s.", path);

	defs = dmap_content_codes (&num_defs);

	mccr = dmap_structure_add (NULL, DMAP_CC_MCCR);
	dmap_structure_add (mccr, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);

	for (i = 0; i < num_defs; i++) {
		GNode *mdcl;

		mdcl = dmap_structure_add (mccr, DMAP_CC_MDCL);
W. Michael Petullo's avatar
W. Michael Petullo committed
1088 1089 1090 1091
		dmap_structure_add (mdcl, DMAP_CC_MCNM,
				    dmap_content_code_string_as_int32 (defs
								       [i].
								       string));
1092
		dmap_structure_add (mdcl, DMAP_CC_MCNA, defs[i].name);
W. Michael Petullo's avatar
W. Michael Petullo committed
1093 1094
		dmap_structure_add (mdcl, DMAP_CC_MCTY,
				    (gint32) defs[i].type);
1095 1096
	}

1097
	_dmap_share_message_set_from_dmap_structure (share, message, mccr);
1098 1099 1100 1101
	dmap_structure_destroy (mccr);
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
1102 1103 1104 1105 1106
_dmap_share_login (DMAPShare * share,
		   SoupServer * server,
		   SoupMessage * message,
		   const char *path,
		   GHashTable * query, SoupClientContext * context)
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
{
/* MLOG login response
 * 	MSTT status
 * 	MLID session id
 */
	GNode *mlog;
	guint32 session_id;

	g_debug ("Path is %s.", path);

1117
	session_id = _dmap_share_session_id_create (share, context);
1118 1119 1120 1121 1122

	mlog = dmap_structure_add (NULL, DMAP_CC_MLOG);
	dmap_structure_add (mlog, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
	dmap_structure_add (mlog, DMAP_CC_MLID, session_id);

1123
	_dmap_share_message_set_from_dmap_structure (share, message, mlog);
1124 1125 1126 1127
	dmap_structure_destroy (mlog);
}

void
W. Michael Petullo's avatar
W. Michael Petullo committed
1128 1129 1130 1131 1132
_dmap_share_logout (DMAPShare * share,
		    SoupServer * server,
		    SoupMessage * message,
		    const char *path,
		    GHashTable * query, SoupClientContext * context)
1133
{
W. Michael Petullo's avatar
W. Michael Petullo committed
1134
	int status;
1135 1136 1137 1138
	guint32 id;

	g_debug ("Path is %s.", path);

W. Michael Petullo's avatar
W. Michael Petullo committed
1139 1140
	if (_dmap_share_session_id_validate
	    (share, context, message, query, &id)) {
1141
		_dmap_share_session_id_remove (share, context, id);
1142 1143 1144 1145 1146 1147 1148 1149