dbus-marshal-validate: Validate length of arrays of fixed-length items
[platform/upstream/dbus.git] / tools / dbus-update-activation-environment.c
1 /*
2  * dbus-update-activation-environment - update D-Bus, and optionally
3  * systemd, activation environment
4  *
5  * Copyright © 2014-2015 Collabora Ltd.
6  *
7  * Permission is hereby granted, free of charge, to any person
8  * obtaining a copy of this software and associated documentation files
9  * (the "Software"), to deal in the Software without restriction,
10  * including without limitation the rights to use, copy, modify, merge,
11  * publish, distribute, sublicense, and/or sell copies of the Software,
12  * and to permit persons to whom the Software is furnished to do so,
13  * subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25  * SOFTWARE.
26  */
27
28 #include <config.h>
29
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #ifdef HAVE_SYSEXITS_H
35 # include <sysexits.h>
36 #endif
37
38 #include <dbus/dbus.h>
39
40 #ifdef DBUS_UNIX
41 # include <unistd.h>
42 # include <sys/stat.h>
43 # include <sys/types.h>
44 #endif
45
46 #include "tool-common.h"
47
48 #define PROGNAME "dbus-update-activation-environment"
49
50 #ifndef EX_USAGE
51 # define EX_USAGE 64
52 #endif
53
54 #ifndef EX_UNAVAILABLE
55 # define EX_UNAVAILABLE 69
56 #endif
57
58 #ifndef EX_OSERR
59 # define EX_OSERR 71
60 #endif
61
62 #ifdef DBUS_WIN
63 /* The Windows C runtime uses a different name */
64 #define environ _environ
65 #elif defined(__APPLE__)
66 # include <crt_externs.h>
67 # define environ (*_NSGetEnviron ())
68 #elif HAVE_DECL_ENVIRON && defined(HAVE_UNISTD_H)
69 # include <unistd.h>
70 #else
71 extern char **environ;
72 #endif
73
74 /* we don't really have anything useful to say about the stage at which we
75  * failed */
76 #define oom() tool_oom ("updating environment")
77
78 static dbus_bool_t verbose = FALSE;
79
80 static void say (const char *format, ...) _DBUS_GNUC_PRINTF (1, 2);
81
82 static void
83 say (const char *format,
84     ...)
85 {
86   va_list ap;
87
88   if (!verbose)
89     return;
90
91   fprintf (stderr, "%s: ", PROGNAME);
92   va_start (ap, format);
93   vfprintf (stderr, format, ap);
94   fputc ('\n', stderr);
95   va_end (ap);
96 }
97
98 #ifdef __linux__
99 static dbus_bool_t
100 systemd_user_running (void)
101 {
102   char *xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR");
103   char *path;
104   struct stat buf;
105   dbus_bool_t ret = FALSE;
106
107   if (xdg_runtime_dir == NULL)
108     return FALSE;
109
110   /* Assume that XDG_RUNTIME_DIR/systemd exists if and only if
111    * "systemd --user" is running. It's OK to use asprintf() here
112    * because we know we're on Linux. */
113   if (asprintf (&path, "%s/systemd", xdg_runtime_dir) < 0)
114     oom ();
115
116   if (stat (path, &buf) == 0)
117     ret = TRUE;
118
119   free (path);
120   return ret;
121 }
122 #endif
123
124 int
125 main (int argc, char **argv)
126 {
127   DBusConnection *conn;
128   DBusMessage *msg;
129   DBusMessage *reply;
130   DBusError error = DBUS_ERROR_INIT;
131   DBusMessageIter msg_iter;
132   DBusMessageIter array_iter;
133   int i;
134   int first_non_option = argc;
135   dbus_bool_t all = FALSE;
136 #ifdef __linux__
137   DBusMessage *sd_msg = NULL;
138   DBusMessageIter sd_msg_iter;
139   DBusMessageIter sd_array_iter;
140   dbus_bool_t systemd = FALSE;
141 #endif
142
143   for (i = 1; i < argc; i++)
144     {
145       if (argv[i][0] != '-')
146         {
147           first_non_option = i;
148           break;
149         }
150       else if (strcmp (argv[i], "--") == 0)
151         {
152           first_non_option = i + 1;
153           break;
154         }
155       else if (strcmp (argv[i], "--all") == 0)
156         {
157           all = TRUE;
158         }
159       else if (strcmp (argv[i], "--systemd") == 0)
160         {
161 #ifdef __linux__
162           systemd = TRUE;
163 #else
164           say ("not on Linux, ignoring --systemd argument");
165 #endif
166         }
167       else if (strcmp (argv[i], "--verbose") == 0)
168         {
169           verbose = TRUE;
170         }
171       else
172         {
173           fprintf (stderr,
174               "%1$s: update environment variables that will be set for D-Bus\n"
175               "    session services\n"
176               "\n"
177               "%1$s [options] VAR[=VAL] [VAR2[=VAL2] ...]\n"
178               "    Add specified variables to D-Bus activation environment.\n"
179               "    If omitted, values are taken from current environment;\n"
180               "    variables not found in the environment are ignored.\n"
181               "%1$s --all\n"
182               "    Add entire current environment to D-Bus activation\n"
183               "    environment.\n"
184               "\n"
185               "Options:\n"
186               "\n"
187               "--all\n"
188               "    Upload all environment variables.\n"
189               "--systemd\n"
190               "    Also update the 'systemd --user' environment\n"
191               "    if possible.\n"
192               "--verbose\n"
193               "    Talk about it.\n"
194               ,
195               PROGNAME);
196           exit (EX_USAGE);
197         }
198     }
199
200   if (all && first_non_option < argc)
201     {
202       fprintf (stderr, "%s: error: --all cannot be used with VAR or "
203                "VAR=VAL arguments\n", PROGNAME);
204       exit (EX_USAGE);
205     }
206
207   conn = dbus_bus_get (DBUS_BUS_SESSION, &error);
208
209   if (conn == NULL)
210     {
211       fprintf (stderr,
212           "%s: error: unable to connect to D-Bus: %s\n", PROGNAME,
213           error.message);
214       exit (EX_OSERR);
215     }
216
217   msg = dbus_message_new_method_call (DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
218       DBUS_INTERFACE_DBUS, "UpdateActivationEnvironment");
219
220   if (msg == NULL)
221     oom ();
222
223   dbus_message_iter_init_append (msg, &msg_iter);
224
225   if (!dbus_message_iter_open_container (&msg_iter, DBUS_TYPE_ARRAY,
226       "{ss}", &array_iter))
227     oom ();
228
229 #ifdef __linux__
230   if (systemd)
231     {
232       if (!systemd_user_running ())
233         {
234           /* This is only best-effort. */
235           say ("systemd --user not found, ignoring --systemd argument");
236           systemd = FALSE;
237         }
238     }
239
240   if (systemd)
241     {
242       sd_msg = dbus_message_new_method_call ("org.freedesktop.systemd1",
243           "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",
244           "SetEnvironment");
245
246       if (sd_msg == NULL)
247         oom ();
248
249       dbus_message_iter_init_append (sd_msg, &sd_msg_iter);
250
251       if (!dbus_message_iter_open_container (&sd_msg_iter, DBUS_TYPE_ARRAY,
252           "s", &sd_array_iter))
253         oom ();
254     }
255 #endif
256
257   for (i = all ? 0 : first_non_option;
258       all ? environ[i] != NULL : i < argc;
259       i++)
260     {
261       const char *var;
262       char *copy;
263       char *eq;
264       const char *val;
265       DBusMessageIter pair_iter;
266
267       if (all)
268         var = environ[i];
269       else
270         var = argv[i];
271
272       copy = strdup (var);
273
274       if (copy == NULL)
275         oom ();
276
277       if (!dbus_validate_utf8 (var, NULL))
278         {
279           /* var is either of the form VAR or VAR=VAL */
280           fprintf (stderr,
281               "%s: warning: environment variable not UTF-8: %s\n",
282               PROGNAME, var);
283           goto next;
284         }
285
286       eq = strchr (copy, '=');
287
288       if (eq == NULL)
289         {
290           if (all)
291             {
292               /* items in the environment block should be of the form
293                * VAR=VAL */
294               fprintf (stderr,
295                   "%s: warning: environment variable without '=': %s\n",
296                   PROGNAME, var);
297               goto next;
298             }
299           else
300             {
301               /* items on the command-line may be of the form VAR
302                * in which case we infer the value from the environment */
303               val = getenv (var);
304
305               if (val == NULL)
306                 {
307                   /* nothing to be done here */
308                   goto next;
309                 }
310
311               if (!dbus_validate_utf8 (val, NULL))
312                 {
313                   fprintf (stderr,
314                       "%s: warning: environment variable not UTF-8: %s=%s\n",
315                       PROGNAME, var, val);
316                   goto next;
317                 }
318             }
319         }
320       else
321         {
322           /* split VAR=VAL into VAR and VAL */
323           *eq = '\0';
324           val = eq + 1;
325         }
326
327 #ifdef __linux__
328       if (systemd)
329         {
330           char *combined;
331
332           /* recombine if necessary */
333           if (asprintf (&combined, "%s=%s", copy, val) < 0)
334             oom ();
335
336           if (!dbus_message_iter_append_basic (&sd_array_iter,
337                 DBUS_TYPE_STRING, &combined))
338             oom ();
339
340           free (combined);
341         }
342 #endif
343
344       if (!dbus_message_iter_open_container (&array_iter,
345               DBUS_TYPE_DICT_ENTRY, NULL, &pair_iter))
346         oom ();
347
348       say ("setting %s=%s", copy, val);
349
350       if (!dbus_message_iter_append_basic (&pair_iter, DBUS_TYPE_STRING,
351               &copy))
352         oom ();
353
354       if (!dbus_message_iter_append_basic (&pair_iter, DBUS_TYPE_STRING,
355               &val))
356         oom ();
357
358       if (!dbus_message_iter_close_container (&array_iter, &pair_iter))
359         oom ();
360
361 next:
362       free (copy);
363     }
364
365   if (!dbus_message_iter_close_container (&msg_iter, &array_iter))
366     oom ();
367
368 #ifdef __linux__
369   if (systemd &&
370       !dbus_message_iter_close_container (&sd_msg_iter, &sd_array_iter))
371     oom ();
372 #endif
373
374   reply = dbus_connection_send_with_reply_and_block (conn, msg, -1, &error);
375
376   if (reply == NULL)
377     {
378       fprintf (stderr,
379           "%s: error sending to dbus-daemon: %s: %s\n",
380           PROGNAME, error.name, error.message);
381       exit (EX_UNAVAILABLE);
382     }
383
384   if (dbus_set_error_from_message (&error, msg) ||
385       !dbus_message_get_args (msg, &error, DBUS_TYPE_INVALID))
386     {
387       fprintf (stderr,
388           "%s: error from dbus-daemon: %s: %s\n",
389           PROGNAME, error.name, error.message);
390       exit (EX_UNAVAILABLE);
391     }
392
393   dbus_message_unref (reply);
394
395 #ifdef __linux__
396   if (systemd)
397     {
398       reply = dbus_connection_send_with_reply_and_block (conn, sd_msg, -1,
399           &error);
400
401       /* non-fatal, the main purpose of this thing is to communicate
402        * with dbus-daemon */
403       if (reply == NULL)
404         {
405           fprintf (stderr,
406               "%s: warning: error sending to systemd: %s: %s\n",
407               PROGNAME, error.name, error.message);
408         }
409       else if (dbus_set_error_from_message (&error, msg) ||
410           !dbus_message_get_args (msg, &error, DBUS_TYPE_INVALID))
411         {
412           fprintf (stderr,
413               "%s: warning: error from systemd: %s: %s\n",
414               PROGNAME, error.name, error.message);
415         }
416
417       if (reply != NULL)
418         dbus_message_unref (reply);
419
420       dbus_message_unref (sd_msg);
421       dbus_error_free (&error);
422     }
423 #endif
424
425   dbus_message_unref (msg);
426   dbus_connection_unref (conn);
427   return 0;
428 }