honor flags again
[opensuse:hal-resmgr.git] / hal-resmgr.c
1 /***************************************************************************
2  *
3  * hal-resmgr.c : tell resmgr about devices managed by hald
4  *
5  * Copyright (C) 2005 SUSE LINUX Products GmbH.
6  * Author: Ludwig Nussel
7  *
8  * based on lshal:
9  * Copyright (C) 2003 David Zeuthen, <david@fubar.dk>
10  *
11  * Licensed under the Academic Free License version 2.0
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26  *
27  **************************************************************************/
28
29
30 #ifdef HAVE_CONFIG_H
31 #  include <config.h>
32 #endif
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <getopt.h>
39 #include <time.h>
40
41 #include <dbus/dbus.h>
42 #include <hal/libhal.h>
43
44 #include <limits.h>
45 #include <pwd.h>
46
47 #include <resmgr.h>
48
49 #include "mainloop.h"
50 #include "mainloop-dbus.h"
51 #include "debug.h"
52 #include "file.h"
53
54 static int diesilently;
55 /** Macro for terminating the program on an unrecoverable error */
56 #define DIE(expr) do { if(!diesilently) error expr; exit(1); } while(0)
57
58 static LibHalContext *hal_ctx;
59 static int retries = 0;
60 static int do_monitor = FALSE;
61 static int do_list = FALSE;
62
63 static int add_all_users = TRUE;
64
65 static char** login_users;
66 static uid_t* login_uids;
67 static unsigned num_login_users;
68 static char** logout_users;
69 static uid_t* logout_uids;
70 static unsigned num_logout_users;
71
72 static int num_devices;
73 static char **devices = NULL;
74
75 #define RESMGR_LOGINCLASSES_PATH "/var/run/resmgr/classes/"
76
77 static void
78 resmgr_get_users()
79 {
80         unsigned i;
81
82         if(!add_all_users)
83                 return;
84
85         login_users = rsm_list_users();
86
87         if(login_users && login_users[0])
88         {
89                 for(i = 0; login_users[i]; ++i);
90                 num_login_users = i;
91
92                 login_uids = malloc(sizeof(uid_t)*num_login_users);
93
94                 for(i = 0; i < num_login_users; ++i) 
95                 {
96                         struct passwd* pw;
97
98                         pw = getpwnam(login_users[i]);
99                         if(!pw)
100                         {
101                                 warning("cannot determine uid for %s", login_users[i]);
102                                 login_uids[i] = -1;
103                                 free(login_users[i]);
104                                 login_users[i] = NULL;
105                         }
106                         else
107                         {
108                                 login_uids[i] = pw->pw_uid;
109                         }
110                 }
111         }
112         else
113         {
114                 free(login_users);
115                 login_users = NULL;
116                 num_login_users = 0;
117         }
118 }
119
120 static int
121 is_user_privileged(const char* user, const char* priv)
122 {
123         char path[PATH_MAX];
124
125 // TODO: use cache and resmgr C api instead of fs
126         snprintf(path, sizeof(path), RESMGR_LOGINCLASSES_PATH "%s/%s", priv, user);
127
128         if(access(path, F_OK) == 0)
129                 return 1;
130
131         return 0;
132 }
133
134 static void
135 rsm_doit(char** classes, const char* device, const char** flaglist)
136 {
137         unsigned i, j;
138         res_file_t* dev;
139
140         dev = res_file_new(device, flaglist);
141
142         debug(1, "%s", device);
143
144         if(num_login_users)
145         {
146                 for(i = 0; i < num_login_users; ++i)
147                 {
148                         if(!login_users[i])
149                                 continue;
150
151                         for(j = 0; classes && classes[j]; ++j)
152                         {
153                                 if(is_user_privileged(login_users[i], classes[j])) {
154                                         res_file_grant(dev, login_uids[i]);
155                                 }
156                                 else {
157                                         res_file_revoke(dev, login_uids[i]);
158                                 }
159                         }
160                 }
161         }
162
163         if(num_logout_users)
164         {
165                 for(i = 0; i < num_logout_users; ++i)
166                 {
167                         if(!logout_users[i])
168                                 continue;
169
170                         for(j = 0; classes && classes[j]; ++j)
171                         {
172                                 res_file_revoke(dev, logout_uids[i]);
173                         }
174                 }
175         }
176
177         if(!num_login_users && !num_logout_users)
178                 res_file_destroy(dev);
179
180         res_file_free(dev);
181 }
182
183 #define IF_ERROR_PRINT(x) \
184         do { if(dbus_error_is_set(&error)) { \
185                 error("%s: %s", (x), error.name, error.message); \
186                 dbus_error_free(&error); \
187         } } while(0)
188
189 /** usb interface events fire before the actual device gets created by udev.
190  * Since e.g. cameras are matched at interface level we need to add a marker to
191  * the parent so that the usbraw interface can later copy it's properties from
192  * the interface node (#145125)
193  * */
194 static int
195 usb_interface_hack(LibHalContext* ctx, const char* udi)
196 {
197         char* bus;
198         char* parent;
199         DBusError error;
200
201         if(!libhal_device_property_exists(ctx, udi, "usb.interface.number", NULL))
202                 return FALSE;
203
204         bus = libhal_device_get_property_string(hal_ctx, udi, "info.bus", NULL);
205         if(!bus || strcmp(bus, "usb"))
206         {
207                 libhal_free_string(bus);
208                 return FALSE;
209         }
210         libhal_free_string(bus);
211
212         parent = libhal_device_get_property_string(hal_ctx, udi, "info.parent", NULL);
213         if(!parent)
214                 return FALSE;
215
216         dbus_error_init (&error);
217
218         if(!libhal_device_set_property_string(ctx, parent, "resmgr.hack_add_usbraw_as", udi, &error))
219         {
220                 IF_ERROR_PRINT("can't tag parent");
221                 dbus_error_init (&error);
222         }
223         libhal_free_string(parent);
224
225         if(!libhal_device_set_property_bool(ctx, udi, "resmgr.hack_ignore_remove", 1, &error))
226         {
227                 IF_ERROR_PRINT("can't set resmgr.hack_ignore_remove");
228                 dbus_error_init (&error);
229         }
230         dbus_error_free (&error);
231
232         return TRUE;
233 }
234
235 static char*
236 guess_device_name(LibHalContext* ctx, const char* udi)
237 {
238         char* device = NULL;
239         if((device = libhal_device_get_property_string (ctx, udi, "resmgr.device", NULL)))
240         {
241         }
242         /* block devices have these */
243         else if((device = libhal_device_get_property_string (ctx, udi, "block.device", NULL)))
244         {
245         }
246         /* alsa devs have these */
247         else if((device = libhal_device_get_property_string (ctx, udi, "linux.device_file", NULL)))
248         {
249         }
250         /* /dev/bus/usb/... */
251         else if((device = libhal_device_get_property_string (ctx, udi, "usbraw.device", NULL)))
252         {
253         }
254
255         return device;
256 }
257
258 static void
259 device_register_resmgr (LibHalContext *ctx, const char *udi)
260 {
261         unsigned i;
262         LibHalPropertyType proptype;
263         char **classes = NULL;
264         const char* key = "resmgr.class";
265         char* device = NULL;
266         char** flags = NULL;
267
268         if(libhal_device_property_exists (ctx, udi, key, NULL)
269         && usb_interface_hack(ctx, udi))
270                 return;
271
272         proptype = libhal_device_get_property_type(ctx, udi, key, NULL );
273
274         switch(proptype)
275         {
276                 case LIBHAL_PROPERTY_TYPE_STRLIST:
277                         classes = libhal_device_get_property_strlist (hal_ctx, udi, key, NULL);
278                         break;
279                 case LIBHAL_PROPERTY_TYPE_STRING:
280                         classes = calloc(2, sizeof(char*));
281                         classes[0] = libhal_device_get_property_string (hal_ctx, udi, key, NULL);
282                         classes[1] = NULL;
283                         break;
284                 default:
285                         break;
286         }
287
288         if(!classes)
289                 return;
290
291         flags = libhal_device_get_property_strlist (hal_ctx, udi, "resmgr.flags", NULL);
292
293         if((device = guess_device_name(hal_ctx, udi)))
294         {
295                 if(do_list)
296                 {
297                         unsigned i;
298                         printf("UDI %s\n", udi);
299                         printf("Device %s\n", device);
300                         fputs("Class", stdout);
301                         for(i = 0; classes && classes[i]; ++i)
302                         {
303                                 printf(" %s", classes[i]);
304                         }
305                         puts("");
306                         for(i = 0; flags && flags[i]; ++i)
307                         {
308                                 if(!i) fputs("Flags ", stdout);
309                                 fputc(' ', stdout);
310                                 fputs(flags[i], stdout);
311                         }
312                         if(i)
313                                 puts("");
314                 }
315                 else
316                 {
317                         rsm_doit(classes, device, flags);
318                 }
319                 libhal_free_string(device);
320         }
321
322         libhal_free_string_array (flags);
323         libhal_free_string_array(classes);
324 }
325
326 /** Print out program usage.
327  *
328  *  @param  argc                Number of arguments given to program
329  *  @param  argv                Arguments given to program
330  */
331 static void
332 usage (int argc __attribute__((unused)), char *argv[])
333 {
334         printf ("\n" "usage : %s [OPTIONS]\n", argv[0]);
335         printf ("\nOPTIONS:\n"
336                  "        --monitor        Monitor device list\n"
337                  "        --retries=NUM    try to get device list NUM times if it failed\n"
338                  "        --debug[=NUM]    set debug level\n"
339                  "        --grant=USER     grant USER access to devices\n"
340                  "        --revoke=USER    revoke USER's access to devices\n"
341                  "        --udi=UDI        set ACLs on UDI\n"
342                  "        --help           Show this information and exit\n"
343                  "\n"
344                  "Registers devices with resmgr. If the --monitor option is given\n"
345                  "then the device list and all devices are monitored for changes.\n"
346                  "\nCan be run as callout from hald without parameters do add or\n"
347                  "remove devices dynamically\n"
348                  "\n");
349 }
350
351 /** scan already known devices
352  */
353 static void
354 get_all_devices (void)
355 {
356         int i;
357         DBusError error;
358
359         if(devices)
360                 return;
361
362         dbus_error_init (&error);
363
364         for(i = 0; i < retries+1; ++i)
365         {
366                 devices = libhal_get_all_devices (hal_ctx, &num_devices, &error);
367                 if(devices)
368                         break;
369                 if(dbus_error_is_set(&error))
370                 {
371                         dbus_error_free(&error);
372                         dbus_error_init (&error);
373                 }
374                 fputc('.', stdout);
375                 fflush(stdout);
376                 sleep(1);
377         }
378         if (devices == NULL)
379         {
380                 if(dbus_error_is_set(&error))
381                 {
382                         DIE(("Couldn't obtain list of devices: %s - %s\n", error.name, error.message));
383                 }
384                 else
385                 {
386                         DIE(("Couldn't obtain list of devices\n"));
387                 }
388         }
389 }
390
391 static void
392 register_devices()
393 {
394         int i;
395
396         for (i = 0; i < num_devices; i++) {
397                 device_register_resmgr(hal_ctx, devices[i]);
398         }
399
400         libhal_free_string_array (devices);
401         devices = NULL;
402         num_devices = 0;
403 }
404
405 static void parse_options(int argc, char* argv[])
406 {
407         while (1) {
408                 int c;
409                 int option_index = 0;
410                 static struct option long_options[] = {
411                         {"help", 0, NULL, 1},
412                         {"monitor", 0, NULL, 2},
413                         {"retries", 1, NULL, 3},
414                         {"debug", 2, NULL, 4},
415                         {"diesilently", 0, NULL, 5},
416                         {"grant", 1, NULL, 7},
417                         {"revoke", 1, NULL, 8},
418                         {"udi", 1, NULL, 9},
419                         {"list", 0, NULL, 10},
420                         {NULL, 0, NULL, 0}
421                 };
422
423                 c = getopt_long (argc, argv, "",
424                                 long_options, &option_index);
425                 if (c == -1)
426                         break;
427
428                 switch (c) {
429                         case 1:
430                                 usage (argc, argv);
431                                 exit(0);
432                                 break;
433                         case 2:
434                                 do_monitor = TRUE;
435                                 break;
436
437                         case 3:
438                                 retries = atoi(optarg);
439                                 break;
440
441                         case 4:
442                                 if(optarg)
443                                         debug_level = atoi(optarg);
444                                 else
445                                         ++debug_level;
446
447                                 if(debug_level < 0)
448                                         debug_level = 1;
449
450                                 break;
451
452                         case 5:
453                                 diesilently = 1;
454                                 break;
455
456                         case 7:
457                                 {
458                                         uid_t uid;
459                                         struct passwd* pw = NULL;
460                                         char* endp;
461                                         unsigned i = num_login_users++;
462
463                                         login_users = realloc(login_users, sizeof(char*)*num_login_users);
464                                         login_uids = realloc(login_uids, sizeof(uid_t)*num_login_users);
465
466                                         uid = strtol(optarg, &endp, 10);
467
468                                         if(optarg != endp && !*endp)
469                                                 pw = getpwuid(uid);
470                                         else
471                                                 pw = getpwnam(optarg);
472
473                                         if(!pw)
474                                         {
475                                                 warning("cannot determine uid for %s", optarg);
476                                                 login_uids[i] = -1;
477                                                 login_users[i] = NULL;
478                                         }
479                                         else
480                                         {
481                                                 login_users[i] = strdup(pw->pw_name);
482                                                 login_uids[i] = pw->pw_uid;
483                                         }
484
485                                         add_all_users = FALSE;
486                                 }
487                                 break;
488
489                         case 8:
490                                 {
491                                         uid_t uid;
492                                         struct passwd* pw = NULL;
493                                         char* endp;
494                                         unsigned i = num_logout_users++;
495
496                                         logout_users = realloc(logout_users, sizeof(char*)*num_logout_users);
497                                         logout_uids = realloc(logout_uids, sizeof(uid_t)*num_logout_users);
498
499                                         uid = strtol(optarg, &endp, 10);
500
501                                         if(optarg != endp && !*endp)
502                                                 pw = getpwuid(uid);
503                                         else
504                                                 pw = getpwnam(optarg);
505
506                                         if(!pw)
507                                         {
508                                                 warning("cannot determine uid for %s", optarg);
509                                                 logout_uids[i] = -1;
510                                                 logout_users[i] = NULL;
511                                         }
512                                         else
513                                         {
514                                                 logout_users[i] = strdup(pw->pw_name);
515                                                 logout_uids[i] = pw->pw_uid;
516                                         }
517
518                                         add_all_users = FALSE;
519                                 }
520                                 break;
521
522                         case 9:
523                                 {
524                                         int i = num_devices++;
525                                         devices = realloc(devices, sizeof(char*)*num_devices+1);
526                                         devices[i] = strdup(optarg);
527                                         devices[i+1] = NULL;
528                                 }
529                                 break;
530
531                         case 10:
532                                 do_list = TRUE;
533                                 break;
534
535                         default:
536                                 usage (argc, argv);
537                                 exit(1);
538                                 break;
539                 }
540         }
541 }
542
543
544 static void main_dbus()
545 {
546         DBusError error;
547         MainLoop *loop = NULL;
548         DBusConnection *conn;
549
550         if (do_monitor)
551         {
552                 loop = main_loop_new();
553                 mainloop_catch_signals(loop);
554         }
555
556         dbus_error_init(&error);
557
558         conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error);
559         if (!conn)
560                 DIE(("dbus_bus_get: %s: %s", error.name, error.message));
561
562         if(loop && !connect_dbus_and_mainloop(loop, conn))
563                 DIE(("connect_dbus_and_mainloop"));
564
565         if ((hal_ctx = libhal_ctx_new ()) == NULL)
566                 DIE(("libhal_ctx_new"));
567
568         if (!libhal_ctx_set_dbus_connection (hal_ctx, conn))
569                 DIE(("libhal_ctx_set_dbus_connection: %s: %s", error.name, error.message));
570
571         if (!libhal_ctx_init (hal_ctx, &error))
572                 DIE(("libhal_ctx_init: %s: %s", error.name, error.message));
573
574         if(loop)
575         {
576                 libhal_ctx_set_device_added (hal_ctx, device_register_resmgr);
577         }
578
579         get_all_devices();
580
581         if(!do_list)
582                 resmgr_get_users();
583
584         register_devices();
585
586         if(loop)
587         {
588                 libhal_device_property_watch_all (hal_ctx, &error);
589                 main_loop_run(loop);
590         }
591
592         libhal_ctx_shutdown (hal_ctx, &error);
593         libhal_ctx_free (hal_ctx);
594
595         dbus_connection_close (conn);
596         dbus_connection_unref (conn);
597 }
598
599 /* use direct hal connection to make callout work during hal startup */
600 static void process_device(const char* udi)
601 {
602         DBusError error;
603
604         debug(1, "adding single device %s", udi);
605
606         dbus_error_init (&error);       
607
608         if ((hal_ctx = libhal_ctx_init_direct (&error)) == NULL) {
609                 DIE(("libhal_ctx_init_direct: %s: %s\n", error.name, error.message));
610         }
611         device_register_resmgr(hal_ctx, udi);
612
613         libhal_ctx_shutdown (hal_ctx, &error);
614         libhal_ctx_free (hal_ctx);
615 }
616
617 int main (int argc, char *argv[])
618 {
619         const char* udi;
620
621         udi = getenv("UDI");
622
623         if(rsm_connect() == -1)
624                 DIE(("can't connect to resmgrd"));
625
626         parse_options(argc, argv);
627
628         if(udi && !login_users && !logout_users)
629         {
630                 resmgr_get_users();
631                 process_device(udi);
632         }
633         else
634                 main_dbus();
635
636         rsm_disconnect();
637
638         return 0;
639 }