1 /*
2 * Copyright 2019-2022 Collabora Ltd.
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include <errno.h>
23 #include <locale.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <glib.h>
28 #include <glib/gstdio.h>
29 #include <gio/gio.h>
30
31 /* For G_CREDENTIALS_*_SUPPORTED */
32 #include <gio/gcredentialsprivate.h>
33
34 static const char * const explicit_external_initial_response_fail[] =
35 {
36 "EXTERNAL with incorrect initial response",
37 "C:AUTH EXTERNAL <wrong-uid>",
38 "S:REJECTED.*$",
39 NULL
40 };
41
42 static const char * const explicit_external_fail[] =
43 {
44 "EXTERNAL without initial response, failing to authenticate",
45 "C:AUTH EXTERNAL",
46 "S:DATA$",
47 "C:DATA <wrong-uid>",
48 "S:REJECTED.*$",
49 NULL
50 };
51
52 #if defined(G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED) || defined(G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED)
53 static const char * const explicit_external_initial_response[] =
54 {
55 "EXTERNAL with initial response",
56 /* This is what most older D-Bus libraries do. */
57 "C:AUTH EXTERNAL <uid>", /* I claim to be <uid> */
58 "S:OK [0-9a-f]+$",
59 NULL
60 };
61
62 static const char * const explicit_external[] =
63 {
64 "EXTERNAL without initial response",
65 /* In theory this is equally valid, although many D-Bus libraries
66 * probably don't support it correctly. */
67 "C:AUTH EXTERNAL", /* Start EXTERNAL, no initial response */
68 "S:DATA$", /* Who are you? */
69 "C:DATA <uid>", /* I claim to be <uid> */
70 "S:OK [0-9a-f]+$",
71 NULL
72 };
73
74 static const char * const implicit_external[] =
75 {
76 "EXTERNAL with empty authorization identity",
77 /* This is what sd-bus does. */
78 "C:AUTH EXTERNAL", /* Start EXTERNAL, no initial response */
79 "S:DATA$", /* Who are you? */
80 "C:DATA", /* I'm whoever the kernel says I am */
81 "S:OK [0-9a-f]+$",
82 NULL
83 };
84
85 static const char * const implicit_external_space[] =
86 {
87 "EXTERNAL with empty authorization identity and whitespace",
88 /* GDBus used to represent empty data blocks like this, although it
89 * isn't interoperable to do so (in particular sd-bus would reject this). */
90 "C:AUTH EXTERNAL", /* Start EXTERNAL, no initial response */
91 "S:DATA$", /* Who are you? */
92 "C:DATA ", /* I'm whoever the kernel says I am */
93 "S:OK [0-9a-f]+$",
94 NULL
95 };
96 #endif
97
98 static const char * const * const handshakes[] =
99 {
100 explicit_external_initial_response_fail,
101 explicit_external_fail,
102 #if defined(G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED) || defined(G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED)
103 explicit_external_initial_response,
104 explicit_external,
105 implicit_external,
106 implicit_external_space,
107 #endif
108 };
109
110 static void
111 encode_uid (guint uid,
112 GString *dest)
113 {
114 gchar *str = g_strdup_printf ("%u", uid);
115 gchar *p;
116
117 g_string_assign (dest, "");
118
119 for (p = str; *p != '\0'; p++)
120 g_string_append_printf (dest, "%02x", (unsigned char) *p);
121
122 g_free (str);
123 }
124
125 typedef struct
126 {
127 GCond cond;
128 GMutex mutex;
129 GDBusServerFlags server_flags;
130 GMainContext *ctx;
131 GMainLoop *loop;
132 gchar *guid;
133 gchar *listenable_address;
134 gboolean ready;
135 } ServerInfo;
136
137 static gboolean
138 idle_in_server_thread_cb (gpointer user_data)
139 {
140 ServerInfo *info = user_data;
141
142 g_mutex_lock (&info->mutex);
143 info->ready = TRUE;
144 g_cond_broadcast (&info->cond);
145 g_mutex_unlock (&info->mutex);
146 return G_SOURCE_REMOVE;
147 }
148
149 static gpointer
150 server_thread_cb (gpointer user_data)
151 {
152 GDBusServer *server = NULL;
153 GError *error = NULL;
154 GSource *source;
155 ServerInfo *info = user_data;
156
157 g_main_context_push_thread_default (info->ctx);
158 server = g_dbus_server_new_sync (info->listenable_address,
159 info->server_flags,
160 info->guid,
161 NULL,
162 NULL,
163 &error);
164 g_assert_no_error (error);
165 g_assert_nonnull (server);
166 g_dbus_server_start (server);
167
168 /* Tell the main thread when the server is ready to accept connections */
169 source = g_idle_source_new ();
170 g_source_set_callback (source, idle_in_server_thread_cb, info, NULL);
171 g_source_attach (source, info->ctx);
172 g_source_unref (source);
173
174 g_main_loop_run (info->loop);
175
176 g_main_context_pop_thread_default (info->ctx);
177 g_dbus_server_stop (server);
178 g_clear_object (&server);
179 return NULL;
180 }
181
182 static void
183 test_sasl_server (void)
184 {
185 GError *error = NULL;
186 GSocketAddress *addr = NULL;
187 GString *buf = g_string_new ("");
188 GString *encoded_uid = g_string_new ("");
189 GString *encoded_wrong_uid = g_string_new ("");
190 GThread *server_thread = NULL;
191 ServerInfo info =
192 {
193 .server_flags = G_DBUS_SERVER_FLAGS_RUN_IN_THREAD,
194 };
195 gchar *escaped = NULL;
196 gchar *path = NULL;
197 gchar *tmpdir = NULL;
198 gsize i;
199
200 tmpdir = g_dir_make_tmp ("gdbus-server-auth-XXXXXX", &error);
201 g_assert_no_error (error);
202 escaped = g_dbus_address_escape_value (tmpdir);
203
204 path = g_build_filename (tmpdir, "socket", NULL);
205 g_cond_init (&info.cond);
206 g_mutex_init (&info.mutex);
207 info.ctx = g_main_context_new ();
208 info.guid = g_dbus_generate_guid ();
209 info.listenable_address = g_strdup_printf ("unix:path=%s/socket", escaped);
210 info.loop = g_main_loop_new (info.ctx, FALSE);
211 info.ready = FALSE;
212 server_thread = g_thread_new ("GDBusServer", server_thread_cb, &info);
213
214 g_mutex_lock (&info.mutex);
215
216 while (!info.ready)
217 g_cond_wait (&info.cond, &info.mutex);
218
219 g_mutex_unlock (&info.mutex);
220
221 addr = g_unix_socket_address_new (path);
222
223 encode_uid (geteuid (), encoded_uid);
224 encode_uid (geteuid () == 0 ? 65534 : 0, encoded_wrong_uid);
225
226 for (i = 0; i < G_N_ELEMENTS (handshakes); i++)
227 {
228 const char * const *handshake = handshakes[i];
229 GSocketClient *client;
230 GSocketConnection *conn;
231 GUnixConnection *conn_unix; /* unowned */
232 GInputStream *istream; /* unowned */
233 GDataInputStream *istream_data;
234 GOutputStream *ostream; /* unowned */
235 GError *error = NULL;
236 gsize j;
237
238 g_test_message ("New handshake: %s", handshake[0]);
239
240 client = g_socket_client_new ();
241 conn = g_socket_client_connect (client, G_SOCKET_CONNECTABLE (addr),
242 NULL, &error);
243 g_assert_no_error (error);
244
245 g_assert_true (G_IS_UNIX_CONNECTION (conn));
246 conn_unix = G_UNIX_CONNECTION (conn);
247 istream = g_io_stream_get_input_stream (G_IO_STREAM (conn));
248 ostream = g_io_stream_get_output_stream (G_IO_STREAM (conn));
249 istream_data = g_data_input_stream_new (istream);
250 g_data_input_stream_set_newline_type (istream_data, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
251
252 g_unix_connection_send_credentials (conn_unix, NULL, &error);
253 g_assert_no_error (error);
254
255 for (j = 1; handshake[j] != NULL; j++)
256 {
257 if (j % 2 == 1)
258 {
259 /* client to server */
260 const char *line = handshake[j];
261
262 g_assert_cmpint (line[0], ==, 'C');
263 g_assert_cmpint (line[1], ==, ':');
264 g_string_assign (buf, line + 2);
265 g_string_replace (buf, "<uid>", encoded_uid->str, 0);
266 g_string_replace (buf, "<wrong-uid>", encoded_wrong_uid->str, 0);
267 g_test_message ("C:“%s”", buf->str);
268 g_string_append (buf, "\r\n");
269
270 g_output_stream_write_all (ostream, buf->str, buf->len, NULL, NULL, &error);
271 g_assert_no_error (error);
272 }
273 else
274 {
275 /* server to client */
276 const char *pattern = handshake[j];
277 char *line;
278 gsize len;
279
280 g_assert_cmpint (pattern[0], ==, 'S');
281 g_assert_cmpint (pattern[1], ==, ':');
282
283 g_test_message ("Expect: /^%s/", pattern + 2);
284 line = g_data_input_stream_read_line (istream_data, &len, NULL, &error);
285 g_assert_no_error (error);
286 g_test_message ("S:“%s”", line);
287 g_assert_cmpuint (len, ==, strlen (line));
288
289 if (!g_regex_match_simple (pattern + 2, line,
290 G_REGEX_ANCHORED,
291 G_REGEX_MATCH_ANCHORED))
292 g_error ("Expected /^%s/, got “%s”", pattern + 2, line);
293
294 g_free (line);
295 }
296 }
297
298 g_object_unref (istream_data);
299 g_object_unref (conn);
300 g_object_unref (client);
301 }
302
303 g_main_loop_quit (info.loop);
304 g_thread_join (server_thread);
305
306 if (tmpdir != NULL)
307 g_assert_no_errno (g_rmdir (tmpdir));
308
309 g_clear_pointer (&info.ctx, g_main_context_unref);
310 g_clear_pointer (&info.loop, g_main_loop_unref);
311 g_clear_object (&addr);
312 g_string_free (buf, TRUE);
313 g_string_free (encoded_uid, TRUE);
314 g_string_free (encoded_wrong_uid, TRUE);
315 g_free (escaped);
316 g_free (info.guid);
317 g_free (info.listenable_address);
318 g_free (path);
319 g_free (tmpdir);
320 g_cond_clear (&info.cond);
321 g_mutex_clear (&info.mutex);
322 }
323
324 int
325 main (int argc,
326 char *argv[])
327 {
328 setlocale (LC_ALL, "");
329 g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
330
331 g_test_add_func ("/gdbus/sasl/server", test_sasl_server);
332
333 return g_test_run();
334 }