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@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
x_io_error_handler(Display * xdisplay)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
remove_prefix(char * s,char * prefix)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*
get_homedir(void)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 *
get_session_file(void)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
ensure_session_directory(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 *
open_x11(void)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
init_x_atoms(Display * display)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 user information; 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
x11_get_address(char ** paddress,pid_t * pid,long * wid)294 x11_get_address (char **paddress, pid_t *pid, long *wid)
295 {
296 int result;
297 Atom type;
298 Window owner;
299 int format;
300 unsigned long items;
301 unsigned long after;
302 char *data;
303
304 *paddress = NULL;
305
306 /* locate the selection owner */
307 owner = XGetSelectionOwner (xdisplay, selection_atom);
308 if (owner == None)
309 return TRUE; /* no owner */
310 if (wid != NULL)
311 *wid = (long) owner;
312
313 /* get the bus address */
314 result = XGetWindowProperty (xdisplay, owner, address_atom, 0, 1024, False,
315 XA_STRING, &type, &format, &items, &after,
316 (unsigned char **) &data);
317 if (result != Success || type == None || after != 0 || data == NULL || format != 8)
318 return FALSE; /* error */
319
320 *paddress = xstrdup (data);
321 XFree (data);
322
323 /* get the PID */
324 if (pid != NULL)
325 {
326 *pid = 0;
327 result = XGetWindowProperty (xdisplay, owner, pid_atom, 0, sizeof pid, False,
328 XA_CARDINAL, &type, &format, &items, &after,
329 (unsigned char **) &data);
330 if (result == Success && type != None && after == 0 && data != NULL && format == 32)
331 *pid = (pid_t) *(long*) data;
332 XFree (data);
333 }
334
335 return TRUE; /* success */
336 }
337
338 /*
339 * Saves the address in the X11 display. Returns 0 on success.
340 * If an error occurs, returns -1. If the selection already exists,
341 * returns 1. (i.e. another daemon is already running)
342 */
343 static Window
set_address_in_x11(char * address,pid_t pid)344 set_address_in_x11(char *address, pid_t pid)
345 {
346 char *current_address;
347 Window wid = None;
348 unsigned long pid32; /* Xlib property functions want _long_ not 32-bit for format "32" */
349
350 /* lock the X11 display to make sure we're doing this atomically */
351 XGrabServer (xdisplay);
352
353 if (!x11_get_address (¤t_address, NULL, NULL))
354 {
355 /* error! */
356 goto out;
357 }
358
359 if (current_address != NULL)
360 {
361 /* someone saved the address in the meantime */
362 free (current_address);
363 goto out;
364 }
365
366 /* Create our window */
367 wid = XCreateWindow (xdisplay, RootWindow (xdisplay, 0), -20, -20, 10, 10,
368 0, CopyFromParent, InputOnly, CopyFromParent,
369 0, NULL);
370 verbose ("Created window %d\n", wid);
371
372 /* Save the property in the window */
373 XChangeProperty (xdisplay, wid, address_atom, XA_STRING, 8, PropModeReplace,
374 (unsigned char *)address, strlen (address));
375 pid32 = pid;
376 XChangeProperty (xdisplay, wid, pid_atom, XA_CARDINAL, 32, PropModeReplace,
377 (unsigned char *)&pid32, 1);
378
379 /* Now grab the selection */
380 XSetSelectionOwner (xdisplay, selection_atom, wid, CurrentTime);
381
382 out:
383 /* Ungrab the server to let other people use it too */
384 XUngrabServer (xdisplay);
385
386 /* And make sure that the ungrab gets sent to X11 */
387 XFlush (xdisplay);
388
389 return wid;
390 }
391
392 /*
393 * Saves the session address in session file. Returns TRUE on
394 * success, FALSE if an error occurs.
395 */
396 static int
set_address_in_file(char * address,pid_t pid,Window wid)397 set_address_in_file (char *address, pid_t pid, Window wid)
398 {
399 char *session_file;
400 FILE *f;
401
402 ensure_session_directory ();
403 session_file = get_session_file();
404 if (session_file == NULL)
405 return FALSE;
406
407 f = fopen (session_file, "w");
408 free (session_file);
409 if (f == NULL)
410 return FALSE; /* some kind of error */
411 fprintf (f,
412 "# This file allows processes on the machine with id %s using \n"
413 "# display %s to find the D-Bus session bus with the below address.\n"
414 "# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will\n"
415 "# be used rather than this file.\n"
416 "# See \"man dbus-launch\" for more details.\n"
417 "DBUS_SESSION_BUS_ADDRESS=%s\n"
418 "DBUS_SESSION_BUS_PID=%ld\n"
419 "DBUS_SESSION_BUS_WINDOWID=%ld\n",
420 get_machine_uuid (),
421 getenv ("DISPLAY"),
422 address, (long)pid, (long)wid);
423
424 fclose (f);
425
426 return TRUE;
427 }
428
429 int
x11_save_address(char * address,pid_t pid,long * wid)430 x11_save_address (char *address, pid_t pid, long *wid)
431 {
432 Window id = set_address_in_x11 (address, pid);
433 if (id != None)
434 {
435 if (!set_address_in_file (address, pid, id))
436 return FALSE;
437
438 if (wid != NULL)
439 *wid = (long) id;
440 return TRUE;
441 }
442 return FALSE;
443 }
444
445 int
x11_init(void)446 x11_init (void)
447 {
448 return open_x11 () != NULL && init_x_atoms (xdisplay);
449 }
450
451 void
x11_handle_event(void)452 x11_handle_event (void)
453 {
454 if (xdisplay != NULL)
455 {
456 while (XPending (xdisplay))
457 {
458 XEvent ignored;
459 XNextEvent (xdisplay, &ignored);
460 }
461 }
462 }
463
464 #else
465 void dummy_dbus_launch_x11 (void);
466
dummy_dbus_launch_x11(void)467 void dummy_dbus_launch_x11 (void) { }
468 #endif
469