1 /*
2  * vim:tw=80:ai:tabstop=4:softtabstop=4:shiftwidth=4:expandtab
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * (C) Copyright Phil Dibowitz 2010
19  */
20 
21 #include "operationfile.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <zip.h>
26 #include <string>
27 
28 #include "libconcord.h"
29 #include "lc_internal.h"
30 #include "binaryfile.h"
31 #include "web.h"
32 #include "remote.h"
33 
34 static const char *FW_URL =
35     "EasyZapper/New/ProcUpgradeFirmware/Upgrade_Receive_Complete.asp";
36 
find_config_binary(uint8_t * config,uint32_t config_size,uint8_t ** binary_ptr,uint32_t * binary_size)37 int find_config_binary(uint8_t *config, uint32_t config_size,
38     uint8_t **binary_ptr, uint32_t *binary_size)
39 {
40     int err;
41 
42     err = GetTag("/INFORMATION", config, config_size, *binary_ptr);
43     if (err == -1)
44         return LC_ERROR;
45 
46     *binary_ptr += 2;
47     *binary_size = config_size - (*binary_ptr - config);
48 
49     // Limit tag searches to XML portion
50     config_size -= *binary_size;
51 
52     string binary_tag_size_s;
53     uint8_t *n = 0;
54     err = GetTag("BINARYDATASIZE", config, config_size, n, &binary_tag_size_s);
55     if (err == -1)
56         return LC_ERROR;
57         uint32_t binary_tag_size = (uint32_t)atoi(binary_tag_size_s.c_str());
58 
59     debug("actual data size %i", *binary_size);
60     debug("reported data size %i", binary_tag_size);
61 
62     if (*binary_size != binary_tag_size) {
63         debug("Config data size mismatch");
64         return LC_ERROR;
65     }
66 
67     string s;
68     err = GetTag("CHECKSUM", config, config_size, n, &s);
69     if (err != 0)
70         return err;
71     const uint8_t checksum = atoi(s.c_str());
72 
73     // Calculate checksum
74     uint32_t u = *binary_size;
75     uint8_t calc_checksum = 0x69;
76     uint8_t *pc = *binary_ptr;
77     while (u--)
78         calc_checksum ^= *pc++;
79 
80     debug("reported checksum %i %02x", checksum, checksum);
81     debug("actual checksum %i %02x", calc_checksum, calc_checksum);
82 
83     if (calc_checksum != checksum) {
84         debug("Config checksum mismatch");
85         return LC_ERROR;
86     }
87 
88     return 0;
89 }
90 
ReadZipFile(char * file_name)91 int OperationFile::ReadZipFile(char *file_name)
92 {
93     struct zip *zip = zip_open(file_name, 0, NULL);
94     if (!zip) {
95         return LC_ERROR;
96     }
97 
98     struct zip_stat stat;
99     zip_uint64_t num_entries = zip_get_num_entries(zip, 0);
100     for (zip_uint64_t i = 0; i < num_entries; i++) {
101         zip_stat_index(zip, i, 0, &stat);
102         struct zip_file *file = zip_fopen(zip, stat.name, 0);
103         if ((strcmp(stat.name, "Data.xml") == 0) ||
104             (strcmp(stat.name, "Description.xml") == 0)) {
105             debug("Internal file is %s", stat.name);
106             debug("Size is %lu", stat.size);
107             xml_size = stat.size;
108             xml = new uint8_t[xml_size];
109             zip_fread(file, xml, xml_size);
110             debug("xml is %p, and xmlsize is %d", xml, xml_size);
111         } else {
112             data_size = stat.size;
113             data = new uint8_t[data_size];
114             data_alloc = true;
115             zip_fread(file, data, data_size);
116             debug("data_size is %d", data_size);
117         }
118         zip_fclose(file);
119     }
120     zip_close(zip);
121     return 0;
122 }
123 
ReadPlainFile(char * file_name)124 int OperationFile::ReadPlainFile(char *file_name)
125 {
126     /* Read file */
127     binaryinfile file;
128 
129     if (file.open(file_name) != 0) {
130         debug("Failed to open %s", file_name);
131         return LC_ERROR_OS_FILE;
132     }
133 
134     debug("file opened");
135     uint32_t size = file.getlength();
136     uint8_t *out = new uint8_t[size];
137     file.read(out, size);
138 
139     if (file.close() != 0) {
140         debug("Failed to close %s\n", file_name);
141         return LC_ERROR_OS_FILE;
142     }
143 
144     debug("finding binary bit...");
145     /* Find the config part */
146     find_config_binary(out, size, &data, &data_size);
147 
148     xml = out;
149     xml_size = size - data_size;
150 
151     return 0;
152 }
153 
OperationFile()154 OperationFile::OperationFile()
155 {
156     data_size = xml_size = 0;
157     data = xml = NULL;
158     data_alloc = false;
159 }
160 
~OperationFile()161 OperationFile::~OperationFile()
162 {
163     /*
164      * In certain places, the data pointer is used to point to an area
165      * inside of the xml memory.  In those cases, we don't want to delete
166      * it.  Use the data_alloc variable to keep track of when we've actually
167      * allocated unique memory to it, and in those cases, delete it.
168      */
169     if (data && data_alloc)
170         delete data;
171     if (xml)
172         delete xml;
173 }
174 
_convert_to_binary(string hex,uint8_t * & ptr)175 int _convert_to_binary(string hex, uint8_t *&ptr)
176 {
177     size_t size = hex.length();
178     for (size_t i = 0; i < size; i += 2) {
179         char tmp[6];
180         sprintf(tmp, "0x%s ", hex.substr(i, 2).c_str());
181         ptr[0] = (uint8_t)strtoul(tmp, NULL, 16);
182         ptr++;
183     }
184     return 0;
185 }
186 
_ExtractFirmwareBinary()187 int OperationFile::_ExtractFirmwareBinary()
188 {
189     debug("extracting firmware binary");
190     uint32_t o_size = FIRMWARE_MAX_SIZE;
191     data = new uint8_t[o_size];
192     data_alloc = true;
193     uint8_t *o = data;
194 
195     uint8_t *x = xml;
196     uint8_t *x_new;
197     uint32_t x_size = xml_size;
198 
199     /*
200      * Some remotes (e.g., Arch 7) contain multiple phases in their
201      * firmware update files.  In that case, extract only the first phase.
202      */
203     if (GetTag("PHASE", x, x_size, x_new) == 0) {
204         debug("multi-phase firmware found, extracting 1st phase");
205         x_size = x_size - (x_new - x);
206         x = x_new;
207         uint8_t *phase_end;
208         GetTag("/PHASE", x, x_size, phase_end);
209         x_size = phase_end - x;
210     }
211 
212     string hex;
213     while (GetTag("DATA", x, x_size, x_new, &hex) == 0) {
214         uint32_t hex_size = hex.length() / 2;
215         if (hex_size > o_size) {
216             return LC_ERROR;
217         }
218 
219         _convert_to_binary(hex, o);
220 
221         x_size = x_size - (x_new - x);
222         x = x_new;
223         o_size -= hex_size;
224     }
225 
226     data_size = o - data;
227     debug("acquired firmware binary");
228 
229     return 0;
230 }
231 
ReadAndParseOpFile(char * file_name,int * type)232 int OperationFile::ReadAndParseOpFile(char *file_name, int *type)
233 {
234     debug("In RAPOF");
235     int err;
236 
237     if (file_name == NULL) {
238         debug("Empty file_name");
239         return LC_ERROR_OS_FILE;
240     }
241 
242     bool is_zip = false;
243     if (!ReadZipFile(file_name)) {
244         debug("Is zip");
245         is_zip = true;
246     }
247 
248     if (!is_zip) {
249         if ((err = ReadPlainFile(file_name)))
250             return LC_ERROR_READ;
251     }
252 
253     /* Determine the file type */
254     uint8_t *start_info_ptr, *end_info_ptr;
255     bool has_binary = false;
256     if (data && data_size) {
257         debug("Has binary!");
258         has_binary = true;
259     }
260 
261     /*
262      * Validate this is a remotely sane XML file
263      */
264     debug("determining type...");
265     if (is_zip) {
266         start_info_ptr = xml;
267         end_info_ptr = xml + xml_size;
268     } else {
269         err = GetTag("INFORMATION", xml, xml_size, start_info_ptr);
270         debug("err is %d", err);
271         if (err == -1) {
272             debug("Unable to find INFORMATION tag");
273             return LC_ERROR;
274         }
275         err = GetTag("/INFORMATION", xml, xml_size, end_info_ptr);
276         if (err == -1) {
277             debug("Unable to find /INFORMATION tag");
278             return LC_ERROR;
279         }
280     }
281     debug("start/end pointers populated");
282 
283     /*
284      * Search for tag only in "connectivity test" files
285      */
286     bool found_get_zaps_only = false;
287     uint8_t *tmp_data = xml;
288     uint32_t tmp_size = xml_size;
289     while (1) {
290         uint8_t *tag_ptr;
291         string tag_s;
292         err = GetTag("KEY", tmp_data, tmp_size, tag_ptr, &tag_s);
293         if (err == -1) {
294             debug("not a connectivity test file");
295             break;
296         }
297         if (!stricmp(tag_s.c_str(), "GETZAPSONLY")) {
298             found_get_zaps_only = true;
299             break;
300         }
301         tmp_data = tag_ptr + tag_s.length();
302         tmp_size = end_info_ptr - tmp_data;
303     }
304 
305     /*
306      * Search for tag only in "firmware" files
307      */
308     bool found_firmware = false;
309     tmp_data = xml;
310     tmp_size = xml_size;
311     while (1) {
312         uint8_t *tag_ptr;
313         string tag_s;
314         /*
315          * Firmware files will have either a TYPE or a PATH tag with
316          * either a URL or a Firmware_Main string in them.
317          *
318          * Unless we created them, which is what the DATA check
319          * is for.
320          */
321         err = GetTag("TYPE", tmp_data, tmp_size, tag_ptr, &tag_s);
322         if (err == -1) {
323             err = GetTag("PATH", tmp_data, tmp_size, tag_ptr, &tag_s);
324             if (err == -1) {
325                 debug("not a firmware file");
326                 break;
327             }
328         }
329         if (!stricmp(tag_s.c_str(), "Firmware_Main")) {
330             debug("IS a firmware file");
331             found_firmware = true;
332             break;
333         } else if (!stricmp(tag_s.c_str(), FW_URL)) {
334             debug("IS a firmware file");
335             found_firmware = true;
336             break;
337         }
338         tmp_data = tag_ptr + tag_s.length();
339         tmp_size = end_info_ptr - tmp_data;
340     }
341 
342     if (found_firmware)
343         _ExtractFirmwareBinary();
344 
345     /*
346      * Search for tag only in "IR learning files.
347      */
348     uint8_t *tag_ptr;
349     err = GetTag("CHECKKEYS", xml, xml_size, tag_ptr);
350     bool found_learn_ir = (err != -1);
351 
352     debug("zaps: %d, binary: %d, firmware: %d, ir: %d",
353         found_get_zaps_only, has_binary, found_firmware,
354         found_learn_ir);
355 
356     /*
357      * Check tag search results for consistency, and deduce the file type
358      */
359     if (found_get_zaps_only && !has_binary && !found_firmware &&
360         !found_learn_ir) {
361         debug("returning connect file");
362         *type = LC_FILE_TYPE_CONNECTIVITY;
363         return 0;
364     }
365     if (!found_get_zaps_only && has_binary && data_size >= 16
366         && !found_firmware && !found_learn_ir) {
367         debug("returning conf file");
368         *type = LC_FILE_TYPE_CONFIGURATION;
369         return 0;
370     }
371     if (!found_get_zaps_only && found_firmware && !found_learn_ir) {
372         debug("returning firmware file");
373         *type = LC_FILE_TYPE_FIRMWARE;
374         return 0;
375     }
376     if (!found_get_zaps_only && !found_firmware && found_learn_ir) {
377         debug("returning IR file");
378         *type = LC_FILE_TYPE_LEARN_IR;
379         return 0;
380     }
381     debug("returning error");
382 
383     /*
384      * Findings didn't match a single file type; indicate a problem
385      */
386     return LC_ERROR;
387 }
388