2908b38269031cda23c4e054b64e3195f68d85b1
[libeap.git] / src / wps / wps_er.c
1 /*
2  * Wi-Fi Protected Setup - External Registrar
3  * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14
15 #include "includes.h"
16
17 #include "common.h"
18 #include "uuid.h"
19 #include "eloop.h"
20 #include "wps_i.h"
21 #include "wps_upnp.h"
22 #include "wps_upnp_i.h"
23
24
25 /* TODO:
26  * SSDP M-SEARCH multicast TX for WFA
27  * create AP entry
28  * fetch wps_device info based on LOCATION: from SSDP NOTIFY
29  * parse wps_device info into AP entry (name, SCPD/control/eventSub URLs, etc.
30  * subscribe to events
31  * send notification of new AP device with wpa_msg
32  * re-send notifications with wpa_msg if ER re-started (to update wpa_gui-qt4)
33  * (also re-send SSDP M-SEARCH in this case to find new APs)
34  * parse UPnP event messages
35  */
36
37 static void wps_er_ap_timeout(void *eloop_data, void *user_ctx);
38
39
40 struct wps_er_ap {
41         struct wps_er_ap *next;
42         struct in_addr addr;
43         char *location;
44
45 };
46
47 struct wps_er {
48         struct wps_registrar *reg;
49         char ifname[17];
50         char *mac_addr_text; /* mac addr of network i.f. we use */
51         u8 mac_addr[ETH_ALEN]; /* mac addr of network i.f. we use */
52         char *ip_addr_text; /* IP address of network i.f. we use */
53         unsigned ip_addr; /* IP address of network i.f. we use (host order) */
54         int multicast_sd;
55         int ssdp_sd;
56         struct wps_er_ap *ap;
57 };
58
59
60 static void wps_er_pin_needed_cb(void *ctx, const u8 *uuid_e,
61                                  const struct wps_device_data *dev)
62 {
63         wpa_printf(MSG_DEBUG, "WPS ER: PIN needed");
64 }
65
66
67 static struct wps_er_ap * wps_er_ap_get(struct wps_er *er,
68                                         struct in_addr *addr)
69 {
70         struct wps_er_ap *ap;
71         for (ap = er->ap; ap; ap = ap->next) {
72                 if (ap->addr.s_addr == addr->s_addr)
73                         break;
74         }
75         return ap;
76 }
77
78
79 static void wps_er_ap_free(struct wps_er *er, struct wps_er_ap *ap)
80 {
81         wpa_printf(MSG_DEBUG, "WPS ER: Removing AP entry for %s (%s)",
82                    inet_ntoa(ap->addr), ap->location);
83         eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
84         os_free(ap->location);
85         os_free(ap);
86 }
87
88
89 static void wps_er_ap_timeout(void *eloop_data, void *user_ctx)
90 {
91         struct wps_er *er = eloop_data;
92         struct wps_er_ap *ap = user_ctx;
93         wpa_printf(MSG_DEBUG, "WPS ER: AP advertisement timed out");
94         wps_er_ap_free(er, ap);
95 }
96
97
98 static void wps_er_ap_add(struct wps_er *er, struct in_addr *addr,
99                           const char *location, int max_age)
100 {
101         struct wps_er_ap *ap;
102
103         ap = wps_er_ap_get(er, addr);
104         if (ap) {
105                 /* Update advertisement timeout */
106                 eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
107                 eloop_register_timeout(max_age, 0, wps_er_ap_timeout, er, ap);
108                 return;
109         }
110
111         ap = os_zalloc(sizeof(*ap));
112         if (ap == NULL)
113                 return;
114         ap->location = os_strdup(location);
115         if (ap->location == NULL) {
116                 os_free(ap);
117                 return;
118         }
119         ap->next = er->ap;
120         er->ap = ap;
121
122         ap->addr.s_addr = addr->s_addr;
123         eloop_register_timeout(max_age, 0, wps_er_ap_timeout, er, ap);
124
125         wpa_printf(MSG_DEBUG, "WPS ER: Added AP entry for %s (%s)",
126                    inet_ntoa(ap->addr), ap->location);
127
128         /* TODO: get device data and subscribe for events */
129 }
130
131
132 static void wps_er_ap_remove(struct wps_er *er, struct in_addr *addr)
133 {
134         struct wps_er_ap *prev = NULL, *ap = er->ap;
135
136         while (ap) {
137                 if (ap->addr.s_addr == addr->s_addr) {
138                         if (prev)
139                                 prev->next = ap->next;
140                         else
141                                 er->ap = ap->next;
142                         wps_er_ap_free(er, ap);
143                         return;
144                 }
145                 prev = ap;
146                 ap = ap->next;
147         }
148 }
149
150
151 static void wps_er_ap_remove_all(struct wps_er *er)
152 {
153         struct wps_er_ap *prev, *ap;
154
155         ap = er->ap;
156         er->ap = NULL;
157
158         while (ap) {
159                 prev = ap;
160                 ap = ap->next;
161                 wps_er_ap_free(er, prev);
162         }
163 }
164
165
166 static void wps_er_ssdp_rx(int sd, void *eloop_ctx, void *sock_ctx)
167 {
168         struct wps_er *er = eloop_ctx;
169         struct sockaddr_in addr; /* client address */
170         socklen_t addr_len;
171         int nread;
172         char buf[MULTICAST_MAX_READ], *pos, *pos2, *start;
173         int wfa = 0, byebye = 0;
174         int max_age = -1;
175         char *location = NULL;
176
177         addr_len = sizeof(addr);
178         nread = recvfrom(sd, buf, sizeof(buf) - 1, 0,
179                          (struct sockaddr *) &addr, &addr_len);
180         if (nread <= 0)
181                 return;
182         buf[nread] = '\0';
183
184         wpa_printf(MSG_DEBUG, "WPS ER: Received SSDP from %s",
185                    inet_ntoa(addr.sin_addr));
186         wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Received SSDP contents",
187                           (u8 *) buf, nread);
188
189         if (sd == er->multicast_sd) {
190                 /* Reply to M-SEARCH */
191                 if (os_strncmp(buf, "HTTP/1.1 200 OK", 15) != 0)
192                         return; /* unexpected response header */
193         } else {
194                 /* Unsolicited message (likely NOTIFY or M-SEARCH) */
195                 if (os_strncmp(buf, "NOTIFY ", 7) != 0)
196                         return; /* only process notifications */
197         }
198
199         for (start = buf; start && *start; start = pos) {
200                 pos = os_strchr(start, '\n');
201                 if (pos) {
202                         if (pos[-1] == '\r')
203                                 pos[-1] = '\0';
204                         *pos++ = '\0';
205                 }
206                 if (os_strstr(start, "schemas-wifialliance-org:device:"
207                               "WFADevice:1"))
208                         wfa = 1;
209                 if (os_strstr(start, "schemas-wifialliance-org:service:"
210                               "WFAWLANConfig:1"))
211                         wfa = 1;
212                 if (os_strncasecmp(start, "LOCATION:", 9) == 0) {
213                         start += 9;
214                         while (*start == ' ')
215                                 start++;
216                         location = start;
217                 } else if (os_strncasecmp(start, "NTS:", 4) == 0) {
218                         if (os_strstr(start, "ssdp:byebye"))
219                                 byebye = 1;
220                 } else if (os_strncasecmp(start, "CACHE-CONTROL:", 14) == 0) {
221                         start += 9;
222                         while (*start == ' ')
223                                 start++;
224                         pos2 = os_strstr(start, "max-age=");
225                         if (pos2 == NULL)
226                                 continue;
227                         pos2 += 8;
228                         max_age = atoi(pos2);
229                 }
230         }
231
232         if (!wfa)
233                 return; /* Not WPS advertisement/reply */
234
235         if (byebye) {
236                 wps_er_ap_remove(er, &addr.sin_addr);
237                 return;
238         }
239
240         if (!location)
241                 return; /* Unknown location */
242
243         if (max_age < 1)
244                 return; /* No max-age reported */
245
246         wpa_printf(MSG_DEBUG, "WPS ER: AP discovered: %s "
247                    "(packet source: %s  max-age: %d)",
248                    location, inet_ntoa(addr.sin_addr), max_age);
249
250         wps_er_ap_add(er, &addr.sin_addr, location, max_age);
251 }
252
253
254 static void wps_er_send_ssdp_msearch(struct wps_er *er)
255 {
256         struct wpabuf *msg;
257         struct sockaddr_in dest;
258
259         msg = wpabuf_alloc(500);
260         if (msg == NULL)
261                 return;
262
263         wpabuf_put_str(msg,
264                        "M-SEARCH * HTTP/1.1\r\n"
265                        "HOST: 239.255.255.250:1900\r\n"
266                        "MAN: \"ssdp:discover\"\r\n"
267                        "MX: 3\r\n"
268                        "ST: urn:schemas-wifialliance-org:device:WFADevice:1"
269                        "\r\n"
270                        "\r\n");
271
272         os_memset(&dest, 0, sizeof(dest));
273         dest.sin_family = AF_INET;
274         dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
275         dest.sin_port = htons(UPNP_MULTICAST_PORT);
276
277         if (sendto(er->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
278                    (struct sockaddr *) &dest, sizeof(dest)) < 0)
279                 wpa_printf(MSG_DEBUG, "WPS ER: M-SEARCH sendto failed: "
280                            "%d (%s)", errno, strerror(errno));
281
282         wpabuf_free(msg);
283 }
284
285
286 struct wps_er *
287 wps_er_init(struct wps_context *wps, const char *ifname)
288 {
289         struct wps_er *er;
290         struct wps_registrar_config rcfg;
291
292         er = os_zalloc(sizeof(*er));
293         if (er == NULL)
294                 return NULL;
295
296         er->multicast_sd = -1;
297         er->ssdp_sd = -1;
298
299         os_strlcpy(er->ifname, ifname, sizeof(er->ifname));
300         os_memset(&rcfg, 0, sizeof(rcfg));
301         rcfg.pin_needed_cb = wps_er_pin_needed_cb;
302         rcfg.cb_ctx = er;
303
304         er->reg = wps_registrar_init(wps, &rcfg);
305         if (er->reg == NULL) {
306                 wps_er_deinit(er);
307                 return NULL;
308         }
309
310         if (get_netif_info(ifname,
311                            &er->ip_addr, &er->ip_addr_text,
312                            er->mac_addr, &er->mac_addr_text)) {
313                 wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
314                            "for %s. Does it have IP address?", ifname);
315                 wps_er_deinit(er);
316                 return NULL;
317         }
318
319         if (add_ssdp_network(ifname)) {
320                 wps_er_deinit(er);
321                 return NULL;
322         }
323
324         er->multicast_sd = ssdp_open_multicast_sock(er->ip_addr);
325         if (er->multicast_sd < 0) {
326                 wps_er_deinit(er);
327                 return NULL;
328         }
329
330         er->ssdp_sd = ssdp_listener_open();
331         if (er->ssdp_sd < 0) {
332                 wps_er_deinit(er);
333                 return NULL;
334         }
335         if (eloop_register_sock(er->multicast_sd, EVENT_TYPE_READ,
336                                 wps_er_ssdp_rx, er, NULL) ||
337             eloop_register_sock(er->ssdp_sd, EVENT_TYPE_READ,
338                                 wps_er_ssdp_rx, er, NULL)) {
339                 wps_er_deinit(er);
340                 return NULL;
341         }
342
343         wpa_printf(MSG_DEBUG, "WPS ER: Start (ifname=%s ip_addr=%s "
344                    "mac_addr=%s)",
345                    er->ifname, er->ip_addr_text, er->mac_addr_text);
346
347         wps_er_send_ssdp_msearch(er);
348
349         return er;
350 }
351
352
353 void wps_er_deinit(struct wps_er *er)
354 {
355         if (er == NULL)
356                 return;
357         wps_er_ap_remove_all(er);
358         if (er->multicast_sd >= 0) {
359                 eloop_unregister_sock(er->multicast_sd, EVENT_TYPE_READ);
360                 close(er->multicast_sd);
361         }
362         if (er->ssdp_sd >= 0) {
363                 eloop_unregister_sock(er->ssdp_sd, EVENT_TYPE_READ);
364                 close(er->ssdp_sd);
365         }
366         wps_registrar_deinit(er->reg);
367         os_free(er->ip_addr_text);
368         os_free(er->mac_addr_text);
369         os_free(er);
370 }