softwarecontainer  0.18.0-739e8d7 2017-05-04
proxy.c
1 /*
2  * Copyright (C) 2013-2016, Pelagicore AB <joakim.gross@pelagicore.com>
3  * Copyright (C) 2011, Stéphane Graber <stgraber@stgraber.org>
4  * Copyright (C) 2010, Alban Crequy <alban.crequy@collabora.co.uk>
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 Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * For further information see LICENSE
22  */
23 
24 
25 #include "proxy.h"
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <string.h>
31 
32 #include <sys/wait.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 
36 #include <fcntl.h>
37 #include <signal.h>
38 #include <errno.h>
39 
40 #include <jansson.h>
41 #include <dbus/dbus-glib-lowlevel.h>
42 
43 
45 DBusConnection *dbus_conn = NULL;
46 
48 DBusGConnection *master_conn = NULL;
49 
50 DBusServer *dbus_srv = NULL;
51 
53 json_t *json_filters = NULL;
54 
56 gchar *address = NULL;
57 
59 gboolean verbose = FALSE;
60 
62 DBusBusType bus = DBUS_BUS_SESSION;
63 
65 GList *eavesdropping_conns = NULL;
66 
67 
68 void handle_sigchld(int sig) {
69  g_message("Received signal SIGCHLD");
70  while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {
71  g_message("Waiting for child");
72  usleep(30);
73  }
74 
75  g_message("Finished waiting for child");
76 }
77 
78 
91 DBusHandlerResult filter_cb (DBusConnection *conn,
92  DBusMessage *msg,
93  void *user_data)
94 {
95  /* Data arriving from client */
96  guint32 serial;
97  DBusHandlerResult retval = DBUS_HANDLER_RESULT_HANDLED;
98 
99  /* Handle Hello */
100  if (dbus_message_get_type (msg) ==
101  DBUS_MESSAGE_TYPE_METHOD_CALL &&
102  strcmp (dbus_message_get_path(msg),
103  "/org/freedesktop/DBus") == 0 &&
104  strcmp (dbus_message_get_interface(msg),
105  "org.freedesktop.DBus") == 0 &&
106  strcmp (dbus_message_get_destination(msg),
107  "org.freedesktop.DBus") == 0 &&
108  strcmp (dbus_message_get_member (msg), "Hello") == 0) {
109 
110  DBusMessage *welcome;
111  const gchar *dbus_local_name;
112 
113  dbus_local_name = dbus_bus_get_unique_name (
114  dbus_g_connection_get_connection(master_conn));
115 
116  if (verbose) {
117  g_message("Hello received\n");
118  }
119 
120  welcome = dbus_message_new_method_return (msg);
121  if (!dbus_message_append_args (welcome,
122  DBUS_TYPE_STRING,
123  &dbus_local_name,
124  DBUS_TYPE_INVALID)) {
125  g_error("Cannot reply to Hello message\n");
126  exit(1);
127  }
128  dbus_connection_send(conn, welcome, &serial);
129 
130  dbus_message_unref (welcome);
131  goto out;
132  }
133 
134  /* Handle Disconnected */
135  if (dbus_message_get_type(msg) ==
136  DBUS_MESSAGE_TYPE_SIGNAL &&
137  strcmp (dbus_message_get_interface (msg),
138  "org.freedesktop.DBus.Local") == 0 &&
139  strcmp (dbus_message_get_member (msg),
140  "Disconnected") == 0) {
141 
142  /* connection was disconnected */
143  if (verbose) {
144  g_message("connection was disconnected\n");
145  }
146 
147  dbus_connection_close (dbus_conn);
148  dbus_connection_unref (dbus_conn);
149  dbus_conn = NULL;
150  exit(0);
151  goto out;
152  }
153 
154  /* Forward */
155  if (dbus_message_get_interface (msg) == NULL ||
156  strcmp (dbus_message_get_interface(msg),
157  "org.freedesktop.DBus") == 0)
158  {
159  dbus_connection_send(
160  dbus_g_connection_get_connection(master_conn),
161  msg,
162  &serial);
163  } else if (is_allowed("outgoing",
164  dbus_message_get_interface (msg),
165  dbus_message_get_path (msg),
166  dbus_message_get_member (msg)))
167  {
168  g_message("Accepted call to '%s' from client to '%s' on '%s'.\n",
169  dbus_message_get_member (msg),
170  dbus_message_get_interface(msg),
171  dbus_message_get_path (msg));
172 
173  dbus_connection_send (
174  dbus_g_connection_get_connection (master_conn),
175  msg,
176  &serial);
177  } else {
178  g_message("Rejected call to '%s' from "
179  "client to '%s' on '%s'.\n",
180  dbus_message_get_member (msg),
181  dbus_message_get_interface (msg),
182  dbus_message_get_path (msg));
183  retval = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
184  }
185 
186 out:
187  return retval;
188 }
189 
197 gboolean compare_entry(const char *comparison, const gchar *string) {
198  if (verbose) {
199  g_message("will try matching %s with %s\n", string, comparison);
200  }
201 
202  if ((strcmp (string, "") != 0) && g_pattern_match_simple (string, comparison)) {
203  if (verbose) {
204  g_message("was a match\n");
205  }
206  return TRUE;
207  } else {
208  if (verbose) {
209  g_message("no match\n");
210  }
211  return FALSE;
212  }
213 }
214 
226 gboolean match_rule(const json_t *rule,
227  const char *entry,
228  const char *comparison)
229 {
230  json_t *json_entry;
231  gchar *string;
232 
233  json_entry = json_object_get(rule, entry);
234 
235 
236  if (!json_is_string (json_entry)) {
237  return FALSE;
238  }
239 
240  string = (gchar*) json_string_value (json_entry);
241  return compare_entry(comparison, string);
242 }
243 
254 gboolean match_method(const json_t *rule, const char *comparison)
255 {
256  json_t *json_entry;
257  gchar *string;
258 
259  json_entry = json_object_get(rule, "method");
260  if (json_is_array(json_entry)) {
261  size_t ix;
262  json_t *val;
263 
264  json_array_foreach(json_entry, ix, val) {
265  if (!json_is_string(val)) {
266  if (verbose) {
267  g_message("Entry in method array is not a string.");
268  }
269  return FALSE;
270  }
271 
272  string = (gchar*) json_string_value (val);
273  if ((strcmp (string, "") != 0) && g_pattern_match_simple (string, comparison)) {
274  if (verbose) {
275  g_message("was a match\n");
276  }
277  return TRUE;
278  }
279  }
280  } else if (json_is_string (json_entry)) {
281  string = (gchar*) json_string_value (json_entry);
282  return compare_entry(comparison, string);
283  }
284 
285  return FALSE;
286 }
287 
288 
301 gboolean is_allowed (const char *direction,
302  const char *interface,
303  const char *path,
304  const char *member)
305 {
306  size_t i;
307  json_t *rule;
308  gboolean direction_ok, interface_ok, object_path_ok, method_ok;
309 
310  /* Check all rules until a match is found. When a match is found we
311  don't check any following rules. This means that a more permissive
312  rule will trump less permissive rules. */
313  for (i = 0; i < json_array_size (json_filters); i++) {
314  direction_ok, interface_ok, object_path_ok, method_ok = FALSE;
315 
316  /* Get the JSON array containing the JSON objects */
317  rule = json_array_get (json_filters, i);
318  if (rule == NULL || !json_is_object (rule)) {
319  json_decref (rule);
320  break;
321  }
322 
323  direction_ok = match_rule (rule, "direction", direction);
324  interface_ok = match_rule (rule, "interface", interface);
325  object_path_ok = match_rule (rule, "object-path", path);
326  method_ok = match_method(rule, member);
327 
328  /* All entries matched for the rule */
329  if (direction_ok &&
330  interface_ok &&
331  object_path_ok &&
332  method_ok)
333  {
334  return TRUE;
335  }
336 
337  /*
338  * Since direction seems to be a common source of errors, the
339  * following printout is added as a helper to developer
340  */
341  if (!direction_ok &&
342  interface_ok &&
343  object_path_ok &&
344  method_ok)
345  {
346  g_message("Direction '%s' does not match but "
347  "everything else does\n", direction);
348  }
349  }
350 
351  return FALSE;
352 }
353 
366 DBusHandlerResult master_filter_cb (DBusConnection *conn,
367  DBusMessage *msg,
368  void *user_data)
369 {
370  /* Data arriving from server */
371 
372  guint32 serial;
373  DBusHandlerResult retval = DBUS_HANDLER_RESULT_HANDLED;
374 
375  if (!dbus_conn) {
376  exit(1);
377  }
378  /* Make sure that a new connection does not have a unique name
379  that was previously owned by an eavesdropping connection */
380  if (dbus_message_get_member(msg) != NULL &&
381  strcmp(dbus_message_get_member(msg), "NameAcquired") == 0)
382  {
383  const char *dest = dbus_message_get_destination(msg);
384  if (verbose)
385  {
386  g_message("NameAcquired received by %s\n", dest);
387  }
388 
389  if (dest != NULL &&
390  is_conn_known_eavesdropper(dest))
391  {
392  if (verbose)
393  {
394  g_message("New connection's unique name ('%s')"
395  " was previously known as an eavesdropper."
396  " Removed old entry...\n", dest);
397  }
398  remove_name_from_known_eavesdroppers(dest);
399  }
400  }
401 
402  /* Forward */
403  if (dbus_message_get_interface(msg) == NULL ||
404  strcmp(dbus_message_get_interface(msg),
405  "org.freedesktop.DBus") == 0)
406  {
407  if (is_incoming_eavesdropping(msg) &&
408  !is_conn_known_eavesdropper(dbus_message_get_sender(msg)))
409  {
410  eavesdropping_conns =
411  g_list_append (eavesdropping_conns,
412  (gpointer) dbus_message_get_sender(msg));
413  }
414 
415  dbus_connection_send(dbus_conn, msg, &serial);
416  } else if (is_conn_known_eavesdropper (dbus_bus_get_unique_name(conn)))
417  {
418  if (verbose) {
419  g_message("'%s' is an eavesdropping connection, let it go...\n",
420  dbus_bus_get_unique_name(conn));
421  }
422  } else if (is_allowed("incoming",
423  dbus_message_get_interface (msg),
424  dbus_message_get_path (msg),
425  dbus_message_get_member (msg)))
426  {
427  g_message("Accepted call to '%s' from server to '%s' on '%s'.\n",
428  dbus_message_get_member (msg),
429  dbus_message_get_interface (msg),
430  dbus_message_get_path (msg));
431  dbus_connection_send(dbus_conn, msg, &serial);
432  } else {
433  g_message("Rejected call to '%s' from server to '%s' on '%s'.\n",
434  dbus_message_get_member (msg),
435  dbus_message_get_interface (msg),
436  dbus_message_get_path (msg));
437  retval = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
438  }
439 
440  return retval;
441 }
442 
449 dbus_bool_t allow_all_connections (DBusConnection *conn,
450  unsigned long uid,
451  void *data)
452 {
453  return TRUE;
454 }
455 
466 gboolean is_incoming_eavesdropping (DBusMessage *msg)
467 {
468  gboolean is_eavesdropping = FALSE;
469 
470  /* Look for AddMatch and eavesdrop=true in message */
471  if (dbus_message_get_member(msg) != NULL &&
472  strcmp(dbus_message_get_member(msg), "AddMatch") == 0)
473  {
474  const char *msg_arguments;
475  dbus_message_get_args (msg,
476  NULL,
477  DBUS_TYPE_STRING,
478  &msg_arguments);
479 
480  if (strstr(msg_arguments, "eavesdrop=true") != NULL)
481  {
482  is_eavesdropping = TRUE;
483  if (verbose)
484  {
485  g_message("'%s' AddMatch-args: \"%s\"\n",
486  dbus_message_get_sender(msg),
487  msg_arguments);
488  }
489  }
490  }
491  return is_eavesdropping;
492 }
493 
503 gboolean is_conn_known_eavesdropper (const char *unique_name)
504 {
505  gboolean found = FALSE;
506  GList *iter = eavesdropping_conns;
507 
508  while (iter != NULL)
509  {
510  if (strcmp(unique_name, (char*) iter->data) == 0)
511  {
512  found = TRUE;
513  break;
514  }
515  iter = iter->next;
516  }
517 
518  return found;
519 }
520 
533 gboolean remove_name_from_known_eavesdroppers (const char *unique_name)
534 {
535  gboolean removed = FALSE;
536  GList *iter = eavesdropping_conns;
537 
538  while (iter != NULL)
539  {
540  if (strcmp(unique_name, (char*) iter->data) == 0)
541  {
542  eavesdropping_conns =
543  g_list_remove (eavesdropping_conns, iter->data);
544  removed = TRUE;
545  }
546  iter = iter->next;
547  }
548 
549  return removed;
550 }
551 
562 void new_connection_cb (DBusServer *server, DBusConnection *conn, void *data) {
563  pid_t pid;
564  pid_t forked;
565  GError *error = NULL;
566 
567  forked = fork();
568  pid = getpid();
569 
570  if (forked != 0) {
571  if (verbose) {
572  g_message("in main process, pid: %d\n", pid);
573  }
574 
575  /* Reconfigure the master socket as forking will break it */
576  start_bus();
577  return;
578  } else {
579  if (verbose) {
580  g_message("in child process, pid: %d\n", pid);
581  }
582  }
583 
584  if (master_conn != NULL) {
585  g_message("master_conn already initialized\n");
586  exit (1);
587  }
588 
589  if (dbus_conn != NULL) {
590  g_message("dbus_conn already initialized\n");
591  exit (1);
592  }
593 
594  /* Init master connection */
595  master_conn = dbus_g_bus_get (bus, &error);
596  if (!master_conn) {
597  g_error("Failed to open connection to bus: %s\n", error->message);
598  g_clear_error (&error);
599  exit(1);
600  }
601 
602  dbus_connection_add_filter (
603  dbus_g_connection_get_connection(master_conn),
604  master_filter_cb,
605  NULL,
606  NULL);
607 
608  if (verbose) {
609  g_message("New connection\n");
610  }
611 
612  dbus_connection_ref (conn);
613  dbus_connection_setup_with_g_main (conn, NULL);
614  dbus_connection_add_filter (conn, filter_cb, NULL, NULL);
615 
616  dbus_connection_set_unix_user_function (conn,
617  allow_all_connections,
618  NULL,
619  NULL);
620 
621  dbus_connection_set_allow_anonymous (conn, TRUE);
622  dbus_conn = conn;
623 }
624 
625 void start_bus() {
626  DBusError error;
627 
628  dbus_error_init (&error);
629 
630  if (dbus_srv != NULL) {
631  dbus_server_disconnect(dbus_srv);
632  dbus_server_unref(dbus_srv);
633  }
634  dbus_srv = dbus_server_listen (address, &error);
635  if (dbus_srv == NULL) {
636  g_error("Cannot listen on %s\n", address);
637  exit(1);
638  }
639 
640  dbus_server_set_new_connection_function (dbus_srv,
641  new_connection_cb,
642  NULL,
643  NULL);
644  dbus_server_setup_with_g_main (dbus_srv, NULL);
645 }
646 
647 
648 void parse_full_config(const char *config_string, const char *section) {
649  json_error_t error;
650  json_t *root;
651  json_t *config;
652 
653  char *full_section = calloc(30, sizeof(char));
654 
655  snprintf(full_section, 30, "dbus-gateway-config-%s", section);
656 
657  g_message("Parsing config");
658 
659  /* Get root JSON object */
660  root = json_loads(config_string, 0, &error);
661 
662  if (!root) {
663  g_error("error: on line %d: %s\n", error.line, error.text);
664  return;
665  }
666 
667  /* Get array */
668  config = json_object_get(root, full_section);
669 
670  g_message("%s\n", json_dumps(config, JSON_INDENT(4)));
671 
672  if (!json_is_array(config)) {
673  g_error("error: %s is not present in config, or not an array. "
674  "Fix your config\n", full_section);
675  json_decref (config);
676  }
677 
678  if (NULL == json_filters) {
679  json_filters = config;
680  } else {
681  if (0 != json_array_extend(json_filters, config)) {
682  g_error("Error extending config array\n");
683  }
684  }
685 }
686 
687 
688 void print_usage() {
689  g_print("dbus-proxy, version %s\n", PACKAGE_VERSION);
690  g_print("Usage: dbus-proxy address session|system\n"
691  "waits for config on stdin\n");
692 }
693 
694 
695 #ifdef LOG_TO_FILE
696 FILE *log_file;
697 
698 gboolean log_file_is_open() {
699  return log_file ? TRUE : FALSE;
700 }
701 
702 gboolean open_log_file() {
703  char buf[30] = {0};
704  pid_t pid = getpid();
705  sprintf(buf, "/tmp/dbus-proxy-%d.log", pid);
706  log_file = fopen(buf, "a");
707 
708  if (NULL == log_file) {
709  return FALSE;
710  }
711 
712  return TRUE;
713 }
714 
715 gboolean close_log_file() {
716  if (NULL == log_file) {
717  return TRUE;
718  }
719 
720  int res = fclose(log_file);
721 
722  if (res != 0) {
723  return FALSE;
724  }
725 
726  log_file = NULL;
727 
728  return TRUE;
729 }
730 
731 void log_handler(const gchar *log_domain,
732  GLogLevelFlags log_level,
733  const gchar *message,
734  gpointer user_data)
735 {
736  if (log_file_is_open()) {
737  fprintf(log_file, "%s\n", message);
738  fflush(log_file);
739  }
740 }
741 #endif
742 
743 
744 void log_handler_silent(const gchar *log_domain,
745  GLogLevelFlags log_level,
746  const gchar *message,
747  gpointer user_data)
748 {
749  /* Do nothing, be silent */
750  return;
751 }
752 
753 
754 /*
755  * Read data and keep listening, or stop listening when appropriate.
756  *
757  * On the event of G_IO_IN we read the config. If zero bytes are
758  * read it probably means that the writing end has closed stdin
759  * and we stop listening for more events.
760  *
761  * On the event of G_IO_HUP, the other end has probably closed
762  * stdin and we stop listening for more events.
763  *
764  * If something was read, we pass it along to be parsed as config
765  * json.
766  *
767  * Other events are not handled and will be ignored.
768  *
769  * 'data' contains the section (either "session" or "system") to
770  * parse from the config.
771  */
772 static gboolean stdin_watch(GIOChannel *source,
773  GIOCondition condition,
774  gpointer *data)
775 {
776  g_message("Got event on stdin");
777 
778  if (condition & G_IO_HUP) {
779  /* Other end probably closed stdin */
780  g_message("Event was G_IO_HUP, will stop listening for events");
781 
782  /* We stop listening for events at this point */
783  return FALSE;
784  }
785 
786  if (condition & G_IO_IN) {
787  g_message("Event condition was G_IO_IN, will read config");
788 
789  GIOStatus ret;
790  gchar *msg;
791  gsize len;
792 
793  ret = g_io_channel_read_line(source, &msg, &len, NULL, NULL);
794  if (G_IO_STATUS_ERROR == ret) {
795  g_error("Error reading from channel");
796  }
797 
798  if (0 == len) {
799  /* In some cases, like when redirecting a file to stdin when
800  starting dbus-proxy, we might receive a G_IO_IN event with
801  zero bytes. We stop listenting for events at this point */
802  g_message("Read zero bytes, will stop listening for events");
803 
804  return FALSE;
805  }
806 
807  g_message("%s", msg);
808 
809  parse_full_config(msg, (const char *)data);
810 
811  return TRUE;
812  }
813 
814  g_message("Got unhandled event on stdin, will ignore and continue "
815  "listening for events");
816  return TRUE;
817 }
818 
819 
820 int main(int argc, char *argv[]) {
821  g_message("Starting dbus-proxy, pid: %d", getpid());
822 
823  GMainLoop *mainloop = NULL;
824  GError *error = NULL;
825 
826  /* Support --version */
827  if (argc == 2 && strcmp(argv[1], "--version") == 0) {
828  print_usage();
829  exit(0);
830  }
831 
832  /* Check for right number of args */
833  if (argc < 3) {
834  print_usage();
835  exit(1);
836  }
837 
838  /* Extract address */
839  address = g_strconcat("unix:path=", argv[1], NULL);
840  if (strcmp (argv[2], "system") == 0) {
841  bus = DBUS_BUS_SYSTEM;
842  } else if (strcmp(argv[2], "session") == 0) {
843  bus = DBUS_BUS_SESSION;
844  } else {
845  g_message("Must give bus type as second argument (either session or system).\n");
846  exit (1);
847  }
848 
849  /* Setup log handlers, if needed, for g_message, g_warning etc.
850  Default behavior is to silence the logging, unless one of the
851  two macros are set. */
852 #ifdef LOG_TO_FILE
853  if (!open_log_file()) {
854  g_error("Could not open log file\n");
855  exit(1);
856  }
857  g_log_set_handler(NULL /*use default log domain*/,
858  G_LOG_LEVEL_MASK,
859  log_handler,
860  NULL /*no need to pass data to handler*/);
861 #endif
862 
863 #ifndef LOG_TO_STDOUT
864  g_log_set_handler(NULL /*use default log domain*/,
865  G_LOG_LEVEL_MASK,
866  log_handler_silent,
867  NULL /*no need to pass data to handler*/);
868 #endif
869 
870  /* Set set signal handler */
871  struct sigaction sa;
872  sa.sa_handler = &handle_sigchld;
873  sigemptyset(&sa.sa_mask);
874  sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
875  if (sigaction(SIGCHLD, &sa, 0) == -1) {
876  perror(0);
877  exit(1);
878  }
879 
880  /* Remember what section of the config we should read later */
881  gpointer section = argv[2];
882 
883  /* Start listening */
884  start_bus();
885 
886  g_message("Setting up event listener on stdin");
887  GIOChannel *channel = g_io_channel_unix_new(STDIN_FILENO);
888  g_io_add_watch(channel,
889  G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
890  (GIOFunc)stdin_watch,
891  section);
892 
893  g_message("Entering mainloop\n");
894 
895  /* Start listening */
896  start_bus();
897  mainloop = g_main_loop_new(NULL /*use default context*/,
898  FALSE /*mainloop is not currently running*/);
899  g_main_loop_run(mainloop);
900 
901  g_message("Exiting dbus-proxy");
902 
903 #ifdef LOG_TO_FILE
904  if (!close_log_file()) {
905  g_message("Could not close log file\n");
906  }
907 #endif
908 
909  return 0;
910 }