]> git.decadent.org.uk Git - ap-utils.git/blobdiff - src/ap-tftp.c
Merge commit 'upstream/1.5'
[ap-utils.git] / src / ap-tftp.c
diff --git a/src/ap-tftp.c b/src/ap-tftp.c
new file mode 100644 (file)
index 0000000..5e6ac57
--- /dev/null
@@ -0,0 +1,670 @@
+/* ------------------------------------------------------------------------- */
+/* 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) 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)    */
+/*                                                                           */
+/* Loosely based on a simple tftp client for busybox.                        */
+/* Tries to follow RFC1350.                                                  */
+/* Only "octet" mode and "put" method supported.                             */
+/*                                                                           */
+/* Not implemented:                                                          */
+/* - uploading of OEM (default) settings                                     */
+/* - uploading of PATCH code                                                 */
+/*                                                                           */
+/* Code based on:                                                            */
+/*                                                                           */
+/* bb tftp: Copyright (C) 2001 Magnus Damm <damm@opensource.se>              */
+/*                                                                           */
+/* atftp:   Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca>  */
+/*                             and Remi Lefebvre <remi@debian.org>           */
+/*                                                                           */
+/* utftp:   Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>                        */
+/*                                                                           */
+/* This program is free software; you can redistribute it and/or modify      */
+/* it under the terms of the GNU General Public License as published by      */
+/* the Free Software Foundation; either version 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* This program is distributed in the hope that it will be useful,           */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU          */
+/* General Public License for more details.                                  */
+/*                                                                           */
+/* You should have received a copy of the GNU General Public License         */
+/* along with this program; if not, write to the Free Software               */
+/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
+/*                                                                           */
+/* ------------------------------------------------------------------------- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "ap-utils.h"
+
+#ifdef HAVE_GETTEXT
+/* GNU gettext stuff*/
+#include <locale.h>
+#include <libgnuintl.h>
+#define _(String) gettext (String)
+#else
+#define _(String) (String)
+#endif
+
+#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
+
+/* opcodes we support */
+#define TFTP_WRQ   2
+#define TFTP_DATA  3
+#define TFTP_ACK   4
+#define TFTP_ERROR 5
+
+#ifdef FEATURE_TFTP_DEBUG
+#define YES _("yes")
+#define NO _("no")
+#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 */
+char *tftp_error_msg[8] = {
+    _("Undefined error"),
+    _("File not found"),
+    _("Access violation"),
+    _("Disk full or allocation error"),
+    _("Illegal TFTP operation"),
+    _("Unknown transfer ID"),
+    _("File already exists"),
+    _("No such user")
+};
+    const int port = 69;
+    struct sockaddr_in to;
+    struct sockaddr_in from;
+    struct timeval tv;
+    fd_set rfds;
+    unsigned short tmp;
+    int fromlen;
+    int opcode = TFTP_WRQ;
+    int tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT;
+    int timeout;
+    int block_nr = 1;
+    int finished = 0;
+    int socketfd;
+    int len;
+    int bc = lseek(localfd, 0, SEEK_CUR);
+    char *nl = "";
+    char *cp;
+    char *buf = malloc(tftp_bufsize);
+    char buf2[64];
+
+    printf("Trying to upload firmware to the AP...\n");
+
+    if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
+       perror("socket()");
+       return EXIT_FAILURE;
+    }
+
+    len = sizeof(to);
+    memset(&to, 0, len);
+    bind(socketfd, (struct sockaddr *)&to, len);
+
+    to.sin_family = AF_INET;
+    to.sin_port = htons(port);
+    to.sin_addr.s_addr = ip;
+
+    while (1) {
+       timeout = TFTP_RETRIES;
+       cp = buf;
+
+       /* first create the opcode part */
+
+       *((unsigned short *) cp) = htons(opcode);
+       cp += 2;
+
+       if (opcode == TFTP_WRQ) {
+           /* see if the filename fits into buf */
+           len = strlen(remotefile) + 1;
+           if ((cp + len + 1 + 6 + woptlen) >= &buf[tftp_bufsize - 1]) {
+               printf(_("Remote-filename too long.\n"));
+               break;
+           }
+
+           strncpy(cp, remotefile, len);
+           cp += len;
+
+           // ATMEL tftp client specific
+           *cp = 1;
+           cp++;
+
+           memcpy(cp, "octet", 6);
+           cp += 6;
+
+           // ATMEL tftp client specific ("upload auth. code")
+           memcpy(cp, wopt, woptlen);
+           cp += woptlen;
+       }
+
+       if (opcode == TFTP_DATA) {
+           *((unsigned short *) cp) = htons(block_nr);
+           cp += 2;
+
+           len = read(localfd, cp, tftp_bufsize);
+
+           if (len < 0) {
+               sprintf(buf2, _("%sError in read()"), nl);
+               perror(buf2);
+               break;
+           }
+
+           block_nr++;
+
+           /*
+            * assure we wont upload more than maxdlen bytes
+            * even if total file length > maxdlen
+            */
+           if (bc + len > maxdlen) {
+               len = maxdlen - bc;
+               cp[len] = '\0';
+           }
+           bc += len;
+
+           if (len != tftp_bufsize)
+               finished++;
+
+           cp += len;
+       }
+
+       /* send packet */
+
+       do {
+           len = cp - buf;
+
+#ifdef FEATURE_TFTP_DEBUG
+           printf(_("sending %u bytes\n"), len);
+           for (cp = buf; cp < &buf[len]; cp++)
+               printf("%02x ", *cp & 0xFF);
+
+           printf("\n");
+#endif
+           if (sendto(socketfd, buf, len, 0,
+               (struct sockaddr *) &to, sizeof(to)) < 0)
+           {
+               sprintf(buf2, _("%sError in sendto()"), nl);
+               perror(buf2);
+               len = -1;
+               break;
+           }
+
+           /* receive packet */
+
+           memset(&from, 0, sizeof(from));
+           fromlen = sizeof(from);
+
+           tv.tv_sec = TFTP_TIMEOUT;
+           tv.tv_usec = 0;
+
+           FD_ZERO(&rfds);
+           FD_SET(socketfd, &rfds);
+
+           switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
+               case 1:
+                   len = recvfrom(socketfd, buf, tftp_bufsize, 0,
+                       (struct sockaddr *) &from, &fromlen);
+
+                   if (len < 0) {
+                       sprintf(buf2, _("%sError in recvfrom()"), nl);
+                       perror(buf2);
+                       break;
+                   }
+
+                   /*
+                   if (to.sin_port == htons(port))
+                       to.sin_port = from.sin_port;
+                   */
+                   if (to.sin_port == from.sin_port) {
+#ifndef FEATURE_TFTP_DEBUG
+                       if (opcode == TFTP_DATA) {
+                           sprintf(buf2, _("\rProgress: uploaded %3i%%."),
+                               bc * 100 / maxdlen);
+                           write(fileno(stdout), buf2, strlen(buf2));
+                           nl = "\n";
+                       }
+#endif
+                       timeout = 0;
+                       break;
+                   }
+
+                   /* assume invalid packet */
+                   printf(_("%sMalformed packet received. Aborting.\n"), nl);
+                   len = -1;
+                   break;
+
+               case 0:
+                   timeout--;
+                   if (timeout == 0)
+                       len = -1;
+
+                   printf(_("%sTimed out waiting for response from server "
+                       "(%i/%i).\n"),nl, TFTP_RETRIES - timeout, TFTP_RETRIES);
+                   nl = "";
+                   break;
+
+               default:
+                   len = -1;
+                   sprintf(buf2, _("%sError in select()"), nl);
+                   perror(buf2);
+           }
+       } while (timeout && (len >= 0));
+
+       if (len < 0)
+           break;
+
+       /* process received packet */
+
+       opcode = ntohs(*((unsigned short *) buf));
+       tmp = ntohs(*((unsigned short *) &buf[2]));
+
+#ifdef FEATURE_TFTP_DEBUG
+       printf(_("Received %d bytes: %04x %04x\n"), len, opcode, tmp);
+#endif
+
+       if (opcode == TFTP_ERROR) {
+           sprintf(buf, "code %i", tmp);
+           if (tmp < (sizeof(tftp_error_msg) / sizeof(char *))) {
+               strcat(buf, " (");
+               strcat(buf, tftp_error_msg[tmp]);
+               strcat(buf, ")");
+           }
+
+           printf(_("%sError: server responded with %s. Aborting.\n"),nl,buf);
+           break;
+       }
+
+       if (opcode == TFTP_ACK) {
+           if (tmp == (block_nr - 1)) {
+               if (finished) {
+                   printf(_("%sFlash programming in progress...\n"), nl);
+                   sleep(5);
+                   printf(_("Finished successfully.\n"));
+                   break;
+               }
+
+               opcode = TFTP_DATA;
+               continue;
+           }
+       }
+    }
+
+    close(socketfd);
+    free(buf);
+
+    return finished ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+void usage(char **argv)
+{
+    printf (_("PLEASE BE _ABSOLUTELY_ SURE TO READ MANPAGE PRIOR USE!!!\n"));
+    printf (_(
+       "\nUsage: %s -i IP -f firmware_file.rom [-c community] [-h]\n"),
+       argv[0]);
+}
+
+int main(int argc, char **argv)
+{
+    struct stat sb;
+    char *localfile = NULL;
+    char *remotefile = NULL;
+    char *cp;
+    int fd;
+    int result = 0;
+    int i;
+    static char wopt[256];
+    int woptlen;
+    int maxdlen;
+
+#ifdef HAVE_GETTEXT
+    /* locale support init */
+    setlocale(LC_ALL, "");
+    bindtextdomain("ap-utils", LOCALEDIR);
+    textdomain("ap-utils");
+#endif
+
+    printf (_("TFTP client for upgrading firmware in ATMEL AT76C510 "
+           "WiSOC-based APs.\n"));
+    printf (_("(C) 2004-2005 Jan Rafaj "
+           "<jr-aputils at cedric dot unob dot cz>\n"));
+
+    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 '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);
+               result = EXIT_SUCCESS;
+               goto quit;
+           default:
+               result = EXIT_FAILURE;
+               goto quit;
+       }
+    }
+
+    /*
+     * either any of mandatory options (IP, firmware path) not specified,
+     * or some extra non-option arguments specified
+     */
+    if (ap_ip.s_addr == 0 || !localfile || optind != argc) {
+       printf(_("Error: invalid argument combination.\n"));
+       result = EXIT_FAILURE;
+       goto quit;
+    }
+
+    fd = open(localfile, O_RDONLY, 0644);
+    if (fd < 0) {
+       perror(_("Error while open()ing firmware file"));
+       result = EXIT_FAILURE;
+       goto quit;
+    }
+
+    fstat(fd, &sb);
+    if (sb.st_size < 94448 || sb.st_size > 524288) {
+       printf(_("Error: invalid firmware file given.\n"));
+       result = EXIT_FAILURE;
+       goto quit;
+    }
+
+    while ((i = read(fd, wopt, sizeof(wopt))) != 0) {
+       if (i < 0) {
+           perror(ERR_READFW);
+           result = EXIT_FAILURE;
+           goto quit;
+       }
+
+       if (i == sizeof(wopt))
+           lseek(fd, -13, SEEK_CUR);
+
+       while (i--) {
+           if (!(memcmp(&wopt[i], "ATMEL", 5)))
+               result |= 1;
+
+           if (!(memcmp(&wopt[i], "802.11 AP", 9)))
+               result |= 2;
+
+           if (!(memcmp(&wopt[i], "atbrfirm.bin", 12))) {
+               result |= 16;
+               remotefile = "atbrfirm.bin";
+           }
+
+           if (!(memcmp(&wopt[i], "atsingle.bin", 12))) {
+               result |= 32;
+               remotefile = "atsingle.bin";
+           }
+
+           if (result > 18)
+               break;
+       }
+
+       if (result > 18)
+           break;
+
+       memset(wopt, 0, sizeof(wopt));
+    }
+#if FEATURE_TFTP_DEBUG
+    printf(_("Firmware file contains:\n"
+           "- string \"ATMEL\": %s\n"
+           "- string \"802.11 AP\": %s\n"
+           "- string \"atbrfirm.bin\": %s\n"
+           "- string \"atsingle.bin\": %s\n"),
+           result & 1 ? YES : NO, result & 2 ? YES : NO,
+           result & 16 ? YES : NO, result & 32 ? YES : NO);
+#endif
+    if ((result & (1|2)) != (1|2) || (result & (16|32)) == (16|32) ||
+       (result & (16|32)) == 0)
+    {
+       printf(_("Error: invalid firmware file given.\n"));
+       result = EXIT_FAILURE;
+       goto quit;
+    }
+
+    lseek(fd, 0, SEEK_SET);
+
+    cp = strrchr(localfile, '/');
+    if (cp)
+       cp++;
+    else
+       cp = localfile;
+
+    printf(_("Using:\n"
+       "- server: %s\n"
+       "- firmware file: \"%s\"\n"
+       "- name used for upload: \"%s\"\n"),
+       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 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, wopt, sizeof(wopt))) != 0) {
+           if (i < 0) {
+               perror(ERR_READFW);
+               result = EXIT_FAILURE;
+               goto quit;
+           }
+
+           /* assure we do not checksum more than maxdlen bytes */
+           if ((readen + i) > maxdlen)
+               i = maxdlen - readen;
+
+           readen += i;
+
+           /* perform iterative checksumming */
+           i /= 2;
+           wp = (unsigned short *)wopt;
+           while (i--) {
+               if (sum + *wp > 0xFFFF)
+                   sum++;
+
+               sum += *(wp++);
+           };
+
+           /* prevent eventual fread() if file length > maxdlen */
+           if (readen == maxdlen)
+               break;
+       }
+       sum ^= 0xFFFF;
+       lseek (fd, 0, SEEK_SET);
+
+       /*
+        * 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 */
+
+       /* 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);
+           result = EXIT_FAILURE;
+           goto quit;
+       }
+       if (lseek(fd, 256, SEEK_SET) == -1) {
+           perror(_("Error while lseek()ing in firmware file\n"));
+           result = EXIT_FAILURE;
+           goto quit;
+       }
+       woptlen = 234;
+       maxdlen = sb.st_size; /* no limit */
+    }
+
+    result = tftp(ap_ip.s_addr, remotefile, fd, wopt, woptlen, maxdlen);
+
+quit:
+    if (localfile)
+       free(localfile);
+    if (community)
+       free(community);
+
+    return(result);
+}