]> git.decadent.org.uk Git - ap-utils.git/blob - src/ap-tftp.c
Update config.{sub,guess} in the right place at build time - closes: #534825
[ap-utils.git] / src / ap-tftp.c
1 /* ------------------------------------------------------------------------- */
2 /* ap-tftp.c                                                                 */
3 /*                                                                           */
4 /* Version: 1.1                                                              */
5 /*                                                                           */
6 /* Copyright (C) 2004-2005 Jan Rafaj <jr-aputils at cedric dot unob dot cz>  */
7 /*                                                                           */
8 /* A simple tftp client for upgrading ATMEL AT76C510 WiSOC-based APs.        */
9 /* Supports ATMEL+INTERSIL boards (1.4x.y firmware) and ATMEL+RFMD boards    */
10 /* (0.x.y.z firmware).                                                       */
11 /* Modelled around TELLUS (GEMTEK/ATMEL OEM) and SMARTBRIDGES TFTP clients   */
12 /* functionality.                                                            */
13 /* Tested with TELLUS, D-Link and SmartBridges hardware.                     */
14 /* This program is part of AP-UTILS project (http://ap-utils.polesye.net)    */
15 /*                                                                           */
16 /* Loosely based on a simple tftp client for busybox.                        */
17 /* Tries to follow RFC1350.                                                  */
18 /* Only "octet" mode and "put" method supported.                             */
19 /*                                                                           */
20 /* Not implemented:                                                          */
21 /* - uploading of OEM (default) settings                                     */
22 /* - uploading of PATCH code                                                 */
23 /*                                                                           */
24 /* Code based on:                                                            */
25 /*                                                                           */
26 /* bb tftp: Copyright (C) 2001 Magnus Damm <damm@opensource.se>              */
27 /*                                                                           */
28 /* atftp:   Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca>  */
29 /*                             and Remi Lefebvre <remi@debian.org>           */
30 /*                                                                           */
31 /* utftp:   Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>                        */
32 /*                                                                           */
33 /* This program is free software; you can redistribute it and/or modify      */
34 /* it under the terms of the GNU General Public License as published by      */
35 /* the Free Software Foundation; either version 2 of the License, or         */
36 /* (at your option) any later version.                                       */
37 /*                                                                           */
38 /* This program is distributed in the hope that it will be useful,           */
39 /* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
40 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU          */
41 /* General Public License for more details.                                  */
42 /*                                                                           */
43 /* You should have received a copy of the GNU General Public License         */
44 /* along with this program; if not, write to the Free Software               */
45 /* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
46 /*                                                                           */
47 /* ------------------------------------------------------------------------- */
48
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <sys/types.h>
53 #include <sys/socket.h>
54 #include <sys/time.h>
55 #include <sys/stat.h>
56 #include <netdb.h>
57 #include <netinet/in.h>
58 #include <arpa/inet.h>
59 #include <unistd.h>
60 #include <fcntl.h>
61 #include "ap-utils.h"
62
63 #ifdef HAVE_GETTEXT
64 /* GNU gettext stuff*/
65 #include <locale.h>
66 #include <libgnuintl.h>
67 #define _(String) gettext (String)
68 #else
69 #define _(String) (String)
70 #endif
71
72 #define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, do not change */
73 #define TFTP_TIMEOUT 2             /* # seconds waiting for single response */
74 #define TFTP_RETRIES 5             /* # retries with TFTP_TIMEOUT */
75 #undef FEATURE_TFTP_DEBUG
76
77 /* opcodes we support */
78 #define TFTP_WRQ   2
79 #define TFTP_DATA  3
80 #define TFTP_ACK   4
81 #define TFTP_ERROR 5
82
83 #ifdef FEATURE_TFTP_DEBUG
84 #define YES _("yes")
85 #define NO _("no")
86 #endif
87 #define ERR_READFW _("Error while read()ing firmware file")
88
89
90 /* This is necessary becouse of linkage to libap.a */
91 short ap_type = ATMEL410;
92 char *community = NULL;
93 struct in_addr ap_ip;
94
95 int tftp(int ip, const char *remotefile, int localfd, const char *wopt,
96          int woptlen, int maxdlen)
97 {
98 /* known errors server may respond with */
99 char *tftp_error_msg[8] = {
100     _("Undefined error"),
101     _("File not found"),
102     _("Access violation"),
103     _("Disk full or allocation error"),
104     _("Illegal TFTP operation"),
105     _("Unknown transfer ID"),
106     _("File already exists"),
107     _("No such user")
108 };
109     const int port = 69;
110     struct sockaddr_in to;
111     struct sockaddr_in from;
112     struct timeval tv;
113     fd_set rfds;
114     unsigned short tmp;
115     int fromlen;
116     int opcode = TFTP_WRQ;
117     int tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT;
118     int timeout;
119     int block_nr = 1;
120     int finished = 0;
121     int socketfd;
122     int len;
123     int bc = lseek(localfd, 0, SEEK_CUR);
124     char *nl = "";
125     char *cp;
126     char *buf = malloc(tftp_bufsize);
127     char buf2[64];
128
129     printf("Trying to upload firmware to the AP...\n");
130
131     if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
132         perror("socket()");
133         return EXIT_FAILURE;
134     }
135
136     len = sizeof(to);
137     memset(&to, 0, len);
138     bind(socketfd, (struct sockaddr *)&to, len);
139
140     to.sin_family = AF_INET;
141     to.sin_port = htons(port);
142     to.sin_addr.s_addr = ip;
143
144     while (1) {
145         timeout = TFTP_RETRIES;
146         cp = buf;
147
148         /* first create the opcode part */
149
150         *((unsigned short *) cp) = htons(opcode);
151         cp += 2;
152
153         if (opcode == TFTP_WRQ) {
154             /* see if the filename fits into buf */
155             len = strlen(remotefile) + 1;
156             if ((cp + len + 1 + 6 + woptlen) >= &buf[tftp_bufsize - 1]) {
157                 printf(_("Remote-filename too long.\n"));
158                 break;
159             }
160
161             strncpy(cp, remotefile, len);
162             cp += len;
163
164             // ATMEL tftp client specific
165             *cp = 1;
166             cp++;
167
168             memcpy(cp, "octet", 6);
169             cp += 6;
170
171             // ATMEL tftp client specific ("upload auth. code")
172             memcpy(cp, wopt, woptlen);
173             cp += woptlen;
174         }
175
176         if (opcode == TFTP_DATA) {
177             *((unsigned short *) cp) = htons(block_nr);
178             cp += 2;
179
180             len = read(localfd, cp, tftp_bufsize);
181
182             if (len < 0) {
183                 sprintf(buf2, _("%sError in read()"), nl);
184                 perror(buf2);
185                 break;
186             }
187
188             block_nr++;
189
190             /*
191              * assure we wont upload more than maxdlen bytes
192              * even if total file length > maxdlen
193              */
194             if (bc + len > maxdlen) {
195                 len = maxdlen - bc;
196                 cp[len] = '\0';
197             }
198             bc += len;
199
200             if (len != tftp_bufsize)
201                 finished++;
202
203             cp += len;
204         }
205
206         /* send packet */
207
208         do {
209             len = cp - buf;
210
211 #ifdef FEATURE_TFTP_DEBUG
212             printf(_("sending %u bytes\n"), len);
213             for (cp = buf; cp < &buf[len]; cp++)
214                 printf("%02x ", *cp & 0xFF);
215
216             printf("\n");
217 #endif
218             if (sendto(socketfd, buf, len, 0,
219                 (struct sockaddr *) &to, sizeof(to)) < 0)
220             {
221                 sprintf(buf2, _("%sError in sendto()"), nl);
222                 perror(buf2);
223                 len = -1;
224                 break;
225             }
226
227             /* receive packet */
228
229             memset(&from, 0, sizeof(from));
230             fromlen = sizeof(from);
231
232             tv.tv_sec = TFTP_TIMEOUT;
233             tv.tv_usec = 0;
234
235             FD_ZERO(&rfds);
236             FD_SET(socketfd, &rfds);
237
238             switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
239                 case 1:
240                     len = recvfrom(socketfd, buf, tftp_bufsize, 0,
241                         (struct sockaddr *) &from, &fromlen);
242
243                     if (len < 0) {
244                         sprintf(buf2, _("%sError in recvfrom()"), nl);
245                         perror(buf2);
246                         break;
247                     }
248
249                     /*
250                     if (to.sin_port == htons(port))
251                         to.sin_port = from.sin_port;
252                     */
253                     if (to.sin_port == from.sin_port) {
254 #ifndef FEATURE_TFTP_DEBUG
255                         if (opcode == TFTP_DATA) {
256                             sprintf(buf2, _("\rProgress: uploaded %3i%%."),
257                                 bc * 100 / maxdlen);
258                             write(fileno(stdout), buf2, strlen(buf2));
259                             nl = "\n";
260                         }
261 #endif
262                         timeout = 0;
263                         break;
264                     }
265
266                     /* assume invalid packet */
267                     printf(_("%sMalformed packet received. Aborting.\n"), nl);
268                     len = -1;
269                     break;
270
271                 case 0:
272                     timeout--;
273                     if (timeout == 0)
274                         len = -1;
275
276                     printf(_("%sTimed out waiting for response from server "
277                         "(%i/%i).\n"),nl, TFTP_RETRIES - timeout, TFTP_RETRIES);
278                     nl = "";
279                     break;
280
281                 default:
282                     len = -1;
283                     sprintf(buf2, _("%sError in select()"), nl);
284                     perror(buf2);
285             }
286         } while (timeout && (len >= 0));
287
288         if (len < 0)
289             break;
290
291         /* process received packet */
292
293         opcode = ntohs(*((unsigned short *) buf));
294         tmp = ntohs(*((unsigned short *) &buf[2]));
295
296 #ifdef FEATURE_TFTP_DEBUG
297         printf(_("Received %d bytes: %04x %04x\n"), len, opcode, tmp);
298 #endif
299
300         if (opcode == TFTP_ERROR) {
301             sprintf(buf, "code %i", tmp);
302             if (tmp < (sizeof(tftp_error_msg) / sizeof(char *))) {
303                 strcat(buf, " (");
304                 strcat(buf, tftp_error_msg[tmp]);
305                 strcat(buf, ")");
306             }
307
308             printf(_("%sError: server responded with %s. Aborting.\n"),nl,buf);
309             break;
310         }
311
312         if (opcode == TFTP_ACK) {
313             if (tmp == (block_nr - 1)) {
314                 if (finished) {
315                     printf(_("%sFlash programming in progress...\n"), nl);
316                     sleep(5);
317                     printf(_("Finished successfully.\n"));
318                     break;
319                 }
320
321                 opcode = TFTP_DATA;
322                 continue;
323             }
324         }
325     }
326
327     close(socketfd);
328     free(buf);
329
330     return finished ? EXIT_SUCCESS : EXIT_FAILURE;
331 }
332
333 void usage(char **argv)
334 {
335     printf (_("PLEASE BE _ABSOLUTELY_ SURE TO READ MANPAGE PRIOR USE!!!\n"));
336     printf (_(
337         "\nUsage: %s -i IP -f firmware_file.rom [-c community] [-h]\n"),
338         argv[0]);
339 }
340
341 int main(int argc, char **argv)
342 {
343     struct stat sb;
344     char *localfile = NULL;
345     char *remotefile = NULL;
346     char *cp;
347     int fd;
348     int result = 0;
349     int i;
350     static char wopt[256];
351     int woptlen;
352     int maxdlen;
353
354 #ifdef HAVE_GETTEXT
355     /* locale support init */
356     setlocale(LC_ALL, "");
357     bindtextdomain("ap-utils", LOCALEDIR);
358     textdomain("ap-utils");
359 #endif
360
361     printf (_("TFTP client for upgrading firmware in ATMEL AT76C510 "
362             "WiSOC-based APs.\n"));
363     printf (_("(C) 2004-2005 Jan Rafaj "
364             "<jr-aputils at cedric dot unob dot cz>\n"));
365
366     if (argc == 1) {
367         usage(argv);
368         return EXIT_SUCCESS;
369     }
370
371     ap_ip.s_addr = 0;
372
373     while ((i = getopt(argc, argv, "i:f:c:h")) != -1) {
374         switch (i) {
375             case 'i':
376                 /* host = gethostbyname(optarg); */
377                 for (cp = optarg, i = 0; *cp && (cp = index(cp, '.')); cp++, i++);
378                 if (i < 3 || !(inet_aton(optarg, &ap_ip))) {
379                     printf(_("Error: invalid IP address format given.\n"));
380                     result = EXIT_FAILURE;
381                     goto quit;
382                 }
383                 break;
384             case 'f': 
385                 localfile = strdup(optarg);
386                 break;
387             case 'c':
388                 community = strdup(optarg);
389                 break;
390             case 'h':
391                 usage(argv);
392                 result = EXIT_SUCCESS;
393                 goto quit;
394             default:
395                 result = EXIT_FAILURE;
396                 goto quit;
397         }
398     }
399
400     /*
401      * either any of mandatory options (IP, firmware path) not specified,
402      * or some extra non-option arguments specified
403      */
404     if (ap_ip.s_addr == 0 || !localfile || optind != argc) {
405         printf(_("Error: invalid argument combination.\n"));
406         result = EXIT_FAILURE;
407         goto quit;
408     }
409
410     fd = open(localfile, O_RDONLY, 0644);
411     if (fd < 0) {
412         perror(_("Error while open()ing firmware file"));
413         result = EXIT_FAILURE;
414         goto quit;
415     }
416
417     fstat(fd, &sb);
418     if (sb.st_size < 94448 || sb.st_size > 524288) {
419         printf(_("Error: invalid firmware file given.\n"));
420         result = EXIT_FAILURE;
421         goto quit;
422     }
423
424     while ((i = read(fd, wopt, sizeof(wopt))) != 0) {
425         if (i < 0) {
426             perror(ERR_READFW);
427             result = EXIT_FAILURE;
428             goto quit;
429         }
430
431         if (i == sizeof(wopt))
432             lseek(fd, -13, SEEK_CUR);
433
434         while (i--) {
435             if (!(memcmp(&wopt[i], "ATMEL", 5)))
436                 result |= 1;
437
438             if (!(memcmp(&wopt[i], "802.11 AP", 9)))
439                 result |= 2;
440
441             if (!(memcmp(&wopt[i], "atbrfirm.bin", 12))) {
442                 result |= 16;
443                 remotefile = "atbrfirm.bin";
444             }
445
446             if (!(memcmp(&wopt[i], "atsingle.bin", 12))) {
447                 result |= 32;
448                 remotefile = "atsingle.bin";
449             }
450
451             if (result > 18)
452                 break;
453         }
454
455         if (result > 18)
456             break;
457
458         memset(wopt, 0, sizeof(wopt));
459     }
460 #if FEATURE_TFTP_DEBUG
461     printf(_("Firmware file contains:\n"
462             "- string \"ATMEL\": %s\n"
463             "- string \"802.11 AP\": %s\n"
464             "- string \"atbrfirm.bin\": %s\n"
465             "- string \"atsingle.bin\": %s\n"),
466             result & 1 ? YES : NO, result & 2 ? YES : NO,
467             result & 16 ? YES : NO, result & 32 ? YES : NO);
468 #endif
469     if ((result & (1|2)) != (1|2) || (result & (16|32)) == (16|32) ||
470         (result & (16|32)) == 0)
471     {
472         printf(_("Error: invalid firmware file given.\n"));
473         result = EXIT_FAILURE;
474         goto quit;
475     }
476
477     lseek(fd, 0, SEEK_SET);
478
479     cp = strrchr(localfile, '/');
480     if (cp)
481         cp++;
482     else
483         cp = localfile;
484
485     printf(_("Using:\n"
486         "- server: %s\n"
487         "- firmware file: \"%s\"\n"
488         "- name used for upload: \"%s\"\n"),
489         inet_ntoa(ap_ip), cp, remotefile);
490
491     if (result & 16) {
492         /* Firmware series 1.4x.y - atbrfirm.bin */
493
494         /*
495          * For most devices using 1.4x.y firmware:
496          * - at most 94448 bytes needs to be written (file content above
497          *   this offset is simply cut off)
498          * - checksum is only performed on the uploaded (first 94448)
499          *   bytes of the firmware file
500          * - auth. code has 8 bytes in length (see below for its structure)
501          * SmartBridges, however, differs in this:
502          * - the TFTP firmware upgrade utility first checks whether the OUI
503          *   portion of its MAC address is 00301A (SMARTBRIDGES PTE. LTD.),
504          *   followed by 09 (apparently AT76C510-based SmartBridges devices),
505          *   and will continue only upon match. This is to prevent performing
506          *   this SmartBridges-style upgrade to non-SmartBridges devices, that
507          *   could otherwise end up with damaged flash content. Note that
508          *   this is still somewhat clumsy, since there's a remote possibility
509          *   that someone redefined his/her ATMEL AP MAC address with
510          *   SmartBridges OUI and added 09, so this check would not prevent
511          *   damage in this case (ouch!). There's however, a better check
512          *   we could come up up with: lets borrow a part of the code from
513          *   get_mib_details() to find out whether the target device appears
514          *   to be running firmware with ATMEL410 SBRIDGES MIB, and proceed
515          *   with 'SmartBridges' upgrade method only, if it does.
516          * - entire file is being written; there's no 'first 94448 bytes only'
517          *   limitation
518          * - 8-byte auth. code (see below) is followed by:
519          *   - (char):   length of the following "password" string
520          *   - (string): "password" string - the firmware will perform
521          *               actual upgrade only if this "password" matches with
522          *               any of the three communities defined in the AP
523          *               (sysadmin's note: finally something reasonable)
524          */
525
526         unsigned short sum = 0;
527         unsigned short *wp;
528         int readen = 0;
529
530         if (community) {
531             /*
532              * OK, looks like we want to upgrade SmartBridges hardware => no
533              * firmware file cutoff, and lets check whether the target device
534              * appears to have SmartBridges firmware. If not, bail out.
535              */
536 /*
537             char operEthernetAddress[12] = {
538                 0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A,
539                 0x01, 0x01, 0x02, 0x03, 0x00
540             };
541 */
542             char AuthRadiusIP[] = {
543                 0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A,
544                 0x01, 0x02, 0x06, 0x03, 0x00
545             };
546             varbind varbinds[1];
547
548             maxdlen = sb.st_size;
549
550 /*
551             varbinds[0].oid = operEthernetAddress;
552             varbinds[0].len_oid = sizeof(operEthernetAddress);
553 */
554             varbinds[0].oid = AuthRadiusIP;
555             varbinds[0].len_oid = sizeof(AuthRadiusIP);
556             varbinds[0].value = NULL;
557             varbinds[0].len_val = 0;
558             varbinds[0].type = NULL_VALUE;
559             if (snmp(varbinds, 1, GET) > 0) {
560 /*
561                 i = varbinds[0].value[0] << 24 |
562                     varbinds[0].value[1] << 16 |
563                     varbinds[0].value[2] << 8  |
564                     varbinds[0].value[3];
565                 if (i != 0x00301A09) {
566 */
567                 if (varbinds[0].len_val != 4) {
568                     printf(_("Error: device with the given IP does not seem "
569                              "to run SmartBridges firmware.\n"));
570                     result = EXIT_FAILURE;
571                     goto quit;
572                 }
573             } else {
574                 printf(_("Error: SNMP authorization error or device unavailable"
575                     ".\n"));
576                 result = EXIT_FAILURE;
577                 goto quit;
578             }
579         } else {
580             /* non-SB hardware */
581             maxdlen = 94448;
582         }
583
584         /* compute checksum */
585         while ((i = read(fd, wopt, sizeof(wopt))) != 0) {
586             if (i < 0) {
587                 perror(ERR_READFW);
588                 result = EXIT_FAILURE;
589                 goto quit;
590             }
591
592             /* assure we do not checksum more than maxdlen bytes */
593             if ((readen + i) > maxdlen)
594                 i = maxdlen - readen;
595
596             readen += i;
597
598             /* perform iterative checksumming */
599             i /= 2;
600             wp = (unsigned short *)wopt;
601             while (i--) {
602                 if (sum + *wp > 0xFFFF)
603                     sum++;
604
605                 sum += *(wp++);
606             };
607
608             /* prevent eventual fread() if file length > maxdlen */
609             if (readen == maxdlen)
610                 break;
611         }
612         sum ^= 0xFFFF;
613         lseek (fd, 0, SEEK_SET);
614
615         /*
616          * wopt[0] - wopt[1] are fw length in unsigned short form
617          * wopt[2] - wopt[3] are fw checksum in unsigned short form
618          * wopt[4] - wopt[7] are fw length in unsigned int form
619          */
620         wopt[0] = maxdlen;
621         wopt[1] = maxdlen >> 8;
622         wopt[2] = sum;
623         wopt[3] = sum >> 8;
624         wopt[4] = maxdlen;
625         wopt[5] = maxdlen >> 8;
626         wopt[6] = maxdlen >> 16;
627         wopt[7] = maxdlen >> 24;
628
629         printf("- checksum: %x\n", ntohs(sum));
630
631         woptlen = 8;
632
633         if (community) {
634             wopt[8] = strlen(community);
635             memcpy((void *) &wopt[9], (void *)community, strlen(community));
636             woptlen += (1 + strlen(community));
637         }
638     } else { /* result & 32 */
639         /* Firmware series 0.x.y.z - atsingle.bin */
640
641         /* For 0.x.y.z firmware:
642          * - first 234 bytes from the file are used as auth. code
643          * - the actual firmware is being uploaded since offset 256 in the file
644          */
645
646         i = read(fd, wopt, 234);
647         if (i < 234) {
648             printf(ERR_READFW);
649             result = EXIT_FAILURE;
650             goto quit;
651         }
652         if (lseek(fd, 256, SEEK_SET) == -1) {
653             perror(_("Error while lseek()ing in firmware file\n"));
654             result = EXIT_FAILURE;
655             goto quit;
656         }
657         woptlen = 234;
658         maxdlen = sb.st_size; /* no limit */
659     }
660
661     result = tftp(ap_ip.s_addr, remotefile, fd, wopt, woptlen, maxdlen);
662
663 quit:
664     if (localfile)
665         free(localfile);
666     if (community)
667         free(community);
668
669     return(result);
670 }