Add regression test for unix:runtime=yes
[platform/upstream/dbus.git] / test / sd-activation.c
1 /* Unit tests for systemd activation.
2  *
3  * Copyright © 2010-2011 Nokia Corporation
4  * Copyright © 2015 Collabora Ltd.
5  *
6  * Permission is hereby granted, free of charge, to any person
7  * obtaining a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction,
9  * including without limitation the rights to use, copy, modify, merge,
10  * publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so,
12  * subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24  * SOFTWARE.
25  */
26
27 #include <config.h>
28
29 #include <string.h>
30
31 #include "test-utils-glib.h"
32
33 typedef struct {
34     TestMainContext *ctx;
35     DBusError e;
36     GError *ge;
37
38     gchar *address;
39     GPid daemon_pid;
40
41     DBusConnection *caller;
42     const char *caller_name;
43     DBusConnection *systemd;
44     const char *systemd_name;
45     DBusMessage *systemd_message;
46     DBusConnection *activated;
47     const char *activated_name;
48     DBusMessage *activated_message;
49 } Fixture;
50
51 /* this is a macro so it gets the right line number */
52 #define assert_signal(m, \
53     sender, path, iface, member, signature, \
54     destination) \
55 do { \
56   g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
57       ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \
58   g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \
59   g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \
60   g_assert_cmpstr (dbus_message_get_path (m), ==, path); \
61   g_assert_cmpstr (dbus_message_get_interface (m), ==, iface); \
62   g_assert_cmpstr (dbus_message_get_member (m), ==, member); \
63   g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \
64   g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
65   g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \
66 } while (0)
67
68 static DBusHandlerResult
69 systemd_filter (DBusConnection *connection,
70     DBusMessage *message,
71     void *user_data)
72 {
73   Fixture *f = user_data;
74
75   if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
76         "NameAcquired") ||
77       dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
78         "NameLost"))
79     {
80       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
81     }
82
83   g_assert (f->systemd_message == NULL);
84   f->systemd_message = dbus_message_ref (message);
85
86   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
87 }
88
89 static DBusHandlerResult
90 activated_filter (DBusConnection *connection,
91     DBusMessage *message,
92     void *user_data)
93 {
94   Fixture *f = user_data;
95
96   if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
97         "NameAcquired") ||
98       dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
99         "NameLost"))
100     {
101       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
102     }
103
104   g_assert (f->activated_message == NULL);
105   f->activated_message = dbus_message_ref (message);
106
107   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
108 }
109
110 static void
111 setup (Fixture *f,
112     gconstpointer context G_GNUC_UNUSED)
113 {
114   f->ctx = test_main_context_get ();
115
116   f->ge = NULL;
117   dbus_error_init (&f->e);
118
119   f->address = test_get_dbus_daemon (
120       "valid-config-files/systemd-activation.conf",
121       TEST_USER_ME, &f->daemon_pid);
122
123   if (f->address == NULL)
124     return;
125
126   f->caller = test_connect_to_bus (f->ctx, f->address);
127   f->caller_name = dbus_bus_get_unique_name (f->caller);
128 }
129
130 static void
131 take_well_known_name (Fixture *f,
132     DBusConnection *connection,
133     const char *name)
134 {
135   int ret;
136
137   ret = dbus_bus_request_name (connection, name,
138       DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
139   test_assert_no_error (&f->e);
140   g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
141 }
142
143 static void
144 test_activation (Fixture *f,
145     gconstpointer context)
146 {
147   DBusMessage *m;
148
149   if (f->address == NULL)
150     return;
151
152   /* The sender sends a message to an activatable service. */
153   m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1");
154   if (!dbus_message_set_destination (m, "com.example.SystemdActivatable1"))
155     g_error ("OOM");
156   dbus_connection_send (f->caller, m, NULL);
157   dbus_message_unref (m);
158
159   /* The fake systemd connects to the bus. */
160   f->systemd = test_connect_to_bus (f->ctx, f->address);
161   if (!dbus_connection_add_filter (f->systemd, systemd_filter, f, NULL))
162     g_error ("OOM");
163   f->systemd_name = dbus_bus_get_unique_name (f->systemd);
164   take_well_known_name (f, f->systemd, "org.freedesktop.systemd1");
165
166   /* It gets its activation request. */
167   while (f->systemd_message == NULL)
168     test_main_context_iterate (f->ctx, TRUE);
169
170   m = f->systemd_message;
171   f->systemd_message = NULL;
172   assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
173       "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
174       "org.freedesktop.systemd1");
175   dbus_message_unref (m);
176
177   /* systemd starts the activatable service. */
178   f->activated = test_connect_to_bus (f->ctx, f->address);
179   if (!dbus_connection_add_filter (f->activated, activated_filter,
180         f, NULL))
181     g_error ("OOM");
182   f->activated_name = dbus_bus_get_unique_name (f->activated);
183   take_well_known_name (f, f->activated, "com.example.SystemdActivatable1");
184
185   /* The message is delivered to the activatable service. */
186   while (f->activated_message == NULL)
187     test_main_context_iterate (f->ctx, TRUE);
188
189   m = f->activated_message;
190   f->activated_message = NULL;
191   assert_signal (m, f->caller_name, "/foo",
192       "com.example.bar", "UnicastSignal1", "",
193       "com.example.SystemdActivatable1");
194   dbus_message_unref (m);
195
196   /* The sender sends a message to a different activatable service. */
197   m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal2");
198   if (!dbus_message_set_destination (m, "com.example.SystemdActivatable2"))
199     g_error ("OOM");
200   dbus_connection_send (f->caller, m, NULL);
201   dbus_message_unref (m);
202
203   /* This time systemd is already ready for it. */
204   while (f->systemd_message == NULL)
205     test_main_context_iterate (f->ctx, TRUE);
206
207   m = f->systemd_message;
208   f->systemd_message = NULL;
209   assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
210       "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
211       "org.freedesktop.systemd1");
212   dbus_message_unref (m);
213
214   /* A malicious process tries to disrupt the activation.
215    * In a more realistic scenario this would be another parallel
216    * connection. */
217   m = dbus_message_new_signal ("/org/freedesktop/systemd1",
218       "org.freedesktop.systemd1.Activator", "ActivationFailure");
219   if (!dbus_message_set_destination (m, "org.freedesktop.DBus"))
220     g_error ("OOM");
221
222   do
223     {
224       const char *unit = "dbus-com.example.SystemdActivatable2.service";
225       const char *error_name = "com.example.Malice";
226       const char *error_message = "I'm on yr bus, making yr activations fail";
227
228       if (!dbus_message_append_args (m,
229             DBUS_TYPE_STRING, &unit,
230             DBUS_TYPE_STRING, &error_name,
231             DBUS_TYPE_STRING, &error_message,
232             DBUS_TYPE_INVALID))
233         g_error ("OOM");
234     }
235   while (0);
236
237   dbus_connection_send (f->caller, m, NULL);
238   dbus_message_unref (m);
239
240   /* This is just to make sure that the malicious message has arrived and
241    * been processed by the dbus-daemon, i.e. @caller won the race
242    * with @activated. */
243   take_well_known_name (f, f->caller, "com.example.Sync");
244
245   /* The activatable service takes its name. Here I'm faking it by using
246    * an existing connection; in real life it would be yet another
247    * connection. */
248   take_well_known_name (f, f->activated, "com.example.SystemdActivatable2");
249
250   /* The message is delivered to the activatable service. */
251   while (f->activated_message == NULL)
252     test_main_context_iterate (f->ctx, TRUE);
253
254   m = f->activated_message;
255   f->activated_message = NULL;
256   assert_signal (m, f->caller_name, "/foo",
257       "com.example.bar", "UnicastSignal2", "",
258       "com.example.SystemdActivatable2");
259   dbus_message_unref (m);
260
261   /* A third activation. */
262   m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal3");
263   if (!dbus_message_set_destination (m, "com.example.SystemdActivatable3"))
264     g_error ("OOM");
265   dbus_connection_send (f->caller, m, NULL);
266   dbus_message_unref (m);
267
268   while (f->systemd_message == NULL)
269     test_main_context_iterate (f->ctx, TRUE);
270
271   m = f->systemd_message;
272   f->systemd_message = NULL;
273   assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
274       "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
275       "org.freedesktop.systemd1");
276   dbus_message_unref (m);
277
278   /* This time activation fails */
279   m = dbus_message_new_signal ("/org/freedesktop/systemd1",
280       "org.freedesktop.systemd1.Activator", "ActivationFailure");
281
282   do
283     {
284       const char *unit = "dbus-com.example.SystemdActivatable3.service";
285       const char *error_name = "com.example.Nope";
286       const char *error_message = "Computer says no";
287
288       if (!dbus_message_append_args (m,
289             DBUS_TYPE_STRING, &unit,
290             DBUS_TYPE_STRING, &error_name,
291             DBUS_TYPE_STRING, &error_message,
292             DBUS_TYPE_INVALID))
293         g_error ("OOM");
294     }
295   while (0);
296
297   if (!dbus_message_set_destination (m, "org.freedesktop.DBus"))
298     g_error ("OOM");
299   dbus_connection_send (f->systemd, m, NULL);
300   dbus_message_unref (m);
301 }
302
303 static void
304 teardown (Fixture *f,
305     gconstpointer context G_GNUC_UNUSED)
306 {
307   dbus_error_free (&f->e);
308   g_clear_error (&f->ge);
309
310   if (f->caller != NULL)
311     {
312       dbus_connection_close (f->caller);
313       dbus_connection_unref (f->caller);
314       f->caller = NULL;
315     }
316
317   if (f->systemd != NULL)
318     {
319       dbus_connection_remove_filter (f->systemd, systemd_filter, f);
320       dbus_connection_close (f->systemd);
321       dbus_connection_unref (f->systemd);
322       f->systemd = NULL;
323     }
324
325   if (f->activated != NULL)
326     {
327       dbus_connection_remove_filter (f->activated, activated_filter, f);
328       dbus_connection_close (f->activated);
329       dbus_connection_unref (f->activated);
330       f->activated = NULL;
331     }
332
333   test_kill_pid (f->daemon_pid);
334   g_spawn_close_pid (f->daemon_pid);
335   test_main_context_unref (f->ctx);
336   g_free (f->address);
337 }
338
339 int
340 main (int argc,
341     char **argv)
342 {
343   test_init (&argc, &argv);
344
345   g_test_add ("/sd-activation", Fixture, NULL,
346       setup, test_activation, teardown);
347
348   return g_test_run ();
349 }