soup-auth-domain-basic.c 9.5 KB
Newer Older
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
 * soup-auth-domain-basic.c: HTTP Basic Authentication (server-side)
 *
 * Copyright (C) 2007 Novell, Inc.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include "soup-auth-domain-basic.h"
15
#include "soup-auth-domain-private.h"
16
#include "soup-message-headers-private.h"
Dan Winship's avatar
Dan Winship committed
17
#include "soup.h"
18
19

/**
20
21
22
 * SoupAuthDomainBasic:
 *
 * Server-side "Basic" authentication.
23
24
25
 *
 * #SoupAuthDomainBasic handles the server side of HTTP "Basic" (ie,
 * cleartext password) authentication.
26
27
 */

28
29
30
31
32
33
enum {
	PROP_0,

	PROP_AUTH_CALLBACK,
	PROP_AUTH_DATA,

34
	LAST_PROPERTY
35
36
};

37
38
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };

39
40
41
42
struct _SoupAuthDomainBasic {
	SoupAuthDomain parent;
};

43
44
45
46
47
48
typedef struct {
	SoupAuthDomainBasicAuthCallback auth_callback;
	gpointer auth_data;
	GDestroyNotify auth_dnotify;
} SoupAuthDomainBasicPrivate;

49
G_DEFINE_FINAL_TYPE_WITH_PRIVATE (SoupAuthDomainBasic, soup_auth_domain_basic, SOUP_TYPE_AUTH_DOMAIN)
50
51
52
53
54
55
56

static void
soup_auth_domain_basic_init (SoupAuthDomainBasic *basic)
{
}

static void
57
soup_auth_domain_basic_finalize (GObject *object)
58
59
{
	SoupAuthDomainBasicPrivate *priv =
60
		soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
61
62
63
64
65
66
67
68

	if (priv->auth_dnotify)
		priv->auth_dnotify (priv->auth_data);

	G_OBJECT_CLASS (soup_auth_domain_basic_parent_class)->finalize (object);
}

static void
69
70
soup_auth_domain_basic_set_property (GObject *object, guint prop_id,
				     const GValue *value, GParamSpec *pspec)
71
72
{
	SoupAuthDomainBasicPrivate *priv =
73
		soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
74
75
76
77
78
79
80
81
82
83
84
85
86

	switch (prop_id) {
	case PROP_AUTH_CALLBACK:
		priv->auth_callback = g_value_get_pointer (value);
		break;
	case PROP_AUTH_DATA:
		if (priv->auth_dnotify) {
			priv->auth_dnotify (priv->auth_data);
			priv->auth_dnotify = NULL;
		}
		priv->auth_data = g_value_get_pointer (value);
		break;
	default:
87
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
88
89
90
91
92
		break;
	}
}

static void
93
94
soup_auth_domain_basic_get_property (GObject *object, guint prop_id,
				     GValue *value, GParamSpec *pspec)
95
96
{
	SoupAuthDomainBasicPrivate *priv =
97
		soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
98
99
100
101
102
103
104
105
106

	switch (prop_id) {
	case PROP_AUTH_CALLBACK:
		g_value_set_pointer (value, priv->auth_callback);
		break;
	case PROP_AUTH_DATA:
		g_value_set_pointer (value, priv->auth_data);
		break;
	default:
107
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
108
109
110
111
112
		break;
	}
}

/**
113
 * soup_auth_domain_basic_new: (constructor)
114
115
116
 * @optname1: name of first option, or %NULL
 * @...: option name/value pairs
 *
117
118
119
120
 * Creates a #SoupAuthDomainBasic.
 *
 * You must set the [property@AuthDomain:realm] property, to indicate the realm
 * name to be returned with the authentication challenge to the client. Other
121
122
 * parameters are optional.
 *
123
 * Returns: the new #SoupAuthDomain
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
 **/
SoupAuthDomain *
soup_auth_domain_basic_new (const char *optname1, ...)
{
	SoupAuthDomain *domain;
	va_list ap;

	va_start (ap, optname1);
	domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_BASIC,
							optname1, ap);
	va_end (ap);

	g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);

	return domain;
}

