]> git.decadent.org.uk Git - ap-utils.git/blobdiff - src/ap-tftp.c
Imported Upstream version 1.5
[ap-utils.git] / src / ap-tftp.c
index 990de43e7eea26f48004fc185cf89fadc1c25f7d..5e6ac579cbd05651b0292d994545aef51e9f810b 100644 (file)
@@ -1,20 +1,22 @@
 /* ------------------------------------------------------------------------- */
 /* ap-tftp.c                                                                 */
 /*                                                                           */
+/* Version: 1.1                                                              */
+/*                                                                           */
+/* Copyright (C) 2004-2005 Jan Rafaj <jr-aputils at cedric dot unob dot cz>  */
+/*                                                                           */
 /* A simple tftp client for upgrading ATMEL AT76C510 WiSOC-based APs.        */
 /* Supports ATMEL+INTERSIL boards (1.4x.y firmware) and ATMEL+RFMD boards    */
 /* (0.x.y.z firmware).                                                       */
-/* Modelled around TELLUS (GEMTEK/ATMEL OEM) TFTP client functionality.      */
+/* Modelled around TELLUS (GEMTEK/ATMEL OEM) and SMARTBRIDGES TFTP clients   */
+/* functionality.                                                            */
+/* Tested with TELLUS, D-Link and SmartBridges hardware.                     */
 /* This program is part of AP-UTILS project (http://ap-utils.polesye.net)    */
 /*                                                                           */
-/* Copyright (C) 2004-2005 Jan Rafaj <jr-aputils at cedric dot unob dot cz>  */
-/*                                                                           */
 /* Loosely based on a simple tftp client for busybox.                        */
 /* Tries to follow RFC1350.                                                  */
 /* Only "octet" mode and "put" method supported.                             */
 /*                                                                           */
-/* Version: 1.0                                                              */
-/*                                                                           */
 /* Not implemented:                                                          */
 /* - uploading of OEM (default) settings                                     */
 /* - uploading of PATCH code                                                 */
@@ -56,6 +58,7 @@
 #include <arpa/inet.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include "ap-utils.h"
 
 #ifdef HAVE_GETTEXT
 /* GNU gettext stuff*/
@@ -66,7 +69,7 @@
 #define _(String) (String)
 #endif
 
-#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
+#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, do not change */
 #define TFTP_TIMEOUT 2             /* # seconds waiting for single response */
 #define TFTP_RETRIES 5             /* # retries with TFTP_TIMEOUT */
 #undef FEATURE_TFTP_DEBUG
 #endif
 #define ERR_READFW _("Error while read()ing firmware file")
 
