DBus booleans seem to stamp on more space than a C99 boolean
[dhcpcd-ui] / src / libdhcpcd / main.c
1 /*
2  * libdhcpcd
3  * Copyright 2009 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <errno.h>
28 #include <poll.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #include <dbus/dbus.h>
35
36 #define IN_LIBDHCPCD
37 #include "libdhcpcd.h"
38
39 #define DHCPCD_TIMEOUT_MS 500
40
41 #ifndef _unused
42 #  ifdef __GNUC__
43 #    define _unused __attribute__((__unused__))
44 #  else
45 #    define _unused
46 #  endif
47 #endif
48
49 DHCPCD_CONNECTION *dhcpcd_connections;
50 DHCPCD_WATCH *dhcpcd_watching;
51
52 static dbus_bool_t
53 dhcpcd_add_watch(DBusWatch *watch, void *data)
54 {
55         DHCPCD_WATCH *w;
56         int flags;
57
58         flags = dbus_watch_get_unix_fd(watch);
59         for (w = dhcpcd_watching; w; w = w->next) {
60                 if (w->pollfd.fd == flags)
61                         break;
62         }
63         if (w == NULL) {
64                 w = malloc(sizeof(*w));
65                 if (w == NULL)
66                         return false;
67                 w->next = dhcpcd_watching;
68                 dhcpcd_watching = w;
69         }
70
71         w->connection = (DHCPCD_CONNECTION *)data;
72         w->watch = watch;
73         w->pollfd.fd = flags;
74         flags = dbus_watch_get_flags(watch);
75         w->pollfd.events = POLLHUP | POLLERR;
76         if (flags & DBUS_WATCH_READABLE)
77                 w->pollfd.events |= POLLIN;
78         if (flags & DBUS_WATCH_WRITABLE)
79                 w->pollfd.events |= POLLOUT;
80         if (w->connection->add_watch)
81                 w->connection->add_watch(w->connection, &w->pollfd,
82                         w->connection->watch_data);
83         return true;
84 }
85
86 static void
87 dhcpcd_delete_watch(DBusWatch *watch, void *data)
88 {
89         DHCPCD_WATCH *w, *l;
90         int fd;
91
92         fd = dbus_watch_get_unix_fd(watch);
93         l = data = NULL;
94         for (w = dhcpcd_watching; w; w = w->next) {
95                 if (w->pollfd.fd == fd) {
96                         if (w->connection->delete_watch)
97                                 w->connection->delete_watch(w->connection,
98                                     &w->pollfd, w->connection->watch_data);
99                         if (l == NULL)
100                                 dhcpcd_watching = w->next;
101                         else
102                                 l->next = w->next;
103                         free(w);
104                         w = l;
105                 }
106         }
107 }
108
109 static DBusHandlerResult
110 dhcpcd_message(_unused DBusConnection *bus, DBusMessage *msg, void *data)
111 {
112         if (dhcpcd_dispatch_message((DHCPCD_CONNECTION *)data, msg))
113                 return DBUS_HANDLER_RESULT_HANDLED;
114         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
115 }
116
117 void
118 dhcpcd_if_free(DHCPCD_IF *ifs)
119 {
120         DHCPCD_IF *i;
121
122         while (ifs != NULL) {
123                 i = ifs->next;
124                 free(ifs);
125                 ifs = i;
126         }
127 }
128
129 DHCPCD_CONNECTION *
130 dhcpcd_open(char **error)
131 {
132         DBusError err;
133         DHCPCD_CONNECTION *con;
134         DBusConnection *bus;
135         int tries;
136
137         dbus_error_init(&err);
138         bus = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
139         if (dbus_error_is_set(&err)) {
140                 if (error)
141                         *error = strdup(err.message);
142                 dbus_error_free(&err);
143                 return NULL;
144         }
145         if (bus == NULL)
146                 return NULL;
147         con = calloc(1, sizeof(*con));
148         if (con == NULL)
149                 goto bad;
150         con->bus = bus;
151         if (!dbus_connection_set_watch_functions(bus,
152                 dhcpcd_add_watch, dhcpcd_delete_watch, NULL, con, NULL))
153                 goto bad;
154         dbus_bus_add_match(bus,
155             "type='signal',interface='" DHCPCD_SERVICE "'", &err);
156         dbus_connection_flush(bus);
157         if (dbus_error_is_set(&err)) {
158                 if (error)
159                         *error = strdup(err.message);
160                 dbus_error_free(&err);
161                 goto bad;
162         }
163         if (!dbus_connection_add_filter(bus, dhcpcd_message, con, NULL))
164                 goto bad;
165
166         /* Give dhcpcd-dbus a little time to automatically start */
167         tries = 5;
168         while (--tries > 0) {
169                 if (dhcpcd_command(con, "GetVersion", NULL, NULL)) {
170                         dhcpcd_error_clear(con);
171                         break;
172                 }
173         }
174
175         con->next = dhcpcd_connections;
176         dhcpcd_connections = con;
177
178         return con;
179
180 bad:
181         free(con);
182         dbus_connection_unref(bus);
183         return NULL;
184 }
185
186 bool
187 dhcpcd_close(DHCPCD_CONNECTION *con)
188 {
189         DHCPCD_CONNECTION *c, *l;
190
191         l = NULL;
192         for (c = dhcpcd_connections; c; c = c->next) {
193                 if (c == con) {
194                         if (l == NULL)
195                                 dhcpcd_connections = con->next;
196                         else
197                                 l->next = con->next;
198                         dbus_connection_unref(con->bus);
199                         dhcpcd_if_free(con->interfaces);
200                         dhcpcd_wi_history_clear(con);
201                         free(con->status);
202                         free(con->error);
203                         free(con);
204                         return true;
205                 }
206                 l = c;
207         }
208         dhcpcd_error_set(con, NULL, ESRCH);
209         return false;
210 }
211
212 DHCPCD_CONNECTION *
213 dhcpcd_if_connection(DHCPCD_IF *interface)
214 {
215         DHCPCD_CONNECTION *c;
216         DHCPCD_IF *i;
217
218         for (c = dhcpcd_connections; c; c = c->next) {
219                 for (i = c->interfaces; i; i = i->next)
220                         if (i == interface)
221                                 return c;
222         }
223         return NULL;
224 }
225
226 void
227 dhcpcd_error_clear(DHCPCD_CONNECTION *con)
228 {
229         free(con->error);
230         con->error = NULL;
231         con->err = 0;
232 }
233
234 void
235 dhcpcd_error_set(DHCPCD_CONNECTION *con, const char *error, int err)
236 {
237         dhcpcd_error_clear(con);
238         if (error != NULL) {
239                 con->error = strdup(error);
240                 if (err == 0)
241                         err = EPROTO;
242         } else if (err != 0)
243                 con->error = strdup(strerror(err));
244         con->err = err;
245         con->errors++;
246 }
247
248 const char *
249 dhcpcd_error(DHCPCD_CONNECTION *con)
250 {
251         return con->error;
252 }
253
254 bool
255 dhcpcd_iter_get(DHCPCD_CONNECTION *con, DBusMessageIter *iter,
256     int type, void *arg)
257 {
258         if (dbus_message_iter_get_arg_type(iter) == type) {
259                 dbus_message_iter_get_basic(iter, arg);
260                 dbus_message_iter_next(iter);
261                 return true;
262         }
263         dhcpcd_error_set(con, 0, EINVAL);
264         return false;
265 }
266
267 static bool
268 dhcpcd_message_error(DHCPCD_CONNECTION *con, DHCPCD_MESSAGE *msg)
269 {
270         DBusMessageIter args;
271         char *s;
272         
273         if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_ERROR)
274                 return false;
275         if (dbus_message_iter_init(msg, &args) &&
276             dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING)
277         {
278                 dbus_message_iter_get_basic(&args, &s);
279                 dhcpcd_error_set(con, s, 0);
280         }
281         return true;
282 }
283
284 DHCPCD_MESSAGE *
285 dhcpcd_send_reply(DHCPCD_CONNECTION *con, DHCPCD_MESSAGE *msg)
286 {
287         DBusMessage *reply;
288         DBusPendingCall *pending;
289
290         pending = NULL;
291         if (!dbus_connection_send_with_reply(con->bus, msg, &pending,
292                 DHCPCD_TIMEOUT_MS) ||
293             pending == NULL)
294                 return NULL;
295         dbus_connection_flush(con->bus);
296
297         /* Block until we receive a reply */
298         dbus_pending_call_block(pending);
299         reply = dbus_pending_call_steal_reply(pending);
300         dbus_pending_call_unref(pending);
301         if (dhcpcd_message_error(con, reply)) {
302                 dbus_message_unref(reply);
303                 return NULL;
304         }
305         return reply;
306 }
307
308 DHCPCD_MESSAGE *
309 dhcpcd_message_reply(DHCPCD_CONNECTION *con, const char *cmd, const char *arg)
310 {
311         DBusMessage *msg, *reply;
312         DBusMessageIter args;
313         
314         msg = dbus_message_new_method_call(DHCPCD_SERVICE, DHCPCD_PATH,
315             DHCPCD_SERVICE, cmd);
316         if (msg == NULL) {
317                 dhcpcd_error_set(con, 0, errno);
318                 return NULL;
319         }
320         dbus_message_iter_init_append(msg, &args);
321         if (arg != NULL &&
322             !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &arg))
323         {
324                 dbus_message_unref(msg);
325                 dhcpcd_error_set(con, 0, EINVAL);
326                 return NULL;
327         }
328         reply = dhcpcd_send_reply(con, msg);
329         dbus_message_unref(msg);
330         return reply;
331 }
332
333 bool
334 dhcpcd_command(DHCPCD_CONNECTION *con, const char *cmd, const char *arg,
335         char **reply)
336 {
337         DBusMessage *msg;
338         DBusMessageIter args;
339         char *s;
340
341         msg = dhcpcd_message_reply(con, cmd, arg);
342         if (msg == NULL)
343                 return false;
344         if (dhcpcd_message_error(con, msg)) {
345                 dbus_message_unref(msg);
346                 return false;
347         }
348         if (reply != NULL &&
349             dbus_message_iter_init(msg, &args) &&
350             dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING)
351         {
352                 dbus_message_iter_get_basic(&args, &s);
353                 *reply = strdup(s);
354         }
355         dbus_message_unref(msg);
356         return true;
357 }
358
359 void
360 dhcpcd_dispatch(int fd)
361 {
362         DHCPCD_WATCH *w;
363         struct pollfd fds;
364         int n, flags;
365
366         fds.fd = fd;
367         fds.events = (POLLIN | POLLHUP | POLLOUT | POLLERR);
368         n = poll(&fds, 1, 0);
369         flags = 0;
370         if (n == 1) {
371                 if (fds.revents & POLLIN)
372                         flags |= DBUS_WATCH_READABLE;
373                 if (fds.revents & POLLOUT)
374                         flags |= DBUS_WATCH_WRITABLE;
375                 if (fds.revents & POLLHUP)
376                         flags |= DBUS_WATCH_HANGUP;
377                 if (fds.revents & POLLERR)
378                         flags |= DBUS_WATCH_ERROR;
379         }
380         for (w = dhcpcd_watching; w; w = w->next) {
381                 if (w->pollfd.fd == fd) {
382                         if (flags != 0)
383                                 dbus_watch_handle(w->watch, flags);
384                         dbus_connection_ref(w->connection->bus);
385                         while (dbus_connection_dispatch(w->connection->bus) ==
386                             DBUS_DISPATCH_DATA_REMAINS)
387                                 ;
388                         dbus_connection_unref(w->connection->bus);
389                 }
390         }
391 }
392
393 DHCPCD_IF *
394 dhcpcd_if_new(DHCPCD_CONNECTION *con, DBusMessageIter *array, char **order)
395 {
396         DBusMessageIter dict, entry, var;
397         DHCPCD_IF *i;
398         char *s;
399         uint32_t u32;
400         int b, errors;
401
402         if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) {
403                 errno = EINVAL;
404                 return NULL;
405         }
406         dbus_message_iter_recurse(array, &dict);
407         i = calloc(1, sizeof(*i));
408         if (i == NULL) {
409                 dhcpcd_error_set(con, 0, errno);
410                 return NULL;
411         }
412         errors = con->errors;
413         for (;
414              dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY;
415              dbus_message_iter_next(&dict))
416         {
417                 dbus_message_iter_recurse(&dict, &entry);
418                 if (!dhcpcd_iter_get(con, &entry, DBUS_TYPE_STRING, &s))
419                         break;
420                 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
421                         break;
422                 dbus_message_iter_recurse(&entry, &var);
423                 if (strcmp(s, "Interface") == 0) {
424                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
425                                 break;
426                         strlcpy(i->ifname, s, sizeof(i->ifname));
427                 } else if (strcmp(s, "Flags") == 0) {
428                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_UINT32, &u32))
429                                 break;
430                         i->flags = u32;
431                 } else if (strcmp(s, "Reason") == 0) {
432                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
433                                 break;
434                         strlcpy(i->reason, s, sizeof(i->reason));
435                 } else if (strcmp(s, "Wireless") == 0) {
436                         /* b is an int as DBus booleans want more space than
437                          * a C99 boolean */
438                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_BOOLEAN, &b))
439                                 break;
440                         i->wireless = b;
441                 } else if (strcmp(s, "SSID") == 0) {
442                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
443                                 break;
444                         strlcpy(i->ssid, s, sizeof(i->ssid));
445                 } else if (strcmp(s, "IPAddress") == 0) {
446                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_UINT32, &u32))
447                                 break;
448                         i->ip.s_addr = u32;
449                 } else if (strcmp(s, "SubnetCIDR") == 0)
450                         dbus_message_iter_get_basic(&var, &i->cidr);
451                 else if (order != NULL && strcmp(s, "InterfaceOrder") == 0)
452                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, order))
453                                 break;
454         }
455
456         if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
457                 free(i);
458                 if (con->errors == errors)
459                         dhcpcd_error_set(con, NULL, EINVAL);
460                 return NULL;
461         }
462         return i;
463 }
464
465 DHCPCD_IF *
466 dhcpcd_interfaces(DHCPCD_CONNECTION *con)
467 {
468         DBusMessage *msg;
469         DBusMessageIter args, dict, entry;
470         DHCPCD_IF *i, *l;
471         int errors;
472         
473         if (con->interfaces != NULL)
474                 return con->interfaces;
475         l = NULL;
476         msg = dhcpcd_message_reply(con, "GetInterfaces", NULL);
477         if (msg == NULL)
478                 return NULL;
479         if (!dbus_message_iter_init(msg, &args) ||
480             dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
481         {
482                 dbus_message_unref(msg);
483                 dhcpcd_error_set(con, 0, EINVAL);
484                 return NULL;
485         }
486
487         l = NULL;
488         errors = con->errors;
489         dbus_message_iter_recurse(&args, &dict);
490         for (;
491              dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY;
492              dbus_message_iter_next(&dict))    
493         {
494                 dbus_message_iter_recurse(&dict, &entry);
495                 dbus_message_iter_next(&entry);
496                 i = dhcpcd_if_new(con, &entry, NULL);
497                 if (i == NULL)
498                         break;
499                 if (l == NULL)
500                         con->interfaces = i;
501                 else
502                         l->next = i;
503                 l = i;
504         }
505         dbus_message_unref(msg);
506         if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
507                 if (con->errors == errors)
508                         dhcpcd_error_set(con, 0, EINVAL);
509                 dhcpcd_if_free(con->interfaces);
510                 con->interfaces = NULL;
511         }
512         return con->interfaces;
513 }
514
515 DHCPCD_IF *
516 dhcpcd_if_find(DHCPCD_CONNECTION *con, const char *ifname)
517 {
518         DHCPCD_IF *i;
519
520         if (con->interfaces == NULL)
521                 dhcpcd_interfaces(con);
522         for (i = con->interfaces; i; i = i ->next)
523                 if (strcmp(i->ifname, ifname) == 0)
524                         return i;
525         return NULL;
526 }
527
528 const char *
529 dhcpcd_status(DHCPCD_CONNECTION *con)
530 {
531         if (con->status == NULL)
532                 dhcpcd_command(con, "GetStatus", NULL, &con->status);
533         return con->status;
534 }
535
536 void
537 dhcpcd_set_watch_functions(DHCPCD_CONNECTION *con,
538     void (*add_watch)(DHCPCD_CONNECTION *, const struct pollfd *, void *),
539     void (*delete_watch)(DHCPCD_CONNECTION *, const struct pollfd *, void *),
540     void *data)
541 {
542         DHCPCD_WATCH *w;
543         
544         con->add_watch = add_watch;
545         con->delete_watch = delete_watch;
546         con->watch_data = data;
547         if (con->add_watch) {
548                 for (w = dhcpcd_watching; w; w = w->next)
549                         if (w->connection == con)
550                                 con->add_watch(con, &w->pollfd, data);
551         }
552 }
553
554 void
555 dhcpcd_set_signal_functions(DHCPCD_CONNECTION *con,
556     void (*event)(DHCPCD_CONNECTION *, DHCPCD_IF *, void *),
557     void (*status_changed)(DHCPCD_CONNECTION *, const char *, void *),
558     void (*wi_scanresults)(DHCPCD_CONNECTION *, DHCPCD_IF *, void *),
559     void *data)
560 {
561         DHCPCD_IF *i;
562         
563         con->event = event;
564         con->status_changed = status_changed;
565         con->wi_scanresults = wi_scanresults;
566         con->signal_data = data;
567         if (con->status_changed) {
568                 if (con->status == NULL)
569                         dhcpcd_status(con);
570                 con->status_changed(con, con->status, data);
571         }
572         if (con->wi_scanresults) {
573                 for (i = dhcpcd_interfaces(con); i; i = i->next)
574                         if (i->wireless)
575                                 con->wi_scanresults(con, i, data);
576         }
577 }