Fix memcmp() reading out of bounds in the tinc info command.
[tinc] / src / info.c
1 /*
2     info.c -- Show information about a node, subnet or address
3     Copyright (C) 2012-2017 Guus Sliepen <guus@tinc-vpn.org>
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 as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21
22 #include "control_common.h"
23 #include "subnet.h"
24 #include "tincctl.h"
25 #include "info.h"
26 #include "utils.h"
27
28 void logger(int level, int priority, const char *format, ...) {
29         (void)level;
30         (void)priority;
31         va_list ap;
32
33         va_start(ap, format);
34         vfprintf(stderr, format, ap);
35         va_end(ap);
36
37         fputc('\n', stderr);
38 }
39
40 char *strip_weight(char *netstr) {
41         size_t len = strlen(netstr);
42
43         if(len >= 3 && !strcmp(netstr + len - 3, "#10")) {
44                 netstr[len - 3] = 0;
45         }
46
47         return netstr;
48 }
49
50 static int info_node(int fd, const char *item) {
51         // Check the list of nodes
52         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_NODES, item);
53
54         bool found = false;
55         char line[4096];
56
57         char node[4096];
58         char id[4096];
59         char from[4096];
60         char to[4096];
61         char subnet[4096];
62         char host[4096];
63         char port[4096];
64         char via[4096];
65         char nexthop[4096];
66         int code, req, cipher, digest, maclength, compression, distance;
67         short int pmtu, minmtu, maxmtu;
68         unsigned int options;
69         union {
70                 node_status_t bits;
71                 uint32_t raw;
72         } status_union;
73         node_status_t status;
74         long int last_state_change;
75         int udp_ping_rtt;
76         uint64_t in_packets, in_bytes, out_packets, out_bytes;
77
78         while(recvline(fd, line, sizeof(line))) {
79                 int n = sscanf(line, "%d %d %4095s %4095s %4095s port %4095s %d %d %d %d %x %"PRIx32" %4095s %4095s %d %hd %hd %hd %ld %d %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, &code, &req, node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_union.raw, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change, &udp_ping_rtt, &in_packets, &in_bytes, &out_packets, &out_bytes);
80
81                 if(n == 2) {
82                         break;
83                 }
84
85                 if(n != 24) {
86                         fprintf(stderr, "Unable to parse node dump from tincd.\n");
87                         return 1;
88                 }
89
90                 if(!strcmp(node, item)) {
91                         found = true;
92                         break;
93                 }
94         }
95
96         if(!found) {
97                 fprintf(stderr, "Unknown node %s.\n", item);
98                 return 1;
99         }
100
101         while(recvline(fd, line, sizeof(line))) {
102                 if(sscanf(line, "%d %d %4095s", &code, &req, node) == 2) {
103                         break;
104                 }
105         }
106
107         printf("Node:         %s\n", item);
108         printf("Node ID:      %s\n", id);
109         printf("Address:      %s port %s\n", host, port);
110
111         char timestr[32] = "never";
112         time_t lsc_time = last_state_change;
113
114         if(last_state_change) {
115                 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&lsc_time));
116         }
117
118         status = status_union.bits;
119
120         if(status.reachable) {
121                 printf("Online since: %s\n", timestr);
122         } else {
123                 printf("Last seen:    %s\n", timestr);
124         }
125
126         printf("Status:      ");
127
128         if(status.validkey) {
129                 printf(" validkey");
130         }
131
132         if(status.visited) {
133                 printf(" visited");
134         }
135
136         if(status.reachable) {
137                 printf(" reachable");
138         }
139
140         if(status.indirect) {
141                 printf(" indirect");
142         }
143
144         if(status.sptps) {
145                 printf(" sptps");
146         }
147
148         if(status.udp_confirmed) {
149                 printf(" udp_confirmed");
150         }
151
152         printf("\n");
153
154         printf("Options:     ");
155
156         if(options & OPTION_INDIRECT) {
157                 printf(" indirect");
158         }
159
160         if(options & OPTION_TCPONLY) {
161                 printf(" tcponly");
162         }
163
164         if(options & OPTION_PMTU_DISCOVERY) {
165                 printf(" pmtu_discovery");
166         }
167
168         if(options & OPTION_CLAMP_MSS) {
169                 printf(" clamp_mss");
170         }
171
172         printf("\n");
173         printf("Protocol:     %d.%d\n", PROT_MAJOR, OPTION_VERSION(options));
174         printf("Reachability: ");
175
176         if(!strcmp(host, "MYSELF")) {
177                 printf("can reach itself\n");
178         } else if(!status.reachable) {
179                 printf("unreachable\n");
180         } else if(strcmp(via, item)) {
181                 printf("indirectly via %s\n", via);
182         } else if(!status.validkey) {
183                 printf("unknown\n");
184         } else if(minmtu > 0) {
185                 printf("directly with UDP\nPMTU:         %d\n", pmtu);
186
187                 if(udp_ping_rtt != -1) {
188                         printf("RTT:          %d.%03d\n", udp_ping_rtt / 1000, udp_ping_rtt % 1000);
189                 }
190         } else if(!strcmp(nexthop, item)) {
191                 printf("directly with TCP\n");
192         } else {
193                 printf("none, forwarded via %s\n", nexthop);
194         }
195
196         printf("RX:           %"PRIu64" packets  %"PRIu64" bytes\n", in_packets, in_bytes);
197         printf("TX:           %"PRIu64" packets  %"PRIu64" bytes\n", out_packets, out_bytes);
198
199         // List edges
200         printf("Edges:       ");
201         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_EDGES, item);
202
203         while(recvline(fd, line, sizeof(line))) {
204                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, from, to);
205
206                 if(n == 2) {
207                         break;
208                 }
209
210                 if(n != 4) {
211                         fprintf(stderr, "Unable to parse edge dump from tincd.\n%s\n", line);
212                         return 1;
213                 }
214
215                 if(!strcmp(from, item)) {
216                         printf(" %s", to);
217                 }
218         }
219
220         printf("\n");
221
222         // List subnets
223         printf("Subnets:     ");
224         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item);
225
226         while(recvline(fd, line, sizeof(line))) {
227                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, subnet, from);
228
229                 if(n == 2) {
230                         break;
231                 }
232
233                 if(n != 4) {
234                         fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
235                         return 1;
236                 }
237
238                 if(!strcmp(from, item)) {
239                         printf(" %s", strip_weight(subnet));
240                 }
241         }
242
243         printf("\n");
244
245         return 0;
246 }
247
248 static int info_subnet(int fd, const char *item) {
249         subnet_t subnet, find;
250
251         if(!str2net(&find, item)) {
252                 fprintf(stderr, "Could not parse subnet or address '%s'.\n", item);
253                 return 1;
254         }
255
256         bool address = !strchr(item, '/');
257         bool weight = strchr(item, '#');
258         bool found = false;
259
260         char line[4096];
261         char netstr[4096];
262         char owner[4096];
263
264         int code, req;
265
266         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item);
267
268         while(recvline(fd, line, sizeof(line))) {
269                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, netstr, owner);
270
271                 if(n == 2) {
272                         break;
273                 }
274
275                 if(n != 4 || !str2net(&subnet, netstr)) {
276                         fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
277                         return 1;
278                 }
279
280                 if(find.type != subnet.type) {
281                         continue;
282                 }
283
284                 if(weight) {
285                         if(find.weight != subnet.weight) {
286                                 continue;
287                         }
288                 }
289
290                 if(find.type == SUBNET_IPV4) {
291                         if(address) {
292                                 if(maskcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, subnet.net.ipv4.prefixlength)) {
293                                         continue;
294                                 }
295                         } else {
296                                 if(find.net.ipv4.prefixlength != subnet.net.ipv4.prefixlength) {
297                                         continue;
298                                 }
299
300                                 if(memcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, sizeof(subnet.net.ipv4.address))) {
301                                         continue;
302                                 }
303                         }
304                 } else if(find.type == SUBNET_IPV6) {
305                         if(address) {
306                                 if(maskcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, subnet.net.ipv6.prefixlength)) {
307                                         continue;
308                                 }
309                         } else {
310                                 if(find.net.ipv6.prefixlength != subnet.net.ipv6.prefixlength) {
311                                         continue;
312                                 }
313
314                                 if(memcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, sizeof(subnet.net.ipv6.address))) {
315                                         continue;
316                                 }
317                         }
318                 }
319
320                 if(find.type == SUBNET_MAC) {
321                         if(memcmp(&find.net.mac.address, &subnet.net.mac.address, sizeof(subnet.net.mac.address))) {
322                                 continue;
323                         }
324                 }
325
326                 found = true;
327                 printf("Subnet: %s\n", strip_weight(netstr));
328                 printf("Owner:  %s\n", owner);
329         }
330
331         if(!found) {
332                 if(address) {
333                         fprintf(stderr, "Unknown address %s.\n", item);
334                 } else {
335                         fprintf(stderr, "Unknown subnet %s.\n", item);
336                 }
337
338                 return 1;
339         }
340
341         return 0;
342 }
343
344 int info(int fd, const char *item) {
345         if(check_id(item)) {
346                 return info_node(fd, item);
347         }
348
349         if(strchr(item, '.') || strchr(item, ':')) {
350                 return info_subnet(fd, item);
351         }
352
353         fprintf(stderr, "Argument is not a node name, subnet or address.\n");
354         return 1;
355 }