+
+/* This is necessary becouse of linkage to libap.a */
+short ap_type = ATMEL410;
+char *community = NULL;
+struct in_addr ap_ip;
+
+int tftp(int ip, const char *remotefile, int localfd, const char *wopt,
+        int woptlen, int maxdlen)
+{
 /* known errors server may respond with */
-static const char *tftp_error_msg[] = {
+char *tftp_error_msg[8] = {
     _("Undefined error"),
     _("File not found"),
     _("Access violation"),
@@ -94,14 +106,10 @@ static const char *tftp_error_msg[] = {
     _("File already exists"),
     _("No such user")
 };
-
-int tftp(int ip, const char *remotefile, int localfd, const char *options)
-{
     const int port = 69;
     struct sockaddr_in to;
     struct sockaddr_in from;
     struct timeval tv;
-    struct stat sb;
     fd_set rfds;
     unsigned short tmp;
     int fromlen;
@@ -112,8 +120,7 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
     int finished = 0;
     int socketfd;
     int len;
-    int maxlen;
-    int optlen;
+    int bc = lseek(localfd, 0, SEEK_CUR);
     char *nl = "";
     char *cp;
     char *buf = malloc(tftp_bufsize);
@@ -121,17 +128,6 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
 
     printf("Trying to upload firmware to the AP...\n");
 
-    if (!strcmp(remotefile, "atbrfirm.bin")) {
-       optlen = 8;
-       maxlen = 94448;
-    } else {
-       /* remotefile = atsingle.bin */
-       optlen = 234;
-       maxlen = 0;
-    }
-
-    fstat(localfd, &sb);
-
     if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket()");
        return EXIT_FAILURE;
@@ -157,7 +153,7 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
        if (opcode == TFTP_WRQ) {
            /* see if the filename fits into buf */
            len = strlen(remotefile) + 1;
-           if ((cp + len + 1 + 6 + optlen) >= &buf[tftp_bufsize - 1]) {
+           if ((cp + len + 1 + 6 + woptlen) >= &buf[tftp_bufsize - 1]) {
                printf(_("Remote-filename too long.\n"));
                break;
            }
@@ -173,8 +169,8 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
            cp += 6;
 
            // ATMEL tftp client specific ("upload auth. code")
-           memcpy(cp, options, optlen);
-           cp += optlen;
+           memcpy(cp, wopt, woptlen);
+           cp += woptlen;
        }
 
        if (opcode == TFTP_DATA) {
@@ -192,13 +188,14 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
            block_nr++;
 
            /*
-            * assure we wont upload more than maxlen bytes
-            * even if total file length > maxlen
+            * assure we wont upload more than maxdlen bytes
+            * even if total file length > maxdlen
             */
-           if (maxlen && (block_nr - 2) * tftp_bufsize + len > maxlen) {
-               len = maxlen - (block_nr - 2) * tftp_bufsize;
+           if (bc + len > maxdlen) {
+               len = maxdlen - bc;
                cp[len] = '\0';
            }
+           bc += len;
 
            if (len != tftp_bufsize)
                finished++;
@@ -256,10 +253,8 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
                    if (to.sin_port == from.sin_port) {
 #ifndef FEATURE_TFTP_DEBUG
                        if (opcode == TFTP_DATA) {
-                           float f = (block_nr - 2) * tftp_bufsize + len;
-
-                           sprintf(buf2, _("\rProgress: uploaded %.0f %%."),
-                               f / sb.st_size * 100);
+                           sprintf(buf2, _("\rProgress: uploaded %3i%%."),
+                               bc * 100 / maxdlen);
                            write(fileno(stdout), buf2, strlen(buf2));
                            nl = "\n";
                        }
@@ -338,12 +333,13 @@ int tftp(int ip, const char *remotefile, int localfd, const char *options)
 void usage(char **argv)
 {
     printf (_("PLEASE BE _ABSOLUTELY_ SURE TO READ MANPAGE PRIOR USE!!!\n"));
-    printf (_("\nUsage: %s <-l firmware_file.rom> <IP>\n"), argv[0]);
+    printf (_(
+       "\nUsage: %s -i IP -f firmware_file.rom [-c community] [-h]\n"),
+       argv[0]);
 }
 
 int main(int argc, char **argv)
 {
-    struct in_addr in;
     struct stat sb;
     char *localfile = NULL;
     char *remotefile = NULL;
@@ -351,7 +347,9 @@ int main(int argc, char **argv)
     int fd;
     int result = 0;
     int i;
-    static char buf[256];
+    static char wopt[256];
+    int woptlen;
+    int maxdlen;
 
 #ifdef HAVE_GETTEXT
     /* locale support init */
@@ -365,75 +363,87 @@ int main(int argc, char **argv)
     printf (_("(C) 2004-2005 Jan Rafaj "
            "<jr-aputils at cedric dot unob dot cz>\n"));
 
-    while ((i = getopt(argc, argv, "l:h")) != -1) {
+    if (argc == 1) {
+       usage(argv);
+       return EXIT_SUCCESS;
+    }
+
+    ap_ip.s_addr = 0;
+
+    while ((i = getopt(argc, argv, "i:f:c:h")) != -1) {
        switch (i) {
-           case 'l': 
+           case 'i':
+               /* host = gethostbyname(optarg); */
+               for (cp = optarg, i = 0; *cp && (cp = index(cp, '.')); cp++, i++);
+               if (i < 3 || !(inet_aton(optarg, &ap_ip))) {
+                   printf(_("Error: invalid IP address format given.\n"));
+                   result = EXIT_FAILURE;
+                   goto quit;
+               }
+               break;
+           case 'f': 
                localfile = strdup(optarg);
                break;
+           case 'c':
+               community = strdup(optarg);
+               break;
            case 'h':
                usage(argv);
-               return EXIT_SUCCESS;
+               result = EXIT_SUCCESS;
+               goto quit;
            default:
-               return EXIT_FAILURE;
+               result = EXIT_FAILURE;
+               goto quit;
        }
     }
 
-    if (argc == 1) {
-       usage(argv);
-       return EXIT_SUCCESS;
-    }
-
     /*
-     * either no mandatory opts, or too few mandatory opts, or more than 1
-     * non-opt arg. specified, or no non-option arg. specified
+     * either any of mandatory options (IP, firmware path) not specified,
+     * or some extra non-option arguments specified
      */
-    if (optind == 1 || optind < 3 || argc > 4 || optind == argc) {
-       printf(_("Error: invalid arguments given.\n"));
-       return EXIT_FAILURE;
+    if (ap_ip.s_addr == 0 || !localfile || optind != argc) {
+       printf(_("Error: invalid argument combination.\n"));
+       result = EXIT_FAILURE;
+       goto quit;
     }
 
-    /* host = gethostbyname(argv[optind]); */
-    for (cp = argv[optind], i = 0; *cp && (cp = index(cp, '.')); cp++, i++);
-    if (i < 3 || !(inet_aton(argv[optind], &in))) {
-       printf(_("Error: invalid IP address format given.\n"));
-       return EXIT_FAILURE;
-    }
-    inet_aton(argv[optind], &in);
-
     fd = open(localfile, O_RDONLY, 0644);
     if (fd < 0) {
        perror(_("Error while open()ing firmware file"));
-       return EXIT_FAILURE;
+       result = EXIT_FAILURE;
+       goto quit;
     }
 
     fstat(fd, &sb);
     if (sb.st_size < 94448 || sb.st_size > 524288) {
        printf(_("Error: invalid firmware file given.\n"));
-       return EXIT_FAILURE;
+       result = EXIT_FAILURE;
+       goto quit;
     }
 
-    while ((i = read(fd, buf, sizeof(buf))) != 0) {
+    while ((i = read(fd, wopt, sizeof(wopt))) != 0) {
        if (i < 0) {
            perror(ERR_READFW);
-           return EXIT_FAILURE;
+           result = EXIT_FAILURE;
+           goto quit;
        }
 
-       if (i == sizeof(buf))
+       if (i == sizeof(wopt))
            lseek(fd, -13, SEEK_CUR);
 
        while (i--) {
-           if (!(memcmp(&buf[i], "ATMEL", 5)))
+           if (!(memcmp(&wopt[i], "ATMEL", 5)))
                result |= 1;
 
-           if (!(memcmp(&buf[i], "802.11 AP", 9)))
+           if (!(memcmp(&wopt[i], "802.11 AP", 9)))
                result |= 2;
 
-           if (!(memcmp(&buf[i], "atbrfirm.bin", 12))) {
+           if (!(memcmp(&wopt[i], "atbrfirm.bin", 12))) {
                result |= 16;
                remotefile = "atbrfirm.bin";
            }
 
-           if (!(memcmp(&buf[i], "atsingle.bin", 12))) {
+           if (!(memcmp(&wopt[i], "atsingle.bin", 12))) {
                result |= 32;
                remotefile = "atsingle.bin";
            }
@@ -445,7 +455,7 @@ int main(int argc, char **argv)
        if (result > 18)
            break;
 
-       memset(buf, 0, sizeof(buf));
+       memset(wopt, 0, sizeof(wopt));
     }
 #if FEATURE_TFTP_DEBUG
     printf(_("Firmware file contains:\n"
@@ -460,7 +470,8 @@ int main(int argc, char **argv)
        (result & (16|32)) == 0)
     {
        printf(_("Error: invalid firmware file given.\n"));
-       return EXIT_FAILURE;
+       result = EXIT_FAILURE;
+       goto quit;
     }
 
     lseek(fd, 0, SEEK_SET);
@@ -475,32 +486,118 @@ int main(int argc, char **argv)
        "- server: %s\n"
        "- firmware file: \"%s\"\n"
        "- name used for upload: \"%s\"\n"),
-       inet_ntoa(in), cp, remotefile);
+       inet_ntoa(ap_ip), cp, remotefile);
 
     if (result & 16) {
        /* Firmware series 1.4x.y - atbrfirm.bin */
 
+       /*
+        * For most devices using 1.4x.y firmware:
+        * - at most 94448 bytes needs to be written (file content above
+        *   this offset is simply cut off)
+        * - checksum is only performed on the uploaded (first 94448)
+        *   bytes of the firmware file
+        * - auth. code has 8 bytes in length (see below for its structure)
+        * SmartBridges, however, differs in this:
+        * - the TFTP firmware upgrade utility first checks whether the OUI
+        *   portion of its MAC address is 00301A (SMARTBRIDGES PTE. LTD.),
+        *   followed by 09 (apparently AT76C510-based SmartBridges devices),
+        *   and will continue only upon match. This is to prevent performing
+        *   this SmartBridges-style upgrade to non-SmartBridges devices, that
+        *   could otherwise end up with damaged flash content. Note that
+        *   this is still somewhat clumsy, since there's a remote possibility
+        *   that someone redefined his/her ATMEL AP MAC address with
+        *   SmartBridges OUI and added 09, so this check would not prevent
+        *   damage in this case (ouch!). There's however, a better check
+        *   we could come up up with: lets borrow a part of the code from
+        *   get_mib_details() to find out whether the target device appears
+        *   to be running firmware with ATMEL410 SBRIDGES MIB, and proceed
+        *   with 'SmartBridges' upgrade method only, if it does.
+        * - entire file is being written; there's no 'first 94448 bytes only'
+        *   limitation
+        * - 8-byte auth. code (see below) is followed by:
+        *   - (char):   length of the following "password" string
+        *   - (string): "password" string - the firmware will perform
+        *               actual upgrade only if this "password" matches with
+        *               any of the three communities defined in the AP
+        *               (sysadmin's note: finally something reasonable)
+        */
+
        unsigned short sum = 0;
        unsigned short *wp;
-       int maxlen = 94448;
-       int bufs_read = 0;
+       int readen = 0;
+
+       if (community) {
+           /*
+            * OK, looks like we want to upgrade SmartBridges hardware => no
+            * firmware file cutoff, and lets check whether the target device
+            * appears to have SmartBridges firmware. If not, bail out.
+            */
+/*
+           char operEthernetAddress[12] = {
+               0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A,
+               0x01, 0x01, 0x02, 0x03, 0x00
+           };
+*/
+           char AuthRadiusIP[] = {
+               0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A,
+               0x01, 0x02, 0x06, 0x03, 0x00
+           };
+           varbind varbinds[1];
+
+           maxdlen = sb.st_size;
+
+/*
+           varbinds[0].oid = operEthernetAddress;
+           varbinds[0].len_oid = sizeof(operEthernetAddress);
+*/
+           varbinds[0].oid = AuthRadiusIP;
+           varbinds[0].len_oid = sizeof(AuthRadiusIP);
+           varbinds[0].value = NULL;
+           varbinds[0].len_val = 0;
+           varbinds[0].type = NULL_VALUE;
+           if (snmp(varbinds, 1, GET) > 0) {
+/*
+               i = varbinds[0].value[0] << 24 |
+                   varbinds[0].value[1] << 16 |
+                   varbinds[0].value[2] << 8  |
+                   varbinds[0].value[3];
+               if (i != 0x00301A09) {
+*/
+               if (varbinds[0].len_val != 4) {
+                   printf(_("Error: device with the given IP does not seem "
+                            "to run SmartBridges firmware.\n"));
+                   result = EXIT_FAILURE;
+                   goto quit;
+               }
+           } else {
+               printf(_("Error: SNMP authorization error or device unavailable"
+                   ".\n"));
+               result = EXIT_FAILURE;
+               goto quit;
+           }
+       } else {
+           /* non-SB hardware */
+           maxdlen = 94448;
+       }
 
        /* compute checksum */
-       while ((i = read(fd, buf, sizeof(buf))) != 0) {
+       while ((i = read(fd, wopt, sizeof(wopt))) != 0) {
            if (i < 0) {
                perror(ERR_READFW);
-               return EXIT_FAILURE;
+               result = EXIT_FAILURE;
+               goto quit;
            }
 
-           /* assure we do not checksum more than maxlen bytes */
-           if ((bufs_read + i) > maxlen)
-               i = maxlen - bufs_read;
+           /* assure we do not checksum more than maxdlen bytes */
+           if ((readen + i) > maxdlen)
+               i = maxdlen - readen;
 
-           bufs_read += i;
+           readen += i;
 
            /* perform iterative checksumming */
            i /= 2;
-           wp = (unsigned short *)buf;
+           wp = (unsigned short *)wopt;
            while (i--) {
                if (sum + *wp > 0xFFFF)
                    sum++;
@@ -508,38 +605,66 @@ int main(int argc, char **argv)
                sum += *(wp++);
            };
 
-           /* prevent eventual fread() if file length > maxlen */
-           if (bufs_read == maxlen)
+           /* prevent eventual fread() if file length > maxdlen */
+           if (readen == maxdlen)
                break;
        }
        sum ^= 0xFFFF;
        lseek (fd, 0, SEEK_SET);
 
-       buf[0] = 0xf0;
-       buf[1] = 0x70;
-       buf[2] = sum;
-       buf[3] = sum >> 8;
-       buf[4] = 0xf0;
-       buf[5] = 0x70;
-       buf[6] = 0x01;
-       buf[7] = 0x00;
+       /*
+        * wopt[0] - wopt[1] are fw length in unsigned short form
+        * wopt[2] - wopt[3] are fw checksum in unsigned short form
+        * wopt[4] - wopt[7] are fw length in unsigned int form
+        */
+       wopt[0] = maxdlen;
+       wopt[1] = maxdlen >> 8;
+       wopt[2] = sum;
+       wopt[3] = sum >> 8;
+       wopt[4] = maxdlen;
+       wopt[5] = maxdlen >> 8;
+       wopt[6] = maxdlen >> 16;
+       wopt[7] = maxdlen >> 24;
 
        printf("- checksum: %x\n", ntohs(sum));
+
+       woptlen = 8;
+
+       if (community) {
+           wopt[8] = strlen(community);
+           memcpy((void *) &wopt[9], (void *)community, strlen(community));
+           woptlen += (1 + strlen(community));
+       }
     } else { /* result & 32 */
        /* Firmware series 0.x.y.z - atsingle.bin */
 
-       i = read(fd, buf, 234);
+       /* For 0.x.y.z firmware:
+        * - first 234 bytes from the file are used as auth. code
+        * - the actual firmware is being uploaded since offset 256 in the file
+        */
+
+       i = read(fd, wopt, 234);
        if (i < 234) {
            printf(ERR_READFW);
-           return EXIT_FAILURE;
+           result = EXIT_FAILURE;
+           goto quit;
        }
        if (lseek(fd, 256, SEEK_SET) == -1) {
            perror(_("Error while lseek()ing in firmware file\n"));
-           return EXIT_FAILURE;
+           result = EXIT_FAILURE;
+           goto quit;
        }
+       woptlen = 234;
+       maxdlen = sb.st_size; /* no limit */
     }
 
-    result = tftp(in.s_addr, remotefile, fd, buf);
+    result = tftp(ap_ip.s_addr, remotefile, fd, wopt, woptlen, maxdlen);
+
+quit:
+    if (localfile)
+       free(localfile);
+    if (community)
+       free(community);
 
     return(result);
 }