/**
 * SoupAuthDomainBasicAuthCallback:
143
 * @domain: (type SoupAuthDomainBasic): the domain
144
145
146
 * @msg: the message being authenticated
 * @username: the username provided by the client
 * @password: the password provided by the client
147
 * @user_data: the data passed to [method@AuthDomainBasic.set_auth_callback]
148
149
 *
 * Callback used by #SoupAuthDomainBasic for authentication purposes.
150
 *
151
152
153
154
155
156
157
158
159
160
161
162
163
164
 * The application should verify that @username and @password and valid
 * and return %TRUE or %FALSE.
 *
 * If you are maintaining your own password database (rather than
 * using the password to authenticate against some other system like
 * PAM or a remote server), you should make sure you know what you are
 * doing. In particular, don't store cleartext passwords, or
 * easily-computed hashes of cleartext passwords, even if you don't
 * care that much about the security of your server, because users
 * will frequently use the same password for multiple sites, and so
 * compromising any site with a cleartext (or easily-cracked) password
 * database may give attackers access to other more-interesting sites
 * as well.
 *
165
 * Returns: %TRUE if @username and @password are valid
166
167
168
 **/

/**
169
 * soup_auth_domain_basic_set_auth_callback: (attributes org.gtk.Method.set_property=auth-callback)
170
 * @domain: (type SoupAuthDomainBasic): the domain
171
172
173
 * @callback: the callback
 * @user_data: data to pass to @auth_callback
 * @dnotify: destroy notifier to free @user_data when @domain
174
 *   is destroyed
175
176
 *
 * Sets the callback that @domain will use to authenticate incoming
177
178
179
180
181
 * requests.
 *
 * For each request containing authorization, @domain will invoke the callback,
 * and then either accept or reject the request based on @callback's return
 * value.
182
183
 *
 * You can also set the auth callback by setting the
184
185
186
 * [property@AuthDomainBasic:auth-callback] and
 * [property@AuthDomainBasic:auth-data] properties, which can also be used to
 * set the callback at construct time.
187
188
189
190
191
192
193
194
 **/
void
soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain,
					  SoupAuthDomainBasicAuthCallback callback,
					  gpointer        user_data,
					  GDestroyNotify  dnotify)
{
	SoupAuthDomainBasicPrivate *priv =
195
		soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (domain));
196
197
198
199
200
201
202
203

	if (priv->auth_dnotify)
		priv->auth_dnotify (priv->auth_data);

	priv->auth_callback = callback;
	priv->auth_data = user_data;
	priv->auth_dnotify = dnotify;

204
205
	g_object_notify_by_pspec (G_OBJECT (domain), properties[PROP_AUTH_CALLBACK]);
	g_object_notify_by_pspec (G_OBJECT (domain), properties[PROP_AUTH_DATA]);
206
207
208
209
210
211
212
213
214
}

static void
pw_free (char *pw)
{
	memset (pw, 0, strlen (pw));
	g_free (pw);
}

215
static gboolean
216
217
218
parse_basic (const char *header,
	     char      **username,
	     char      **password)
219
220
221
222
{
	char *decoded, *colon;
	gsize len, plen;

223
	if (!header || (strncmp (header, "Basic ", 6) != 0))
224
		return FALSE;
225
226
227

	decoded = (char *)g_base64_decode (header + 6, &len);
	if (!decoded)
228
		return FALSE;
229
230
231
232

	colon = memchr (decoded, ':', len);
	if (!colon) {
		pw_free (decoded);
233
		return FALSE;
234
235
236
237
	}
	*colon = '\0';
	plen = len - (colon - decoded) - 1;

238
	*password = g_strndup (colon + 1, plen);
239
	memset (colon + 1, 0, plen);
240
241
242
243
244
	*username = decoded;
	return TRUE;
}

