Work with older style as well.
[dhcpcd-ui] / src / libdhcpcd / main.c
1 /*
2  * libdhcpcd
3  * Copyright 2009-2012 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 <arpa/inet.h>
28 #include <errno.h>
29 #include <poll.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <dbus/dbus.h>
36
37 #define IN_LIBDHCPCD
38 #include "libdhcpcd.h"
39 #include "config.h"
40
41 #define DHCPCD_TIMEOUT_MS 500
42
43 #ifndef _unused
44 #  ifdef __GNUC__
45 #    define _unused __attribute__((__unused__))
46 #  else
47 #    define _unused
48 #  endif
49 #endif
50
51 DHCPCD_CONNECTION *dhcpcd_connections;
52 DHCPCD_WATCH *dhcpcd_watching;
53
54 static dbus_bool_t
55 dhcpcd_add_watch(DBusWatch *watch, void *data)
56 {
57         DHCPCD_WATCH *w;
58         int flags;
59
60         flags = dbus_watch_get_unix_fd(watch);
61         for (w = dhcpcd_watching; w; w = w->next) {
62                 if (w->pollfd.fd == flags)
63                         break;
64         }
65         if (w == NULL) {
66                 w = malloc(sizeof(*w));
67                 if (w == NULL)
68                         return false;
69                 w->next = dhcpcd_watching;
70                 dhcpcd_watching = w;
71         }
72
73         w->connection = (DHCPCD_CONNECTION *)data;
74         w->watch = watch;
75         w->pollfd.fd = flags;
76         flags = dbus_watch_get_flags(watch);
77         w->pollfd.events = POLLHUP | POLLERR;
78         if (flags & DBUS_WATCH_READABLE)
79                 w->pollfd.events |= POLLIN;
80         if (flags & DBUS_WATCH_WRITABLE)
81                 w->pollfd.events |= POLLOUT;
82         if (w->connection->add_watch)
83                 w->connection->add_watch(w->connection, &w->pollfd,
84                         w->connection->watch_data);
85         return true;
86 }
87
88 static void
89 dhcpcd_delete_watch(DBusWatch *watch, void *data)
90 {
91         DHCPCD_WATCH *w, *l;
92         int fd;
93
94         fd = dbus_watch_get_unix_fd(watch);
95         l = data = NULL;
96         for (w = dhcpcd_watching; w; w = w->next) {
97                 if (w->pollfd.fd == fd) {
98                         if (w->connection->delete_watch)
99                                 w->connection->delete_watch(w->connection,
100                                     &w->pollfd, w->connection->watch_data);
101                         if (l == NULL)
102                                 dhcpcd_watching = w->next;
103                         else
104                                 l->next = w->next;
105                         free(w);
106                         w = l;
107                 }
108         }
109 }
110
111 static DBusHandlerResult
112 dhcpcd_message(_unused DBusConnection *bus, DBusMessage *msg, void *data)
113 {
114         if (dhcpcd_dispatch_message((DHCPCD_CONNECTION *)data, msg))
115                 return DBUS_HANDLER_RESULT_HANDLED;
116         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
117 }
118
119 void
120 dhcpcd_if_free(DHCPCD_IF *ifs)
121 {
122         DHCPCD_IF *i;
123
124         while (ifs != NULL) {
125                 i = ifs->next;
126                 free(ifs);
127                 ifs = i;
128         }
129 }
130
131 DHCPCD_CONNECTION *
132 dhcpcd_open(char **error)
133 {
134         DBusError err;
135         DHCPCD_CONNECTION *con;
136         DBusConnection *bus;
137         int tries;
138
139         dbus_error_init(&err);
140         bus = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
141         if (dbus_error_is_set(&err)) {
142                 if (error)
143                         *error = strdup(err.message);
144                 dbus_error_free(&err);
145                 return NULL;
146         }
147         if (bus == NULL)
148                 return NULL;
149         con = calloc(1, sizeof(*con));
150         if (con == NULL)
151                 goto bad;
152         con->bus = bus;
153         if (!dbus_connection_set_watch_functions(bus,
154                 dhcpcd_add_watch, dhcpcd_delete_watch, NULL, con, NULL))
155                 goto bad;
156         dbus_bus_add_match(bus,
157             "type='signal',interface='" DHCPCD_SERVICE "'", &err);
158         dbus_connection_flush(bus);
159         if (dbus_error_is_set(&err)) {
160                 if (error)
161                         *error = strdup(err.message);
162                 dbus_error_free(&err);
163                 goto bad;
164         }
165         if (!dbus_connection_add_filter(bus, dhcpcd_message, con, NULL))
166                 goto bad;
167
168         /* Give dhcpcd-dbus a little time to automatically start */
169         tries = 5;
170         while (--tries > 0) {
171                 if (dhcpcd_command(con, "GetVersion", NULL, NULL)) {
172                         dhcpcd_error_clear(con);
173                         break;
174                 }
175         }
176
177         con->next = dhcpcd_connections;
178         dhcpcd_connections = con;
179
180         return con;
181
182 bad:
183         free(con);
184         dbus_connection_unref(bus);
185         return NULL;
186 }
187
188 bool
189 dhcpcd_close(DHCPCD_CONNECTION *con)
190 {
191         DHCPCD_CONNECTION *c, *l;
192
193         l = NULL;
194         for (c = dhcpcd_connections; c; c = c->next) {
195                 if (c == con) {
196                         if (l == NULL)
197                                 dhcpcd_connections = con->next;
198                         else
199                                 l->next = con->next;
200                         dbus_connection_unref(con->bus);
201                         dhcpcd_if_free(con->interfaces);
202                         dhcpcd_wi_history_clear(con);
203                         free(con->status);
204                         free(con->error);
205                         free(con);
206                         return true;
207                 }
208                 l = c;
209         }
210         dhcpcd_error_set(con, NULL, ESRCH);
211         return false;
212 }
213
214 DHCPCD_CONNECTION *
215 dhcpcd_if_connection(DHCPCD_IF *interface)
216 {
217         DHCPCD_CONNECTION *c;
218         DHCPCD_IF *i;
219
220         for (c = dhcpcd_connections; c; c = c->next) {
221                 for (i = c->interfaces; i; i = i->next)
222                         if (i == interface)
223                                 return c;
224         }
225         return NULL;
226 }
227
228 void
229 dhcpcd_error_clear(DHCPCD_CONNECTION *con)
230 {
231         free(con->error);
232         con->error = NULL;
233         con->err = 0;
234 }
235
236 void
237 dhcpcd_error_set(DHCPCD_CONNECTION *con, const char *error, int err)
238 {
239         dhcpcd_error_clear(con);
240         if (error != NULL) {
241                 con->error = strdup(error);
242                 if (err == 0)
243                         err = EPROTO;
244         } else if (err != 0)
245                 con->error = strdup(strerror(err));
246         con->err = err;
247         con->errors++;
248 }
249
250 const char *
251 dhcpcd_error(DHCPCD_CONNECTION *con)
252 {
253         return con->error;
254 }
255
256 bool
257 dhcpcd_iter_get(DHCPCD_CONNECTION *con, DBusMessageIter *iter,
258     int type, void *arg)
259 {
260         if (dbus_message_iter_get_arg_type(iter) == type) {
261                 dbus_message_iter_get_basic(iter, arg);
262                 dbus_message_iter_next(iter);
263                 return true;
264         }
265         dhcpcd_error_set(con, 0, EINVAL);
266         return false;
267 }
268
269 static bool
270 dhcpcd_message_error(DHCPCD_CONNECTION *con, DHCPCD_MESSAGE *msg)
271 {
272         DBusMessageIter args;
273         char *s;
274         
275         if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_ERROR)
276                 return false;
277         if (dbus_message_iter_init(msg, &args) &&
278             dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING)
279         {
280                 dbus_message_iter_get_basic(&args, &s);
281                 dhcpcd_error_set(con, s, 0);
282         }
283         return true;
284 }
285
286 DHCPCD_MESSAGE *
287 dhcpcd_send_reply(DHCPCD_CONNECTION *con, DHCPCD_MESSAGE *msg)
288 {
289         DBusMessage *reply;
290         DBusPendingCall *pending;
291
292         pending = NULL;
293         if (!dbus_connection_send_with_reply(con->bus, msg, &pending,
294                 DHCPCD_TIMEOUT_MS) ||
295             pending == NULL)
296                 return NULL;
297         dbus_connection_flush(con->bus);
298
299         /* Block until we receive a reply */
300         dbus_pending_call_block(pending);
301         reply = dbus_pending_call_steal_reply(pending);
302         dbus_pending_call_unref(pending);
303         if (dhcpcd_message_error(con, reply)) {
304                 dbus_message_unref(reply);
305                 return NULL;
306         }
307         return reply;
308 }
309
310 DHCPCD_MESSAGE *
311 dhcpcd_message_reply(DHCPCD_CONNECTION *con, const char *cmd, const char *arg)
312 {
313         DBusMessage *msg, *reply;
314         DBusMessageIter args;
315         
316         msg = dbus_message_new_method_call(DHCPCD_SERVICE, DHCPCD_PATH,
317             DHCPCD_SERVICE, cmd);
318         if (msg == NULL) {
319                 dhcpcd_error_set(con, 0, errno);
320                 return NULL;
321         }
322         dbus_message_iter_init_append(msg, &args);
323         if (arg != NULL &&
324             !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &arg))
325         {
326                 dbus_message_unref(msg);
327                 dhcpcd_error_set(con, 0, EINVAL);
328                 return NULL;
329         }
330         reply = dhcpcd_send_reply(con, msg);
331         dbus_message_unref(msg);
332         return reply;
333 }
334
335 bool
336 dhcpcd_command(DHCPCD_CONNECTION *con, const char *cmd, const char *arg,
337         char **reply)
338 {
339         DBusMessage *msg;
340         DBusMessageIter args;
341         char *s;
342
343         msg = dhcpcd_message_reply(con, cmd, arg);
344         if (msg == NULL)
345                 return false;
346         if (dhcpcd_message_error(con, msg)) {
347                 dbus_message_unref(msg);
348                 return false;
349         }
350         if (reply != NULL &&
351             dbus_message_iter_init(msg, &args) &&
352             dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING)
353         {
354                 dbus_message_iter_get_basic(&args, &s);
355                 *reply = strdup(s);
356         }
357         dbus_message_unref(msg);
358         return true;
359 }
360
361 void
362 dhcpcd_dispatch(int fd)
363 {
364         DHCPCD_WATCH *w;
365         struct pollfd fds;
366         int n, flags;
367
368         fds.fd = fd;
369         fds.events = (POLLIN | POLLHUP | POLLOUT | POLLERR);
370         n = poll(&fds, 1, 0);
371         flags = 0;
372         if (n == 1) {
373                 if (fds.revents & POLLIN)
374                         flags |= DBUS_WATCH_READABLE;
375                 if (fds.revents & POLLOUT)
376                         flags |= DBUS_WATCH_WRITABLE;
377                 if (fds.revents & POLLHUP)
378                         flags |= DBUS_WATCH_HANGUP;
379                 if (fds.revents & POLLERR)
380                         flags |= DBUS_WATCH_ERROR;
381         }
382         for (w = dhcpcd_watching; w; w = w->next) {
383                 if (w->pollfd.fd == fd) {
384                         if (flags != 0)
385                                 dbus_watch_handle(w->watch, flags);
386                         dbus_connection_ref(w->connection->bus);
387                         while (dbus_connection_dispatch(w->connection->bus) ==
388                             DBUS_DISPATCH_DATA_REMAINS)
389                                 ;
390                         dbus_connection_unref(w->connection->bus);
391                 }
392         }
393 }
394
395 DHCPCD_IF *
396 dhcpcd_if_new(DHCPCD_CONNECTION *con, DBusMessageIter *array, char **order)
397 {
398         DBusMessageIter dict, entry, var, a;
399         DHCPCD_IF *i;
400         char *s, *p;
401         uint32_t u32;
402         int b, errors;
403
404         if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) {
405                 errno = EINVAL;
406                 return NULL;
407         }
408         dbus_message_iter_recurse(array, &dict);
409         i = calloc(1, sizeof(*i));
410         if (i == NULL) {
411                 dhcpcd_error_set(con, 0, errno);
412                 return NULL;
413         }
414         errors = con->errors;
415         for (;
416              dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY;
417              dbus_message_iter_next(&dict))
418         {
419                 dbus_message_iter_recurse(&dict, &entry);
420                 if (!dhcpcd_iter_get(con, &entry, DBUS_TYPE_STRING, &s))
421                         break;
422                 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
423                         break;
424                 dbus_message_iter_recurse(&entry, &var);
425                 if (strcmp(s, "Interface") == 0) {
426                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
427                                 break;
428                         strlcpy(i->ifname, s, sizeof(i->ifname));
429                 } else if (strcmp(s, "Type") == 0) {
430                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
431                                 break;
432                         strlcpy(i->type, s, sizeof(i->type));
433                 } else if (strcmp(s, "Flags") == 0) {
434                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_UINT32, &u32))
435                                 break;
436                         i->flags = u32;
437                 } else if (strcmp(s, "Up") == 0) {
438                         /* b is an int as DBus booleans want more space than
439                          * a C99 boolean */
440                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_BOOLEAN, &b))
441                                 break;
442                         i->up = b;
443                 } else if (strcmp(s, "Reason") == 0) {
444                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
445                                 break;
446                         strlcpy(i->reason, s, sizeof(i->reason));
447                 } else if (strcmp(s, "Wireless") == 0) {
448                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_BOOLEAN, &b))
449                                 break;
450                         i->wireless = b;
451                 } else if (strcmp(s, "SSID") == 0) {
452                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
453                                 break;
454                         strlcpy(i->ssid, s, sizeof(i->ssid));
455                 } else if (strcmp(s, "IPAddress") == 0) {
456                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_UINT32, &u32))
457                                 break;
458                         i->ip.s_addr = u32;
459                 } else if (strcmp(s, "SubnetCIDR") == 0)
460                         dbus_message_iter_get_basic(&var, &i->cidr);
461                 else if (strcmp(s, "RA_Prefix") == 0) {
462                         /* Don't crash with older dhcpcd versions */
463                         if (dbus_message_iter_get_arg_type(&dict) ==
464                             DBUS_TYPE_STRING)
465                         {
466                                 if (!dhcpcd_iter_get(con, &a,
467                                     DBUS_TYPE_STRING, &s))
468                                         break;
469                                 inet_pton(AF_INET6, s, &i->prefix.s6_addr);
470                                 continue;
471                         }
472
473                         if (dbus_message_iter_get_arg_type(&dict) !=
474                             DBUS_TYPE_DICT_ENTRY)
475                                 break;
476                         dbus_message_iter_recurse(&var, &a);
477                         if (!dhcpcd_iter_get(con, &a, DBUS_TYPE_STRING, &s))
478                                 break;
479                         /* Future versions may include pltime and vltime */
480                         p = strchr(s, ',');
481                         if (p)
482                                 *p = '\0';
483                         p = strchr(s, '/');
484                         if (p) {
485                                 *p++ = '\0';
486                                 i->prefix_len = atoi(p);
487                         } else
488                                 i->prefix_len = 0;
489                         inet_pton(AF_INET6, s, &i->prefix.s6_addr);
490                 } else if (strcmp(s, "RA_PrefixLen") == 0) { 
491                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_BYTE, &b)) 
492                                 break; 
493                         i->prefix_len = b; 
494                 } else if (order != NULL && strcmp(s, "InterfaceOrder") == 0)
495                         if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, order))
496                                 break;
497         }
498
499         if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
500                 free(i);
501                 if (con->errors == errors)
502                         dhcpcd_error_set(con, NULL, EINVAL);
503                 return NULL;
504         }
505         return i;
506 }
507
508 DHCPCD_IF *
509 dhcpcd_interfaces(DHCPCD_CONNECTION *con)
510 {
511         DBusMessage *msg;
512         DBusMessageIter args, dict, entry;
513         DHCPCD_IF *i, *l;
514         int errors;
515         
516         if (con->interfaces != NULL)
517                 return con->interfaces;
518         l = NULL;
519         msg = dhcpcd_message_reply(con, "GetInterfaces", NULL);
520         if (msg == NULL)
521                 return NULL;
522         if (!dbus_message_iter_init(msg, &args) ||
523             dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
524         {
525                 dbus_message_unref(msg);
526                 dhcpcd_error_set(con, 0, EINVAL);
527                 return NULL;
528         }
529
530         l = NULL;
531         errors = con->errors;
532         dbus_message_iter_recurse(&args, &dict);
533         for (;
534              dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY;
535              dbus_message_iter_next(&dict))    
536         {
537                 dbus_message_iter_recurse(&dict, &entry);
538                 dbus_message_iter_next(&entry);
539                 i = dhcpcd_if_new(con, &entry, NULL);
540                 if (i == NULL)
541                         break;
542                 if (l == NULL)
543                         con->interfaces = i;
544                 else
545                         l->next = i;
546                 l = i;
547         }
548         dbus_message_unref(msg);
549         if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
550                 if (con->errors == errors)
551                         dhcpcd_error_set(con, 0, EINVAL);
552                 dhcpcd_if_free(con->interfaces);
553                 con->interfaces = NULL;
554         }
555         return con->interfaces;
556 }
557
558 DHCPCD_IF *
559 dhcpcd_if_find(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
560 {
561         DHCPCD_IF *i;
562
563         if (con->interfaces == NULL)
564                 dhcpcd_interfaces(con);
565         for (i = con->interfaces; i; i = i ->next)
566                 if (strcmp(i->ifname, ifname) == 0 &&
567                     strcmp(i->type, type) == 0)
568                         return i;
569         return NULL;
570 }
571
572 const char *
573 dhcpcd_status(DHCPCD_CONNECTION *con)
574 {
575         if (con->status == NULL)
576                 dhcpcd_command(con, "GetStatus", NULL, &con->status);
577         return con->status;
578 }
579
580 void
581 dhcpcd_set_watch_functions(DHCPCD_CONNECTION *con,
582     void (*add_watch)(DHCPCD_CONNECTION *, const struct pollfd *, void *),
583     void (*delete_watch)(DHCPCD_CONNECTION *, const struct pollfd *, void *),
584     void *data)
585 {
586         DHCPCD_WATCH *w;
587         
588         con->add_watch = add_watch;
589         con->delete_watch = delete_watch;
590         con->watch_data = data;
591         if (con->add_watch) {
592                 for (w = dhcpcd_watching; w; w = w->next)
593                         if (w->connection == con)
594                                 con->add_watch(con, &w->pollfd, data);
595         }
596 }
597
598 void
599 dhcpcd_set_signal_functions(DHCPCD_CONNECTION *con,
600     void (*event)(DHCPCD_CONNECTION *, DHCPCD_IF *, void *),
601     void (*status_changed)(DHCPCD_CONNECTION *, const char *, void *),
602     void (*wi_scanresults)(DHCPCD_CONNECTION *, DHCPCD_IF *, void *),
603     void *data)
604 {
605         DHCPCD_IF *i;
606         
607         con->event = event;
608         con->status_changed = status_changed;
609         con->wi_scanresults = wi_scanresults;
610         con->signal_data = data;
611         if (con->status_changed) {
612                 if (con->status == NULL)
613                         dhcpcd_status(con);
614                 con->status_changed(con, con->status, data);
615         }
616         if (con->wi_scanresults) {
617                 for (i = dhcpcd_interfaces(con); i; i = i->next)
618                         if (i->wireless && strcmp(i->type, "ipv4") == 0)
619                                 con->wi_scanresults(con, i, data);
620         }
621 }