Update README.md
[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                 opt = (struct nmrp_opt*)(((char *)opt) + olen);
152                 rem -= olen;
153         } while (rem);
154
155         return NULL;
156 }
157
158 static char *msg_filename(struct nmrp_msg *msg)
159 {
160         static char buf[256];
161         uint16_t len;
162         char *p = msg_opt(msg, NMRP_O_FILE_NAME, &len);
163         if (p) {
164                 len = MIN(sizeof(buf) - 1, len);
165                 memcpy(buf, p, len);
166                 buf[len] = '\0';
167                 return buf;
168         }
169
170         return NULL;
171 }
172
173 static inline void msg_init(struct nmrp_msg *msg, uint16_t code)
174 {
175         memset(msg, 0, sizeof(*msg));
176         msg->len = htons(NMRP_HDR_LEN);
177         msg->code = code;
178 }
179
180 static char *msg_mkopt(struct nmrp_msg *msg, char *p, uint16_t type, const void *val, size_t len)
181 {
182         struct nmrp_opt* opt = (struct nmrp_opt*)p;
183
184         len &= 0xffff;
185
186         msg->len = ntohs(msg->len);
187
188         if ((msg->len + len > sizeof(*msg))) {
189                 fprintf(stderr, "Error: invalid option - this is a bug\n");
190                 exit(1);
191         }
192
193         opt->type = htons(type);
194         opt->len = NMRP_OPT_HDR_LEN + len;
195
196         if (val) {
197                 memcpy(opt->val, val, len);
198         }
199
200         msg->len += opt->len;
201         p += opt->len;
202
203         msg->len = htons(msg->len);
204         opt->len = htons(opt->len);
205
206         return p;
207 }
208
209 static void msg_mkadvertise(struct nmrp_msg *msg, const char *magic)
210 {
211         msg_init(msg, NMRP_C_ADVERTISE);
212         msg_mkopt(msg, msg->opts, NMRP_O_MAGIC_NO, magic, strlen(magic));
213 }
214
215 static void msg_mkconfack(struct nmrp_msg *msg, uint32_t ipaddr, uint32_t ipmask, uint16_t region)
216 {
217         char *p;
218         uint32_t ip[2] = { ipaddr, ipmask };
219
220         msg_init(msg, NMRP_C_CONF_ACK);
221         p = msg_mkopt(msg, msg->opts, NMRP_O_DEV_IP, &ip, 8);
222         p = msg_mkopt(msg, p, NMRP_O_FW_UP, NULL, 0);
223
224 #ifdef NMRPFLASH_SET_REGION
225         if (region) {
226                 p = msg_mkopt(msg, p, NMRP_O_DEV_REGION, &region, 2);
227         }
228 #endif
229 }
230
231 #ifdef NMRPFLASH_FUZZ
232 #define NMRP_INITIAL_TIMEOUT 0
233 #define ethsock_create(a, b) ((struct ethsock*)1)
234 #define ethsock_get_hwaddr(a) ethsock_get_hwaddr_fake(a)
235 #define ethsock_recv(sock, buf, len) read(STDIN_FILENO, buf, len)
236 #define ethsock_send(a, b, c) (0)
237 #define ethsock_set_timeout(a, b) (0)
238 #define ethsock_arp_add(a, b, c, d) (0)
239 #define ethsock_arp_del(a, b) (0)
240 #define ethsock_ip_add(a, b, c, d) (0)
241 #define ethsock_ip_del(a, b) (0)
242 #define ethsock_close(a) (0)
243 #define tftp_put(a) (0)
244
245 static uint8_t *ethsock_get_hwaddr_fake(struct ethsock* sock)
246 {
247         static uint8_t hwaddr[6] = { 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa };
248         return hwaddr;
249 }
250 #else
251 #define NMRP_INITIAL_TIMEOUT 60
252 #endif
253
254 static int pkt_send(struct ethsock *sock, struct nmrp_pkt *pkt)
255 {
256         return ethsock_send(sock, pkt, sizeof(*pkt));
257 }
258
259 static int pkt_recv(struct ethsock *sock, struct nmrp_pkt *pkt)
260 {
261         ssize_t bytes, mlen;
262
263         memset(pkt, 0, sizeof(*pkt));
264         bytes = ethsock_recv(sock, pkt, sizeof(*pkt));
265         if (bytes < 0) {
266                 return 1;
267         } else if (!bytes) {
268                 return 2;
269         }
270
271         mlen = ntohs(pkt->msg.len);
272
273         if (bytes < (mlen + sizeof(pkt->eh))
274                         || bytes < NMRP_MIN_PKT_LEN
275                         || mlen < NMRP_HDR_LEN) {
276                 fprintf(stderr, "Short packet (%d raw, %d message)\n",
277                                 (int)bytes, (int)mlen);
278                 return 1;
279         } else if (mlen > sizeof(pkt->msg)) {
280                 printf("Truncating %d byte message.\n", (int)mlen);
281                 pkt->msg.len = htons(sizeof(pkt->msg));
282         }
283
284         return 0;
285 }
286
287 static int mac_parse(const char *str, uint8_t *hwaddr)
288 {
289         int i;
290         unsigned data[6];
291
292         sscanf(str, "%02x:%02x:%02x:%02x:%02x:%02x%n",
293                         data, data + 1, data + 2, data + 3, data + 4, data + 5, &i);
294
295         if (i == strlen(str)) {
296                 for (i = 0; i != 6; ++i) {
297                         if (data[i] > 255) {
298                                 break;
299                         }
300
301                         hwaddr[i] = data[i] & 0xff;
302                 }
303
304                 if (i == 6) {
305                         return 1;
306                 }
307         }
308         return 0;
309 }
310
311 struct is_valid_ip_arg
312 {
313         struct in_addr *ipaddr;
314         struct in_addr *ipmask;
315         int result;
316 };
317
318 static int is_valid_ip_cb(struct ethsock_ip_callback_args *args)
319 {
320 #define SUBNET(x) ((x)->ipaddr->s_addr & (x)->ipmask->s_addr)
321         struct is_valid_ip_arg *arg = args->arg;
322         if (SUBNET(args) == SUBNET(arg)) {
323                 arg->result = args->ipaddr->s_addr != arg->ipaddr->s_addr;
324                 return 0;
325         }
326
327         return 1;
328 #undef SUBNET
329 }
330
331 static int is_valid_ip(struct ethsock *sock, struct in_addr *ipaddr,
332                 struct in_addr *ipmask)
333 {
334         int status;
335         struct is_valid_ip_arg arg = {
336                 .ipaddr = ipaddr,
337                 .ipmask = ipmask,
338                 .result = 0
339         };
340
341         status = ethsock_for_each_ip(sock, is_valid_ip_cb, &arg);
342         return status < 0 ? status : arg.result;
343 }
344
345 static void sigh(int sig)
346 {
347         g_interrupted = 1;
348 }
349
350 static const char *spinner = "\\|/-";
351
352 int nmrp_do(struct nmrpd_args *args)
353 {
354         struct nmrp_pkt tx, rx;
355         uint8_t *src, dest[6];
356         uint16_t region;
357         char *filename;
358         time_t beg;
359         int i, status, ulreqs, expect, upload_ok, autoip;
360         struct ethsock *sock;
361         struct ethsock_ip_undo *ip_undo = NULL;
362         struct ethsock_arp_undo *arp_undo = NULL;
363         uint32_t intf_addr;
364         void (*sigh_orig)(int);
365         struct in_addr ipaddr;
366         struct in_addr ipmask;
367
368         if (args->op != NMRP_UPLOAD_FW) {
369                 fprintf(stderr, "Operation not implemented.\n");
370                 return 1;
371         }
372
373         if (!mac_parse(args->mac, dest)) {
374                 fprintf(stderr, "Invalid MAC address '%s'.\n", args->mac);
375                 return 1;
376         }
377
378         ipmask.s_addr = inet_addr(args->ipmask);
379         if (ipmask.s_addr == INADDR_NONE
380                         || netmask(bitcount(ipmask.s_addr)) != ipmask.s_addr) {
381                 fprintf(stderr, "Invalid subnet mask '%s'.\n", args->ipmask);
382                 return 1;
383         }
384
385         if (!args->ipaddr) {
386                 autoip = true;
387                 /* The MAC of the device that was used to test this utility starts
388                  * with a4:2b:8c, hence 164 (0xa4) and 183 (0x2b + 0x8c)
389                  */
390                 args->ipaddr = "10.164.183.252";
391
392                 if (!args->ipaddr_intf) {
393                         args->ipaddr_intf = "10.164.183.253";
394                 }
395         } else if (args->ipaddr_intf) {
396                 autoip = true;
397         } else {
398                 autoip = false;
399         }
400
401         if ((ipaddr.s_addr = inet_addr(args->ipaddr)) == INADDR_NONE) {
402                 fprintf(stderr, "Invalid IP address '%s'.\n", args->ipaddr);
403                 return 1;
404         }
405
406         if (args->ipaddr_intf && (intf_addr = inet_addr(args->ipaddr_intf)) == INADDR_NONE) {
407                 fprintf(stderr, "Invalid IP address '%s'.\n", args->ipaddr_intf);
408                 return 1;
409         }
410
411         if (args->file_local && strcmp(args->file_local, "-") && access(args->file_local, R_OK) == -1) {
412                 fprintf(stderr, "Error accessing file '%s'.\n", args->file_local);
413                 return 1;
414         }
415
416         if (args->file_remote) {
417                 if (!tftp_is_valid_filename(args->file_remote)) {
418                         fprintf(stderr, "Invalid remote filename '%s'.\n",
419                                         args->file_remote);
420                         return 1;
421                 }
422         }
423
424         if (args->region) {
425                 region = to_region_code(args->region);
426                 if (!region) {
427                         fprintf(stderr, "Invalid region code '%s'.\n", args->region);
428                         return 1;
429                 }
430         } else {
431                 region = 0;
432         }
433
434         status = 1;
435
436         sock = ethsock_create(args->intf, ETH_P_NMRP);
437         if (!sock) {
438                 return 1;
439         }
440
441         sigh_orig = signal(SIGINT, sigh);
442
443         if (!autoip) {
444                 status = is_valid_ip(sock, &ipaddr, &ipmask);
445                 if (status <= 0) {
446                         if (!status) {
447                                 fprintf(stderr, "Address %s/%s cannot be used on interface %s.\n",
448                                                 args->ipaddr, args->ipmask, args->intf);
449                         }
450                         goto out;
451                 }
452         } else {
453                 if (verbosity) {
454                         printf("Adding %s to interface %s.\n", args->ipaddr_intf, args->intf);
455                 }
456
457                 if (ethsock_ip_add(sock, intf_addr, ipmask.s_addr, &ip_undo) != 0) {
458                         goto out;
459                 }
460         }
461
462         if (ethsock_set_timeout(sock, args->rx_timeout)) {
463                 goto out;
464         }
465
466         src = ethsock_get_hwaddr(sock);
467         if (!src) {
468                 goto out;
469         }
470
471         memcpy(tx.eh.ether_shost, src, 6);
472         memcpy(tx.eh.ether_dhost, dest, 6);
473         tx.eh.ether_type = htons(ETH_P_NMRP);
474
475         msg_mkadvertise(&tx.msg, "NTGR");
476
477         i = 0;
478         upload_ok = 0;
479         beg = time_monotonic();
480
481         while (!g_interrupted) {
482                 printf("\rAdvertising NMRP server on %s ... %c",
483                                 args->intf, spinner[i]);
484                 fflush(stdout);
485                 i = (i + 1) & 3;
486
487                 if (pkt_send(sock, &tx) < 0) {
488                         xperror("sendto");
489                         goto out;
490                 }
491
492                 status = pkt_recv(sock, &rx);
493                 if (status == 0) {
494                         if (memcmp(rx.eh.ether_dhost, src, 6) == 0) {
495                                 break;
496                         } else if (verbosity) {
497                                 printf("\nIgnoring bogus response: %s -> %s.\n",
498                                                 mac_to_str(rx.eh.ether_shost),
499                                                 mac_to_str(rx.eh.ether_dhost));
500                         }
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, ipaddr.s_addr, ipmask.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, ipaddr.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 = leafname(args->file_local);
576                                         printf("Received upload request without filename.\n");
577                                 }
578
579                                 status = 0;
580
581                                 if (args->tftpcmd) {
582                                         printf("Executing '%s' ... \n", args->tftpcmd);
583                                         setenv("IP", inet_ntoa(ipaddr), 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(ipmask), 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, &ipaddr, &ipmask);
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 }