static char *
245
246
247
soup_auth_domain_basic_accepts (SoupAuthDomain    *domain,
				SoupServerMessage *msg,
				const char        *header)
248
249
{
	SoupAuthDomainBasicPrivate *priv =
250
		soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (domain));
251
252
253
	char *username, *password;
	gboolean ok = FALSE;

254
	if (!parse_basic (header, &username, &password))
255
256
257
258
259
260
261
262
263
		return NULL;

	if (priv->auth_callback) {
		ok = priv->auth_callback (domain, msg, username, password,
					  priv->auth_data);
	} else {
		ok = soup_auth_domain_try_generic_auth_callback (
			domain, msg, username);
	}
264
265
266
267
268
269
270
271
272
273
274
275

	pw_free (password);

	if (ok)
		return username;
	else {
		g_free (username);
		return NULL;
	}
}

static char *
276
277
soup_auth_domain_basic_challenge (SoupAuthDomain    *domain,
				  SoupServerMessage *msg)
278
{
279
280
281
282
283
	GString *challenge;

	challenge = g_string_new ("Basic ");
	soup_header_g_string_append_param (challenge, "realm", soup_auth_domain_get_realm (domain));
	return g_string_free (challenge, FALSE);
284
}
285
286

static gboolean
287
288
289
290
soup_auth_domain_basic_check_password (SoupAuthDomain    *domain,
				       SoupServerMessage *msg,
				       const char        *username,
				       const char        *password)
291
292
293
294
295
{
	const char *header;
	char *msg_username, *msg_password;
	gboolean ok;

296
297
	header = soup_message_headers_get_one_common (soup_server_message_get_request_headers (msg),
                                                      SOUP_HEADER_AUTHORIZATION);
298
	if (!parse_basic (header, &msg_username, &msg_password))
299
300
301
302
303
304
305
306
307
		return FALSE;

	ok = (!strcmp (username, msg_username) &&
	      !strcmp (password, msg_password));
	g_free (msg_username);
	pw_free (msg_password);

	return ok;
}
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323

static void
soup_auth_domain_basic_class_init (SoupAuthDomainBasicClass *basic_class)
{
	SoupAuthDomainClass *auth_domain_class =
		SOUP_AUTH_DOMAIN_CLASS (basic_class);
	GObjectClass *object_class = G_OBJECT_CLASS (basic_class);

	auth_domain_class->accepts        = soup_auth_domain_basic_accepts;
	auth_domain_class->challenge      = soup_auth_domain_basic_challenge;
	auth_domain_class->check_password = soup_auth_domain_basic_check_password;

	object_class->finalize     = soup_auth_domain_basic_finalize;
	object_class->set_property = soup_auth_domain_basic_set_property;
	object_class->get_property = soup_auth_domain_basic_get_property;

324
	/**
325
	 * SoupAuthDomainBasic:auth-callback: (type SoupAuthDomainBasicAuthCallback) (attributes org.gtk.Property.set=soup_auth_domain_basic_set_auth_callback)
326
	 *
327
	 * The [callback@AuthDomainBasicAuthCallback].
328
	 */
329
        properties[PROP_AUTH_CALLBACK] =
Patrick Griffis's avatar
Patrick Griffis committed
330
		g_param_spec_pointer ("auth-callback",
331
332
				      "Authentication callback",
				      "Password-checking callback",
333
				      G_PARAM_READWRITE |
334
				      G_PARAM_STATIC_STRINGS);
335
336
337
	/**
	 * SoupAuthDomainBasic:auth-data:
	 *
338
	 * The data to pass to the [callback@AuthDomainBasicAuthCallback].
339
	 */
340
        properties[PROP_AUTH_DATA] =
Patrick Griffis's avatar
Patrick Griffis committed
341
		g_param_spec_pointer ("auth-data",
342
343
				      "Authentication callback data",
				      "Data to pass to authentication callback",
344
				      G_PARAM_READWRITE |
345
346
347
				      G_PARAM_STATIC_STRINGS);

        g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
348
}