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