Home | History | Annotate | Download | only in tools
      1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
      2 /* dbus-launch.h  dbus-launch utility
      3  *
      4  * Copyright (C) 2006 Thiago Macieira <thiago (at) kde.org>
      5  *
      6  * Licensed under the Academic Free License version 2.1
      7  *
      8  * This program is free software; you can redistribute it and/or modify
      9  * it under the terms of the GNU General Public License as published by
     10  * the Free Software Foundation; either version 2 of the License, or
     11  * (at your option) any later version.
     12  *
     13  * This program is distributed in the hope that it will be useful,
     14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  * GNU General Public License for more details.
     17  *
     18  * You should have received a copy of the GNU General Public License
     19  * along with this program; if not, write to the Free Software
     20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     21  *
     22  */
     23 
     24 #include <config.h>
     25 #include "dbus-launch.h"
     26 
     27 #ifdef DBUS_BUILD_X11
     28 #include <stdlib.h>
     29 #include <sys/types.h>
     30 #include <sys/stat.h>
     31 #include <unistd.h>
     32 #include <fcntl.h>
     33 #include <errno.h>
     34 #include <stdio.h>
     35 #include <string.h>
     36 #include <pwd.h>
     37 #include <X11/Xlib.h>
     38 #include <X11/Xatom.h>
     39 
     40 Display *xdisplay = NULL;
     41 static Atom selection_atom;
     42 static Atom address_atom;
     43 static Atom pid_atom;
     44 
     45 static int
     46 x_io_error_handler (Display *xdisplay)
     47 {
     48   verbose ("X IO error\n");
     49   kill_bus_and_exit (0);
     50   return 0;
     51 }
     52 
     53 static void
     54 remove_prefix (char *s,
     55                char *prefix)
     56 {
     57   int plen;
     58 
     59   plen = strlen (prefix);
     60 
     61   if (strncmp (s, prefix, plen) == 0)
     62     {
     63       memmove (s, s + plen, strlen (s) - plen + 1);
     64     }
     65 }
     66 
     67 static const char*
     68 get_homedir (void)
     69 {
     70   const char *home;
     71 
     72   home = getenv ("HOME");
     73   if (home == NULL)
     74     {
     75       /* try from the user database */
     76       struct passwd *user = getpwuid (getuid());
     77       if (user != NULL)
     78         home = user->pw_dir;
     79     }
     80 
     81   if (home == NULL)
     82     {
     83       fprintf (stderr, "Can't get user home directory\n");
     84       exit (1);
     85     }
     86 
     87   return home;
     88 }
     89 
     90 #define DBUS_DIR ".dbus"
     91 #define DBUS_SESSION_BUS_DIR "session-bus"
     92 
     93 static char *
     94 get_session_file (void)
     95 {
     96   static const char prefix[] = "/" DBUS_DIR "/" DBUS_SESSION_BUS_DIR "/";
     97   const char *machine;
     98   const char *home;
     99   char *display;
    100   char *result;
    101   char *p;
    102 
    103   machine = get_machine_uuid ();
    104   if (machine == NULL)
    105     return NULL;
    106 
    107   display = xstrdup (getenv ("DISPLAY"));
    108   if (display == NULL)
    109     {
    110       verbose ("X11 integration disabled because X11 is not running\n");
    111       return NULL;
    112     }
    113 
    114   /* remove the screen part of the display name */
    115   p = strrchr (display, ':');
    116   if (p != NULL)
    117     {
    118       for ( ; *p; ++p)
    119         {
    120           if (*p == '.')
    121             {
    122               *p = '\0';
    123               break;
    124             }
    125         }
    126     }
    127 
    128   /* Note that we leave the hostname in the display most of the
    129    * time. The idea is that we want to be per-(machine,display,user)
    130    * triplet to be extra-sure we get a bus we can connect to. Ideally
    131    * we'd recognize when the hostname matches the machine we're on in
    132    * all cases; we do try to drop localhost and localhost.localdomain
    133    * as a special common case so that alternate spellings of DISPLAY
    134    * don't result in extra bus instances.
    135    *
    136    * We also kill the ":" if there's nothing in front of it. This
    137    * avoids an ugly double underscore in the filename.
    138    */
    139   remove_prefix (display, "localhost.localdomain:");
    140   remove_prefix (display, "localhost:");
    141   remove_prefix (display, ":");
    142 
    143   /* replace the : in the display with _ if the : is still there.
    144    * use _ instead of - since it can't be in hostnames.
    145    */
    146   for (p = display; *p; ++p)
    147     {
    148       if (*p == ':')
    149         *p = '_';
    150     }
    151 
    152   home = get_homedir ();
    153 
    154   result = malloc (strlen (home) + strlen (prefix) + strlen (machine) +
    155                    strlen (display) + 2);
    156   if (result == NULL)
    157     {
    158       /* out of memory */
    159       free (display);
    160       return NULL;
    161     }
    162 
    163   strcpy (result, home);
    164   strcat (result, prefix);
    165   strcat (result, machine);
    166   strcat (result, "-");
    167   strcat (result, display);
    168   free (display);
    169 
    170   verbose ("session file: %s\n", result);
    171   return result;
    172 }
    173 
    174 static void
    175 ensure_session_directory (void)
    176 {
    177   const char *home;
    178   char *dir;
    179 
    180   home = get_homedir ();
    181 
    182   /* be sure we have space for / and nul */
    183   dir = malloc (strlen (home) + strlen (DBUS_DIR) + strlen (DBUS_SESSION_BUS_DIR) + 3);
    184   if (dir == NULL)
    185     {
    186       fprintf (stderr, "no memory\n");
    187       exit (1);
    188     }
    189 
    190   strcpy (dir, home);
    191   strcat (dir, "/");
    192   strcat (dir, DBUS_DIR);
    193 
    194   if (mkdir (dir, 0700) < 0)
    195     {
    196       /* only print a warning here, writing the session file itself will fail later */
    197       if (errno != EEXIST)
    198         fprintf (stderr, "Unable to create %s\n", dir);
    199     }
    200 
    201   strcat (dir, "/");
    202   strcat (dir, DBUS_SESSION_BUS_DIR);
    203 
    204   if (mkdir (dir, 0700) < 0)
    205     {
    206       /* only print a warning here, writing the session file itself will fail later */
    207       if (errno != EEXIST)
    208         fprintf (stderr, "Unable to create %s\n", dir);
    209     }
    210 
    211   free (dir);
    212 }
    213 
    214 static Display *
    215 open_x11 (void)
    216 {
    217   if (xdisplay != NULL)
    218     return xdisplay;
    219 
    220   xdisplay = XOpenDisplay (NULL);
    221   if (xdisplay != NULL)
    222     {
    223       verbose ("Connected to X11 display '%s'\n", DisplayString (xdisplay));
    224       XSetIOErrorHandler (x_io_error_handler);
    225     }
    226   return xdisplay;
    227 }
    228 
    229 static int
    230 init_x_atoms (Display *display)
    231 {
    232   static const char selection_prefix[] = "_DBUS_SESSION_BUS_SELECTION_";
    233   static const char address_prefix[] = "_DBUS_SESSION_BUS_ADDRESS";
    234   static const char pid_prefix[] = "_DBUS_SESSION_BUS_PID";
    235   static int init = FALSE;
    236   char *atom_name;
    237   const char *machine;
    238   char *user_name;
    239   struct passwd *user;
    240 
    241   if (init)
    242     return TRUE;
    243 
    244   machine = get_machine_uuid ();
    245   if (machine == NULL)
    246     return FALSE;
    247 
    248   user = getpwuid (getuid ());
    249   if (user == NULL)
    250     {
    251       verbose ("Could not determine the user informations; aborting X11 integration.\n");
    252       return FALSE;
    253     }
    254   user_name = xstrdup(user->pw_name);
    255 
    256   atom_name = malloc (strlen (machine) + strlen (user_name) + 2 +
    257                       MAX (strlen (selection_prefix),
    258                            MAX (strlen (address_prefix),
    259                                 strlen (pid_prefix))));
    260   if (atom_name == NULL)
    261     {
    262       verbose ("Could not create X11 atoms; aborting X11 integration.\n");
    263       free (user_name);
    264       return FALSE;
    265     }
    266 
    267   /* create the selection atom */
    268   strcpy (atom_name, selection_prefix);
    269   strcat (atom_name, user_name);
    270   strcat (atom_name, "_");
    271   strcat (atom_name, machine);
    272   selection_atom = XInternAtom (display, atom_name, FALSE);
    273 
    274   /* create the address property atom */
    275   strcpy (atom_name, address_prefix);
    276   address_atom = XInternAtom (display, atom_name, FALSE);
    277 
    278   /* create the PID property atom */
    279   strcpy (atom_name, pid_prefix);
    280   pid_atom = XInternAtom (display, atom_name, FALSE);
    281 
    282   free (atom_name);
    283   free (user_name);
    284   init = TRUE;
    285   return TRUE;
    286 }
    287 
    288 /*
    289  * Gets the daemon address from the X11 display.
    290  * Returns FALSE if there was an error. Returning
    291  * TRUE does not mean the address exists.
    292  */
    293 int
    294 x11_get_address (char **paddress, pid_t *pid, long *wid)
    295 {
    296   Atom type;
    297   Window owner;
    298   int format;
    299   unsigned long items;
    300   unsigned long after;
    301   char *data;
    302 
    303   *paddress = NULL;
    304 
    305   /* locate the selection owner */
    306   owner = XGetSelectionOwner (xdisplay, selection_atom);
    307   if (owner == None)
    308     return TRUE;                /* no owner */
    309   if (wid != NULL)
    310     *wid = (long) owner;
    311 
    312   /* get the bus address */
    313   XGetWindowProperty (xdisplay, owner, address_atom, 0, 1024, False,
    314                       XA_STRING, &type, &format, &items, &after,
    315                       (unsigned char **) &data);
    316   if (type == None || after != 0 || data == NULL || format != 8)
    317     return FALSE;               /* error */
    318 
    319   *paddress = xstrdup (data);
    320   XFree (data);
    321 
    322   /* get the PID */
    323   if (pid != NULL)
    324     {
    325       *pid = 0;
    326       XGetWindowProperty (xdisplay, owner, pid_atom, 0, sizeof pid, False,
    327                           XA_CARDINAL, &type, &format, &items, &after,
    328                           (unsigned char **) &data);
    329       if (type != None && after == 0 && data != NULL && format == 32)
    330         *pid = (pid_t) *(long*) data;
    331       XFree (data);
    332     }
    333 
    334   return TRUE;                  /* success */
    335 }
    336 
    337 /*
    338  * Saves the address in the X11 display. Returns 0 on success.
    339  * If an error occurs, returns -1. If the selection already exists,
    340  * returns 1. (i.e. another daemon is already running)
    341  */
    342 static Window
    343 set_address_in_x11(char *address, pid_t pid)
    344 {
    345   char *current_address;
    346   Window wid = None;
    347   unsigned long pid32; /* Xlib property functions want _long_ not 32-bit for format "32" */
    348 
    349   /* lock the X11 display to make sure we're doing this atomically */
    350   XGrabServer (xdisplay);
    351 
    352   if (!x11_get_address (&current_address, NULL, NULL))
    353     {
    354       /* error! */
    355       goto out;
    356     }
    357 
    358   if (current_address != NULL)
    359     {
    360       /* someone saved the address in the meantime */
    361       free (current_address);
    362       goto out;
    363     }
    364 
    365   /* Create our window */
    366   wid = XCreateWindow (xdisplay, RootWindow (xdisplay, 0), -20, -20, 10, 10,
    367                        0, CopyFromParent, InputOnly, CopyFromParent,
    368                        0, NULL);
    369   verbose ("Created window %d\n", wid);
    370 
    371   /* Save the property in the window */
    372   XChangeProperty (xdisplay, wid, address_atom, XA_STRING, 8, PropModeReplace,
    373                    (unsigned char *)address, strlen (address));
    374   pid32 = pid;
    375   XChangeProperty (xdisplay, wid, pid_atom, XA_CARDINAL, 32, PropModeReplace,
    376                    (unsigned char *)&pid32, 1);
    377 
    378   /* Now grab the selection */
    379   XSetSelectionOwner (xdisplay, selection_atom, wid, CurrentTime);
    380 
    381  out:
    382   /* Ungrab the server to let other people use it too */
    383   XUngrabServer (xdisplay);
    384 
    385   /* And make sure that the ungrab gets sent to X11 */
    386   XFlush (xdisplay);
    387 
    388   return wid;
    389 }
    390 
    391 /*
    392  * Saves the session address in session file. Returns TRUE on
    393  * success, FALSE if an error occurs.
    394  */
    395 static int
    396 set_address_in_file (char *address, pid_t pid, Window wid)
    397 {
    398   char *session_file;
    399   FILE *f;
    400 
    401   ensure_session_directory ();
    402   session_file = get_session_file();
    403   if (session_file == NULL)
    404     return FALSE;
    405 
    406   f = fopen (session_file, "w");
    407   if (f == NULL)
    408     return FALSE;               /* some kind of error */
    409   fprintf (f,
    410            "# This file allows processes on the machine with id %s using \n"
    411            "# display %s to find the D-Bus session bus with the below address.\n"
    412            "# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will\n"
    413            "# be used rather than this file.\n"
    414            "# See \"man dbus-launch\" for more details.\n"
    415            "DBUS_SESSION_BUS_ADDRESS=%s\n"
    416            "DBUS_SESSION_BUS_PID=%ld\n"
    417            "DBUS_SESSION_BUS_WINDOWID=%ld\n",
    418            get_machine_uuid (),
    419            getenv ("DISPLAY"),
    420            address, (long)pid, (long)wid);
    421 
    422   fclose (f);
    423   free (session_file);
    424 
    425   return TRUE;
    426 }
    427 
    428 int
    429 x11_save_address (char *address, pid_t pid, long *wid)
    430 {
    431   Window id = set_address_in_x11 (address, pid);
    432   if (id != None)
    433     {
    434       if (!set_address_in_file (address, pid, id))
    435         return FALSE;
    436 
    437       if (wid != NULL)
    438         *wid = (long) id;
    439       return TRUE;
    440     }
    441   return FALSE;
    442 }
    443 
    444 int
    445 x11_init (void)
    446 {
    447   return open_x11 () != NULL && init_x_atoms (xdisplay);
    448 }
    449 
    450 void
    451 x11_handle_event (void)
    452 {
    453   if (xdisplay != NULL)
    454     {
    455       while (XPending (xdisplay))
    456         {
    457           XEvent ignored;
    458           XNextEvent (xdisplay, &ignored);
    459         }
    460     }
    461 }
    462 
    463 #else
    464 void dummy_dbus_launch_x11 (void);
    465 
    466 void dummy_dbus_launch_x11 (void) { }
    467 #endif
    468