1 /* GDBus - GLib D-Bus Library
2 *
3 * Copyright (C) 2008-2010 Red Hat, Inc.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General
18 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 *
20 * Author: David Zeuthen <davidz@redhat.com>
21 */
22
23 #include "config.h"
24
25 #include <string.h>
26
27 #include "gdbusauthmechanismexternal.h"
28 #include "gcredentials.h"
29 #include "gdbuserror.h"
30 #include "gioenumtypes.h"
31
32 #include "glibintl.h"
33
34 #ifdef G_OS_WIN32
35 #include "gwin32sid.h"
36 #endif
37
38 struct _GDBusAuthMechanismExternalPrivate
39 {
40 gboolean is_client;
41 gboolean is_server;
42 GDBusAuthMechanismState state;
43 gboolean empty_data_sent;
44 };
45
46 static gint mechanism_get_priority (void);
47 static const gchar *mechanism_get_name (void);
48
49 static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism);
50 static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism,
51 const gchar *data,
52 gsize data_len,
53 gsize *out_data_len);
54 static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism,
55 const gchar *data,
56 gsize data_len,
57 gsize *out_data_len);
58 static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism);
59 static void mechanism_server_initiate (GDBusAuthMechanism *mechanism,
60 const gchar *initial_response,
61 gsize initial_response_len);
62 static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
63 const gchar *data,
64 gsize data_len);
65 static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism,
66 gsize *out_data_len);
67 static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism);
68 static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism);
69 static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism);
70 static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism,
71 GDBusConnectionFlags conn_flags,
72 gsize *out_initial_response_len);
73 static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
74 const gchar *data,
75 gsize data_len);
76 static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism,
77 gsize *out_data_len);
78 static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism);
79
80 /* ---------------------------------------------------------------------------------------------------- */
81
82 G_DEFINE_TYPE_WITH_PRIVATE (GDBusAuthMechanismExternal, _g_dbus_auth_mechanism_external, G_TYPE_DBUS_AUTH_MECHANISM)
83
84 /* ---------------------------------------------------------------------------------------------------- */
85
86 static void
87 _g_dbus_auth_mechanism_external_finalize (GObject *object)
88 {
89 //GDBusAuthMechanismExternal *mechanism = G_DBUS_AUTH_MECHANISM_EXTERNAL (object);
90
91 if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize != NULL)
92 G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize (object);
93 }
94
95 static void
96 _g_dbus_auth_mechanism_external_class_init (GDBusAuthMechanismExternalClass *klass)
97 {
98 GObjectClass *gobject_class;
99 GDBusAuthMechanismClass *mechanism_class;
100
101 gobject_class = G_OBJECT_CLASS (klass);
102 gobject_class->finalize = _g_dbus_auth_mechanism_external_finalize;
103
104 mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
105 mechanism_class->get_name = mechanism_get_name;
106 mechanism_class->get_priority = mechanism_get_priority;
107 mechanism_class->is_supported = mechanism_is_supported;
108 mechanism_class->encode_data = mechanism_encode_data;
109 mechanism_class->decode_data = mechanism_decode_data;
110 mechanism_class->server_get_state = mechanism_server_get_state;
111 mechanism_class->server_initiate = mechanism_server_initiate;
112 mechanism_class->server_data_receive = mechanism_server_data_receive;
113 mechanism_class->server_data_send = mechanism_server_data_send;
114 mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason;
115 mechanism_class->server_shutdown = mechanism_server_shutdown;
116 mechanism_class->client_get_state = mechanism_client_get_state;
117 mechanism_class->client_initiate = mechanism_client_initiate;
118 mechanism_class->client_data_receive = mechanism_client_data_receive;
119 mechanism_class->client_data_send = mechanism_client_data_send;
120 mechanism_class->client_shutdown = mechanism_client_shutdown;
121 }
122
123 static void
124 _g_dbus_auth_mechanism_external_init (GDBusAuthMechanismExternal *mechanism)
125 {
126 mechanism->priv = _g_dbus_auth_mechanism_external_get_instance_private (mechanism);
127 }
128
129 /* ---------------------------------------------------------------------------------------------------- */
130
131 static gboolean
132 mechanism_is_supported (GDBusAuthMechanism *mechanism)
133 {
134 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), FALSE);
135
136 #if defined(G_OS_WIN32)
137 /* all that is required is current process SID */
138 return TRUE;
139 #else
140 /* This mechanism is only available if credentials has been exchanged */
141 if (_g_dbus_auth_mechanism_get_credentials (mechanism) != NULL)
142 return TRUE;
143 else
144 return FALSE;
145 #endif
146 }
147
148 static gint
149 mechanism_get_priority (void)
150 {
151 /* We prefer EXTERNAL to most other mechanism (DBUS_COOKIE_SHA1 and ANONYMOUS) */
152 return 100;
153 }
154
155 static const gchar *
156 mechanism_get_name (void)
157 {
158 return "EXTERNAL";
159 }
160
161 static gchar *
162 mechanism_encode_data (GDBusAuthMechanism *mechanism,
163 const gchar *data,
164 gsize data_len,
165 gsize *out_data_len)
166 {
167 return NULL;
168 }
169
170
171 static gchar *
172 mechanism_decode_data (GDBusAuthMechanism *mechanism,
173 const gchar *data,
174 gsize data_len,
175 gsize *out_data_len)
176 {
177 return NULL;
178 }
179
180 /* ---------------------------------------------------------------------------------------------------- */
181
182 static GDBusAuthMechanismState
183 mechanism_server_get_state (GDBusAuthMechanism *mechanism)
184 {
185 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
186
187 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
188 g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
189
190 return m->priv->state;
191 }
192
193 static gboolean
194 data_matches_credentials (const gchar *data,
195 gsize data_len,
196 GCredentials *credentials)
197 {
198 gboolean match;
199
200 match = FALSE;
201
202 if (credentials == NULL)
203 goto out;
204
205 #if defined(G_OS_UNIX)
206 {
207 gint64 alleged_uid;
208 gchar *endp;
209
210 /* If we were unable to find out the uid, then nothing
211 * can possibly match it. */
212 if (g_credentials_get_unix_user (credentials, NULL) == (uid_t) -1)
213 goto out;
214
215 /* An empty authorization identity means we want to be
216 * whatever identity the out-of-band credentials say we have
217 * (RFC 4422 appendix A.1). This effectively matches any uid. */
218 if (data == NULL || data_len == 0)
219 {
220 match = TRUE;
221 goto out;
222 }
223 /* on UNIX, this is the uid as a string in base 10 */
224 alleged_uid = g_ascii_strtoll (data, &endp, 10);
225 if (*endp == '\0')
226 {
227 if (g_credentials_get_unix_user (credentials, NULL) == alleged_uid)
228 {
229 match = TRUE;
230 }
231 }
232 }
233 #else
234 /* TODO: Dont know how to compare credentials on this OS. Please implement. */
235 #endif
236
237 out:
238 return match;
239 }
240
241 static void
242 mechanism_server_initiate (GDBusAuthMechanism *mechanism,
243 const gchar *initial_response,
244 gsize initial_response_len)
245 {
246 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
247
248 g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
249 g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
250
251 m->priv->is_server = TRUE;
252
253 if (initial_response != NULL)
254 {
255 if (data_matches_credentials (initial_response,
256 initial_response_len,
257 _g_dbus_auth_mechanism_get_credentials (mechanism)))
258 {
259 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
260 }
261 else
262 {
263 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
264 }
265 }
266 else
267 {
268 /* The initial-response optimization was not used, so we need to
269 * send an empty challenge to prompt the client to respond. */
270 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
271 }
272 }
273
274 static void
275 mechanism_server_data_receive (GDBusAuthMechanism *mechanism,
276 const gchar *data,
277 gsize data_len)
278 {
279 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
280
281 g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
282 g_return_if_fail (m->priv->is_server && !m->priv->is_client);
283 g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
284
285 if (data_matches_credentials (data,
286 data_len,
287 _g_dbus_auth_mechanism_get_credentials (mechanism)))
288 {
289 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
290 }
291 else
292 {
293 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
294 }
295 }
296
297 static gchar *
298 mechanism_server_data_send (GDBusAuthMechanism *mechanism,
299 gsize *out_data_len)
300 {
301 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
302
303 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
304 g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
305
306 if (out_data_len)
307 *out_data_len = 0;
308
309 if (m->priv->empty_data_sent)
310 {
311 /* We have already sent an empty data response.
312 Reject the connection. */
313 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
314 return NULL;
315 }
316
317 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
318 m->priv->empty_data_sent = TRUE;
319
320 return g_strdup ("");
321 }
322
323 static gchar *
324 mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism)
325 {
326 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
327
328 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
329 g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
330 g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
331
332 /* can never end up here because we are never in the REJECTED state */
333 g_assert_not_reached ();
334
335 return NULL;
336 }
337
338 static void
339 mechanism_server_shutdown (GDBusAuthMechanism *mechanism)
340 {
341 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
342
343 g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
344 g_return_if_fail (m->priv->is_server && !m->priv->is_client);
345
346 m->priv->is_server = FALSE;
347 }
348
349 /* ---------------------------------------------------------------------------------------------------- */
350
351 static GDBusAuthMechanismState
352 mechanism_client_get_state (GDBusAuthMechanism *mechanism)
353 {
354 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
355
356 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
357 g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
358
359 return m->priv->state;
360 }
361
362 static gchar *
363 mechanism_client_initiate (GDBusAuthMechanism *mechanism,
364 GDBusConnectionFlags conn_flags,
365 gsize *out_initial_response_len)
366 {
367 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
368 gchar *initial_response = NULL;
369
370 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
371 g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
372
373 m->priv->is_client = TRUE;
374 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
375
376 *out_initial_response_len = 0;
377
378 if (conn_flags & G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE)
379 {
380 /* If backwards-compatibility with GDBus servers < 2.73.3 is not a
381 * concern, we do not send an initial response, because there is
382 * no way to express an empty authorization identity this way.
383 * Instead, we'll reply to the server's first (empty) challenge
384 * with an empty authorization identity in our first response. */
385 g_debug ("Using cross-namespace EXTERNAL authentication (this will deadlock if server is GDBus < 2.73.3)");
386 }
387 else
388 {
389 /* Send the Unix uid or Windows SID as an initial response.
390 * This is the only thing that is interoperable with GDBus 2.73.3
391 * servers. */
392 #if defined(G_OS_UNIX)
393 GCredentials *credentials;
394
395 credentials = _g_dbus_auth_mechanism_get_credentials (mechanism);
396 g_assert (credentials != NULL);
397
398 initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) g_credentials_get_unix_user (credentials, NULL));
399 #elif defined(G_OS_WIN32)
400 initial_response = _g_win32_current_process_sid_string (NULL);
401 #else
402 /* GDBus < 2.73.3 servers can't have worked on this platform anyway,
403 * so it isn't a regression to behave as though
404 * G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE had been set. */
405 g_debug ("Unknown platform, cannot use initial response in EXTERNAL");
406 #endif
407 }
408
409 if (initial_response)
410 {
411 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
412 *out_initial_response_len = strlen (initial_response);
413 }
414 return initial_response;
415 }
416
417 static void
418 mechanism_client_data_receive (GDBusAuthMechanism *mechanism,
419 const gchar *data,
420 gsize data_len)
421 {
422 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
423
424 g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
425 g_return_if_fail (m->priv->is_client && !m->priv->is_server);
426 g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
427
428 /* The server sent us a challenge, which should normally
429 * be empty. We respond with our authorization identity. */
430 m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
431 }
432
433 static gchar *
434 mechanism_client_data_send (GDBusAuthMechanism *mechanism,
435 gsize *out_data_len)
436 {
437 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
438
439 g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
440 g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
441 g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
442
443 /* We respond to the server's challenge by sending our
444 * authorization identity, which is the empty string, meaning
445 * whoever the out-of-band credentials say we are. */
446 *out_data_len = 0;
447 return g_strdup ("");
448 }
449
450 static void
451 mechanism_client_shutdown (GDBusAuthMechanism *mechanism)
452 {
453 GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
454
455 g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
456 g_return_if_fail (m->priv->is_client && !m->priv->is_server);
457
458 m->priv->is_client = FALSE;
459 }
460
461 /* ---------------------------------------------------------------------------------------------------- */