More consistent function names
[oweals/nmrpflash.git] / nmrp.c
1 /**
2  * nmrpflash - Netgear Unbrick Utility
3  * Copyright (C) 2016 Joseph Lehner <joseph.c.lehner@gmail.com>
4  *
5  * nmrpflash 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 3 of the License, or
8  * (at your option) any later version.
9  *
10  * nmrpflash 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
16  * along with nmrpflash.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <stdio.h>
26 #include <time.h>
27 #include "nmrpd.h"
28
29 #define NMRP_HDR_LEN 6
30 #define NMRP_OPT_HDR_LEN 4
31 #define NMRP_MIN_PKT_LEN (sizeof(struct eth_hdr) +  NMRP_HDR_LEN)
32
33 #define ETH_P_NMRP 0x0912
34
35 #ifndef PACKED
36 #define PACKED __attribute__((__packed__))
37 #endif
38
39 #ifdef NMRPFLASH_WINDOWS
40 #define setenv(name, value, overwrite) SetEnvironmentVariable(name, value)
41 #endif
42
43 enum nmrp_code {
44         NMRP_C_NONE = 0,
45         NMRP_C_ADVERTISE = 1,
46         NMRP_C_CONF_REQ = 2,
47         NMRP_C_CONF_ACK = 3,
48         NMRP_C_CLOSE_REQ = 4,
49         NMRP_C_CLOSE_ACK = 5,
50         NMRP_C_KEEP_ALIVE_REQ = 6,
51         NMRP_C_KEEP_ALIVE_ACK = 7,
52         NMRP_C_TFTP_UL_REQ = 16
53 };
54
55 enum nmrp_opt_type {
56         NMRP_O_MAGIC_NO = 0x0001,
57         NMRP_O_DEV_IP = 0x0002,
58         NMRP_O_DEV_REGION = 0x0004,
59         NMRP_O_FW_UP = 0x0101,
60         NMRP_O_ST_UP = 0x0102,
61         NMRP_O_FILE_NAME = 0x0181
62 };
63
64 struct nmrp_opt {
65         uint16_t type;
66         uint16_t len;
67         char val[1];
68 } PACKED;
69
70 struct nmrp_msg {
71         uint16_t reserved;
72         uint8_t code;
73         uint8_t id;
74         uint16_t len;
75         char opts[44];
76 } PACKED;
77
78 struct nmrp_pkt {
79         struct eth_hdr eh;
80         struct nmrp_msg msg;
81 } PACKED;
82
83 static const char *msg_code_str(uint16_t code)
84 {
85 #define MSG_CODE(x) case NMRP_C_ ## x: return #x
86         static char buf[16];
87
88         switch (code) {
89                 MSG_CODE(ADVERTISE);
90                 MSG_CODE(CONF_REQ);
91                 MSG_CODE(CONF_ACK);
92                 MSG_CODE(CLOSE_REQ);
93                 MSG_CODE(CLOSE_ACK);
94                 MSG_CODE(KEEP_ALIVE_REQ);
95                 MSG_CODE(KEEP_ALIVE_ACK);
96                 MSG_CODE(TFTP_UL_REQ);
97                 default:
98                         snprintf(buf, sizeof(buf), "%04x", ntohs(code));
99                         return buf;
100         }
101 #undef MSG_CODE
102 }
103
104 static uint16_t to_region_code(const char *region)
105 {
106 #define REGION_CODE(r, c) if (!strcasecmp(region, r)) return htons(c)
107         REGION_CODE("NA", 0x0001);
108         REGION_CODE("WW", 0x0002);
109         REGION_CODE("GR", 0x0003);
110         REGION_CODE("PR", 0x0004);
111         REGION_CODE("RU", 0x0005);
112         REGION_CODE("BZ", 0x0006);
113         REGION_CODE("IN", 0x0007);
114         REGION_CODE("KO", 0x0008);
115         REGION_CODE("JP", 0x0009);
116 #undef REGION_CODE
117         return 0;
118 }
119
120 static void msg_dump(struct nmrp_msg *msg)
121 {
122         int rem;
123
124         fprintf(stderr, "res=0x%04x, code=0x%02x, id=0x%02x, len=%u",
125                         ntohs(msg->reserved), msg->code, msg->id, ntohs(msg->len));
126
127         rem = ntohs(msg->len) - NMRP_HDR_LEN;
128         fprintf(stderr, "%s\n", rem ? "" : " (no opts)");
129 }
130
131 static void *msg_opt(struct nmrp_msg *msg, uint16_t type, uint16_t* len)
132 {
133         struct nmrp_opt* opt = (struct nmrp_opt*)msg->opts;
134         size_t rem = ntohs(msg->len) - NMRP_HDR_LEN;
135         uint16_t olen;
136
137         do {
138                 olen = ntohs(opt->len);
139                 if (olen < NMRP_OPT_HDR_LEN || olen > rem) {
140                         break;
141                 }
142
143                 if (ntohs(opt->type) == type) {
144                         if (len) {
145                                 *len = olen;
146                         }
147
148                         return opt->val;
149                 }
150
151                 rem -= olen;
152         } while (rem);
153
154         return NULL;
155 }
156
157 static char *msg_filename(struct nmrp_msg *msg)
158 {
159         static char buf[256];
160         uint16_t len;
161         char *p = msg_opt(msg, NMRP_O_FILE_NAME, &len);
162         if (p) {
163                 len = MIN(sizeof(buf) - 1, len);
164                 memcpy(buf, p, len);
165                 buf[len] = '\0';
166                 return buf;
167         }
168
169         return NULL;
170 }
171
172 static inline void msg_init(struct nmrp_msg *msg, uint16_t code)
173 {
174         memset(msg, 0, sizeof(*msg));
175         msg->len = htons(NMRP_HDR_LEN);
176         msg->code = code;
177 }
178
179 static char *msg_mkopt(struct nmrp_msg *msg, char *p, uint16_t type, const void *val, size_t len)
180 {
181         struct nmrp_opt* opt = (struct nmrp_opt*)p;
182
183         len &= 0xffff;
184
185         msg->len = ntohs(msg->len);
186
187         if ((msg->len + len > sizeof(*msg))) {
188                 fprintf(stderr, "Error: invalid option - this is a bug\n");
189                 exit(1);
190         }
191
192         opt->type = htons(type);
193         opt->len = NMRP_OPT_HDR_LEN + len;
194
195         if (val) {
196                 memcpy(opt->val, val, len);
197         }
198
199         msg->len += opt->len;
200         p += opt->len;
201
202         msg->len = htons(msg->len);
203         opt->len = htons(opt->len);
204
205         return p;
206 }
207
208 static void msg_mkadvertise(struct nmrp_msg *msg, const char *magic)
209 {
210         msg_init(msg, NMRP_C_ADVERTISE);
211         msg_mkopt(msg, msg->opts, NMRP_O_MAGIC_NO, magic, strlen(magic));
212 }
213
214 static void msg_mkconfack(struct nmrp_msg *msg, uint32_t ipaddr, uint32_t ipmask, uint16_t region)
215 {
216         char *p;
217         struct {
218                 uint32_t addr;
219                 uint32_t mask;
220         } PACKED ip = {
221                 .addr = ipaddr,
222                 .mask = ipmask
223         };
224
225         msg_init(msg, NMRP_C_CONF_ACK);
226         p = msg_mkopt(msg, msg->opts, NMRP_O_DEV_IP, &ip, 8);
227         p = msg_mkopt(msg, p, NMRP_O_FW_UP, NULL, 0);
228
229 #ifdef NMRPFLASH_SET_REGION
230         if (region) {
231                 p = msg_mkopt(msg, p, NMRP_O_DEV_REGION, &region, 2);
232         }
233 #endif
234 }
235
236 #ifdef NMRPFLASH_FUZZ
237 #define NMRP_INITIAL_TIMEOUT 0
238 #define ethsock_create(a, b) ((struct ethsock*)1)
239 #define ethsock_get_hwaddr(a) ethsock_get_hwaddr_fake(a)
240 #define ethsock_recv(sock, buf, len) read(STDIN_FILENO, buf, len)
241 #define ethsock_send(a, b, c) (0)
242 #define ethsock_set_timeout(a, b) (0)
243 #define ethsock_ip_add(a, b, c, d) (0)
244 #define ethsock_ip_del(a, b) (0)
245 #define ethsock_close(a) (0)
246 #define tftp_put(a) (0)
247
248 static uint8_t *ethsock_get_hwaddr_fake(struct ethsock* sock)
249 {
250         static uint8_t hwaddr[6] = { 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa };
251         return hwaddr;
252 }
253 #else
254 #define NMRP_INITIAL_TIMEOUT 60
255 #endif
256
257 static int pkt_send(struct ethsock *sock, struct nmrp_pkt *pkt)
258 {
259         return ethsock_send(sock, pkt, sizeof(*pkt));
260 }
261
262 static int pkt_recv(struct ethsock *sock, struct nmrp_pkt *pkt)
263 {
264         ssize_t bytes, len;
265
266         memset(pkt, 0, sizeof(*pkt));
267         bytes = ethsock_recv(sock, pkt, sizeof(*pkt));
268         if (bytes < 0) {
269                 return 1;
270         } else if (!bytes) {
271                 return 2;
272         } else if (bytes < NMRP_MIN_PKT_LEN) {
273                 fprintf(stderr, "Short packet (%d bytes)\n", (int)bytes);
274                 return 1;
275         }
276
277         len = ntohs(pkt->msg.len) + sizeof(pkt->eh);
278
279         if (bytes < len) {
280                 fprintf(stderr, "Short packet (expected %d, got %d).\n",
281                                 (int)len, (int)bytes);
282                 return 1;
283         } else if (bytes > sizeof(pkt->msg) + sizeof(pkt->eh)) {
284                 fprintf(stderr, "Packet size exceeds maximum (got %d).\n",
285                                 (int)bytes);
286         }
287
288         return 0;
289 }
290
291 static int mac_parse(const char *str, uint8_t *hwaddr)
292 {
293         int i;
294         unsigned data[6];
295
296         sscanf(str, "%02x:%02x:%02x:%02x:%02x:%02x%n",
297                         data, data + 1, data + 2, data + 3, data + 4, data + 5, &i);
298
299         if (i == strlen(str)) {
300                 for (i = 0; i != 6; ++i) {
301                         if (data[i] > 255) {
302                                 break;
303                         }
304
305                         hwaddr[i] = data[i] & 0xff;
306                 }
307
308                 if (i == 6) {
309                         return 1;
310                 }
311         }
312         return 0;
313 }
314
315 struct is_valid_ip_arg
316 {
317         struct in_addr *ipaddr;
318         struct in_addr *ipmask;
319         int result;
320 };
321
322 static int is_valid_ip_cb(struct ethsock_ip_callback_args *args)
323 {
324 #define SUBNET(x) ((x)->ipaddr->s_addr & (x)->ipmask->s_addr)
325         struct is_valid_ip_arg *arg = args->arg;
326         if (SUBNET(args) == SUBNET(arg)) {
327                 arg->result = args->ipaddr->s_addr != arg->ipaddr->s_addr;
328                 return 0;
329         }
330
331         return 1;
332 #undef SUBNET
333 }
334
335 static int is_valid_ip(struct ethsock *sock, struct in_addr *ipaddr,
336                 struct in_addr *ipmask)
337 {
338         int status;
339         struct is_valid_ip_arg arg = {
340                 .ipaddr = ipaddr,
341                 .ipmask = ipmask,
342                 .result = 0
343         };
344
345         status = ethsock_for_each_ip(sock, is_valid_ip_cb, &arg);
346         return status < 0 ? status : arg.result;
347 }
348
349 static void sigh(int sig)
350 {
351         g_interrupted = 1;
352 }
353
354 static const char *spinner = "\\|/-";
355
356 int nmrp_do(struct nmrpd_args *args)
357 {
358         struct nmrp_pkt tx, rx;
359         uint8_t *src, dest[6];
360         uint16_t region;
361         char *filename;
362         time_t beg;
363         int i, status, ulreqs, expect, upload_ok, autoip;
364         struct ethsock *sock;
365         struct ethsock_ip_undo *ip_undo = NULL;
366         struct ethsock_arp_undo *arp_undo = NULL;
367         uint32_t intf_addr;
368         void (*sigh_orig)(int);
369         struct {
370                 struct in_addr addr;
371                 struct in_addr mask;
372         } PACKED ipconf;
373
374         if (args->op != NMRP_UPLOAD_FW) {
375                 fprintf(stderr, "Operation not implemented.\n");
376                 return 1;
377         }
378
379         if (!mac_parse(args->mac, dest)) {
380                 fprintf(stderr, "Invalid MAC address '%s'.\n", args->mac);
381                 return 1;
382         }
383
384         ipconf.mask.s_addr = inet_addr(args->ipmask);
385         if (ipconf.mask.s_addr == INADDR_NONE
386                         || netmask(bitcount(ipconf.mask.s_addr)) != ipconf.mask.s_addr) {
387                 fprintf(stderr, "Invalid subnet mask '%s'.\n", args->ipmask);
388                 return 1;
389         }
390
391         if (!args->ipaddr) {
392                 autoip = true;
393                 /* The MAC of the device that was used to test this utility starts
394                  * with a4:2b:8c, hence 164 (0xa4) and 183 (0x2b + 0x8c)
395                  */
396                 args->ipaddr = "10.164.183.252";
397
398                 if (!args->ipaddr_intf) {
399                         args->ipaddr_intf = "10.164.183.253";
400                 }
401         } else if (args->ipaddr_intf) {
402                 autoip = true;
403         } else {
404                 autoip = false;
405         }
406
407         if ((ipconf.addr.s_addr = inet_addr(args->ipaddr)) == INADDR_NONE) {
408                 fprintf(stderr, "Invalid IP address '%s'.\n", args->ipaddr);
409                 return 1;
410         }
411
412         if (args->ipaddr_intf && (intf_addr = inet_addr(args->ipaddr_intf)) == INADDR_NONE) {
413                 fprintf(stderr, "Invalid IP address '%s'.\n", args->ipaddr_intf);
414                 return 1;
415         }
416
417         if (args->file_local && strcmp(args->file_local, "-") && access(args->file_local, R_OK) == -1) {
418                 fprintf(stderr, "Error accessing file '%s'.\n", args->file_local);
419                 return 1;
420         }
421
422         if (args->file_remote) {
423                 if (!tftp_is_valid_filename(args->file_remote)) {
424                         fprintf(stderr, "Invalid remote filename '%s'.\n",
425                                         args->file_remote);
426                         return 1;
427                 }
428         }
429
430         if (args->region) {
431                 region = to_region_code(args->region);
432                 if (!region) {
433                         fprintf(stderr, "Invalid region code '%s'.\n", args->region);
434                         return 1;
435                 }
436         } else {
437                 region = 0;
438         }
439
440         status = 1;
441
442         sock = ethsock_create(args->intf, ETH_P_NMRP);
443         if (!sock) {
444                 return 1;
445         }
446
447         sigh_orig = signal(SIGINT, sigh);
448
449         if (!autoip) {
450                 status = is_valid_ip(sock, &ipconf.addr, &ipconf.mask);
451                 if (status <= 0) {
452                         if (!status) {
453                                 fprintf(stderr, "Address %s/%s cannot be used on interface %s.\n",
454                                                 args->ipaddr, args->ipmask, args->intf);
455                         }
456                         goto out;
457                 }
458         } else {
459                 if (verbosity) {
460                         printf("Adding %s to interface %s.\n", args->ipaddr_intf, args->intf);
461                 }
462
463                 if (ethsock_ip_add(sock, intf_addr, ipconf.mask.s_addr, &ip_undo) != 0) {
464                         goto out;
465                 }
466         }
467
468         if (ethsock_set_timeout(sock, args->rx_timeout)) {
469                 goto out;
470         }
471
472         src = ethsock_get_hwaddr(sock);
473         if (!src) {
474                 goto out;
475         }
476
477         memcpy(tx.eh.ether_shost, src, 6);
478         memcpy(tx.eh.ether_dhost, dest, 6);
479         tx.eh.ether_type = htons(ETH_P_NMRP);
480
481         msg_mkadvertise(&tx.msg, "NTGR");
482
483         i = 0;
484         upload_ok = 0;
485         beg = time_monotonic();
486
487         while (!g_interrupted) {
488                 printf("\rAdvertising NMRP server on %s ... %c",
489                                 args->intf, spinner[i]);
490                 fflush(stdout);
491                 i = (i + 1) & 3;
492
493                 if (pkt_send(sock, &tx) < 0) {
494                         xperror("sendto");
495                         goto out;
496                 }
497
498                 status = pkt_recv(sock, &rx);
499                 if (status == 0 && memcmp(rx.eh.ether_dhost, src, 6) == 0) {
500                         break;
501                 } else if (status == 1) {
502                         goto out;
503                 } else {
504                         /* because we don't want nmrpflash's exit status to be zero */
505                         status = 1;
506                         if ((time_monotonic() - beg) >= NMRP_INITIAL_TIMEOUT) {
507                                 printf("\nNo response after 60 seconds. Bailing out.\n");
508                                 goto out;
509                         }
510                 }
511         }
512
513         printf("\n");
514
515         expect = NMRP_C_CONF_REQ;
516         ulreqs = 0;
517
518         while (!g_interrupted) {
519                 if (expect != NMRP_C_NONE && rx.msg.code != expect) {
520                         fprintf(stderr, "Received %s while waiting for %s!\n",
521                                         msg_code_str(rx.msg.code), msg_code_str(expect));
522                 }
523
524                 msg_init(&tx.msg, NMRP_C_NONE);
525
526                 status = 1;
527
528                 switch (rx.msg.code) {
529                         case NMRP_C_ADVERTISE:
530                                 printf("Received NMRP advertisement from %s.\n",
531                                                 mac_to_str(rx.eh.ether_shost));
532                                 status = 1;
533                                 goto out;
534                         case NMRP_C_CONF_REQ:
535                                 msg_mkconfack(&tx.msg, ipconf.addr.s_addr, ipconf.mask.s_addr, region);
536                                 expect = NMRP_C_TFTP_UL_REQ;
537
538                                 printf("Received configuration request from %s.\n",
539                                                 mac_to_str(rx.eh.ether_shost));
540
541                                 memcpy(tx.eh.ether_dhost, rx.eh.ether_shost, 6);
542
543                                 printf("Sending configuration: %s, netmask %s.\n",
544                                                 args->ipaddr, args->ipmask);
545
546                                 if (ethsock_arp_add(sock, rx.eh.ether_shost, ipconf.addr.s_addr, &arp_undo) != 0) {
547                                         goto out;
548                                 }
549
550                                 break;
551                         case NMRP_C_TFTP_UL_REQ:
552                                 if (!upload_ok) {
553                                         if (++ulreqs > 5) {
554                                                 printf("Bailing out after %d upload requests.\n",
555                                                                 ulreqs);
556                                                 tx.msg.code = NMRP_C_CLOSE_REQ;
557                                                 break;
558                                         }
559                                 } else {
560                                         if (verbosity) {
561                                                 printf("Ignoring extra upload request.\n");
562                                         }
563                                         ethsock_set_timeout(sock, args->ul_timeout);
564                                         tx.msg.code = NMRP_C_KEEP_ALIVE_REQ;
565                                         break;
566                                 }
567
568                                 filename = msg_filename(&rx.msg);
569                                 if (filename) {
570                                         if (!args->file_remote) {
571                                                 args->file_remote = filename;
572                                         }
573                                         printf("Received upload request: filename '%s'.\n", filename);
574                                 } else if (!args->file_remote) {
575                                         args->file_remote = args->file_local;
576                                         printf("Received upload request with empty filename.\n");
577                                 }
578
579                                 status = 0;
580
581                                 if (args->tftpcmd) {
582                                         printf("Executing '%s' ... \n", args->tftpcmd);
583                                         setenv("IP", inet_ntoa(ipconf.addr), 1);
584                                         setenv("PORT", lltostr(args->port, 10), 1);
585                                         setenv("MAC", mac_to_str(rx.eh.ether_shost), 1);
586                                         setenv("NETMASK", inet_ntoa(ipconf.mask), 1);
587                                         //setenv("FILENAME", args->file_remote ? args->file_remote : "", 1);
588                                         status = system(args->tftpcmd);
589                                 }
590
591                                 if (!status && args->file_local) {
592                                         if (!autoip) {
593                                                 status = is_valid_ip(sock, &ipconf.addr, &ipconf.mask);
594                                                 if (status < 0) {
595                                                         goto out;
596                                                 } else if (!status) {
597                                                         printf("IP address of %s has changed. Please assign a "
598                                                                         "static ip to the interface.\n", args->intf);
599                                                         tx.msg.code = NMRP_C_CLOSE_REQ;
600                                                         break;
601                                                 }
602                                         }
603
604                                         if (verbosity) {
605                                                 printf("Using remote filename '%s'.\n",
606                                                                 args->file_remote);
607                                         }
608
609                                         if (!strcmp(args->file_local, "-")) {
610                                                 printf("Uploading from stdin ... ");
611                                         } else {
612                                                 printf("Uploading %s ... ", leafname(args->file_local));
613                                         }
614                                         fflush(stdout);
615                                         if (!(status = tftp_put(args))) {
616                                                 printf("OK\n");
617                                         }
618
619                                 }
620
621                                 if (!status) {
622                                         printf("Waiting for remote to respond.\n");
623                                         upload_ok = 1;
624                                         ethsock_set_timeout(sock, args->ul_timeout);
625                                         tx.msg.code = NMRP_C_KEEP_ALIVE_REQ;
626                                         expect = NMRP_C_NONE;
627                                 } else if (status == -2) {
628                                         expect = NMRP_C_TFTP_UL_REQ;
629                                 } else {
630                                         goto out;
631                                 }
632
633                                 break;
634                         case NMRP_C_KEEP_ALIVE_REQ:
635                                 tx.msg.code = NMRP_C_KEEP_ALIVE_ACK;
636                                 ethsock_set_timeout(sock, args->ul_timeout);
637                                 printf("Received keep-alive request.\n");
638                                 break;
639                         case NMRP_C_CLOSE_REQ:
640                                 tx.msg.code = NMRP_C_CLOSE_ACK;
641                                 break;
642                         case NMRP_C_CLOSE_ACK:
643                                 status = 0;
644                                 goto out;
645                         default:
646                                 fprintf(stderr, "Unknown message code 0x%02x!\n",
647                                                 rx.msg.code);
648                                 msg_dump(&rx.msg);
649                 }
650
651                 if (tx.msg.code != NMRP_C_NONE) {
652                         if (pkt_send(sock, &tx) < 0) {
653                                 xperror("sendto");
654                                 goto out;
655                         }
656
657                         if (tx.msg.code == NMRP_C_CLOSE_REQ) {
658                                 goto out;
659                         }
660                 }
661
662                 if (rx.msg.code == NMRP_C_CLOSE_REQ) {
663                         printf("Remote finished. Closing connection.\n");
664                         break;
665                 }
666
667                 status = pkt_recv(sock, &rx);
668                 if (status) {
669                         if (status == 2) {
670                                 fprintf(stderr, "Timeout while waiting for %s.\n",
671                                                 msg_code_str(expect));
672                         }
673                         goto out;
674                 }
675
676                 ethsock_set_timeout(sock, args->rx_timeout);
677
678         }
679
680         if (!g_interrupted) {
681                 status = 0;
682                 if (ulreqs) {
683                         printf("Reboot your device now.\n");
684                 } else {
685                         printf("No upload request received.\n");
686                 }
687         }
688
689 out:
690         signal(SIGINT, sigh_orig);
691         ethsock_arp_del(sock, &arp_undo);
692         ethsock_ip_del(sock, &ip_undo);
693         ethsock_close(sock);
694         return status;
695 }