1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <onlineupdate/mar_private.h>
13 #include <onlineupdate/mar_cmdline.h>
14 #include <onlineupdate/mar.h>
15 
16 #ifdef _WIN32
17 #include <winsock2.h>
18 #else
19 #include <netinet/in.h>
20 #include <unistd.h>
21 #endif
22 
23 struct MarItemStack {
24   void *head;
25   uint32_t size_used;
26   uint32_t size_allocated;
27   uint32_t last_offset;
28 };
29 
30 /**
31  * Push a new item onto the stack of items.  The stack is a single block
32  * of memory.
33  */
mar_push(struct MarItemStack * stack,uint32_t length,uint32_t flags,const char * name)34 static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags,
35                     const char *name) {
36   int namelen;
37   uint32_t n_offset, n_length, n_flags;
38   uint32_t size;
39   char *data;
40 
41   namelen = strlen(name);
42   size = MAR_ITEM_SIZE(namelen);
43 
44   if (stack->size_allocated - stack->size_used < size) {
45     /* increase size of stack */
46     size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE);
47     stack->head = realloc(stack->head, size_needed);
48     if (!stack->head)
49       return -1;
50     stack->size_allocated = size_needed;
51   }
52 
53   data = (((char *) stack->head) + stack->size_used);
54 
55   n_offset = htonl(stack->last_offset);
56   n_length = htonl(length);
57   n_flags = htonl(flags);
58 
59   memcpy(data, &n_offset, sizeof(n_offset));
60   data += sizeof(n_offset);
61 
62   memcpy(data, &n_length, sizeof(n_length));
63   data += sizeof(n_length);
64 
65   memcpy(data, &n_flags, sizeof(n_flags));
66   data += sizeof(n_flags);
67 
68   memcpy(data, name, namelen + 1);
69 
70   stack->size_used += size;
71   stack->last_offset += length;
72   return 0;
73 }
74 
mar_concat_file(FILE * fp,const char * path)75 static int mar_concat_file(FILE *fp, const char *path) {
76   FILE *in;
77   char buf[BLOCKSIZE];
78   size_t len;
79   int rv = 0;
80 
81   in = fopen(path, "rb");
82   if (!in) {
83     fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n");
84     perror(path);
85     return -1;
86   }
87 
88   while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) {
89     if (fwrite(buf, len, 1, fp) != 1) {
90       rv = -1;
91       break;
92     }
93   }
94 
95   fclose(in);
96   return rv;
97 }
98 
99 /**
100  * Writes out the product information block to the specified file.
101  *
102  * @param fp           The opened MAR file being created.
103  * @param stack        A pointer to the MAR item stack being used to create
104  *                     the MAR
105  * @param infoBlock    The product info block to store in the file.
106  * @return 0 on success.
107 */
108 static int
mar_concat_product_info_block(FILE * fp,struct MarItemStack * stack,struct ProductInformationBlock * infoBlock)109 mar_concat_product_info_block(FILE *fp,
110                               struct MarItemStack *stack,
111                               struct ProductInformationBlock *infoBlock)
112 {
113   char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE];
114   uint32_t additionalBlockID = 1, infoBlockSize, unused;
115   if (!fp || !infoBlock ||
116       !infoBlock->MARChannelID ||
117       !infoBlock->productVersion) {
118     return -1;
119   }
120 
121   /* The MAR channel name must be < 64 bytes per the spec */
122   if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) {
123     return -1;
124   }
125 
126   /* The product version must be < 32 bytes per the spec */
127   if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) {
128     return -1;
129   }
130 
131   /* Although we don't need the product information block size to include the
132      maximum MAR channel name and product version, we allocate the maximum
133      amount to make it easier to modify the MAR file for repurposing MAR files
134      to different MAR channels. + 2 is for the NULL terminators. */
135   infoBlockSize = sizeof(infoBlockSize) +
136                   sizeof(additionalBlockID) +
137                   PIB_MAX_MAR_CHANNEL_ID_SIZE +
138                   PIB_MAX_PRODUCT_VERSION_SIZE + 2;
139   if (stack) {
140     stack->last_offset += infoBlockSize;
141   }
142 
143   /* Write out the product info block size */
144   infoBlockSize = htonl(infoBlockSize);
145   if (fwrite(&infoBlockSize,
146       sizeof(infoBlockSize), 1, fp) != 1) {
147     return -1;
148   }
149   infoBlockSize = ntohl(infoBlockSize);
150 
151   /* Write out the product info block ID */
152   additionalBlockID = htonl(additionalBlockID);
153   if (fwrite(&additionalBlockID,
154       sizeof(additionalBlockID), 1, fp) != 1) {
155     return -1;
156   }
157   additionalBlockID = ntohl(additionalBlockID);
158 
159   /* Write out the channel name and NULL terminator */
160   if (fwrite(infoBlock->MARChannelID,
161       strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) {
162     return -1;
163   }
164 
165   /* Write out the product version string and NULL terminator */
166   if (fwrite(infoBlock->productVersion,
167       strlen(infoBlock->productVersion) + 1, 1, fp) != 1) {
168     return -1;
169   }
170 
171   /* Write out the rest of the block that is unused */
172   unused = infoBlockSize - (sizeof(infoBlockSize) +
173                             sizeof(additionalBlockID) +
174                             strlen(infoBlock->MARChannelID) +
175                             strlen(infoBlock->productVersion) + 2);
176   memset(buf, 0, sizeof(buf));
177   if (fwrite(buf, unused, 1, fp) != 1) {
178     return -1;
179   }
180   return 0;
181 }
182 
183 /**
184  * Refreshes the product information block with the new information.
185  * The input MAR must not be signed or the function call will fail.
186  *
187  * @param path             The path to the MAR file whose product info block
188  *                         should be refreshed.
189  * @param infoBlock        Out parameter for where to store the result to
190  * @return 0 on success, -1 on failure
191 */
192 int
refresh_product_info_block(const char * path,struct ProductInformationBlock * infoBlock)193 refresh_product_info_block(const char *path,
194                            struct ProductInformationBlock *infoBlock)
195 {
196   FILE *fp ;
197   int rv;
198   uint32_t numSignatures, additionalBlockSize, additionalBlockID,
199     offsetAdditionalBlocks, numAdditionalBlocks, i;
200   int additionalBlocks, hasSignatureBlock;
201 
202   rv = get_mar_file_info(path,
203                          &hasSignatureBlock,
204                          &numSignatures,
205                          &additionalBlocks,
206                          &offsetAdditionalBlocks,
207                          &numAdditionalBlocks);
208   if (rv) {
209     fprintf(stderr, "ERROR: Could not obtain MAR information.\n");
210     return -1;
211   }
212 
213   if (hasSignatureBlock && numSignatures) {
214     fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
215     return -1;
216   }
217 
218   fp = fopen(path, "r+b");
219   if (!fp) {
220     fprintf(stderr, "ERROR: could not open target file: %s\n", path);
221     return -1;
222   }
223 
224   if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
225     fprintf(stderr, "ERROR: could not seek to additional blocks\n");
226     fclose(fp);
227     return -1;
228   }
229 
230   for (i = 0; i < numAdditionalBlocks; ++i) {
231     /* Get the position of the start of this block */
232     int64_t oldPos = ftello(fp);
233 
234     /* Read the additional block size */
235     if (fread(&additionalBlockSize,
236               sizeof(additionalBlockSize),
237               1, fp) != 1) {
238       fclose(fp);
239       return -1;
240     }
241     additionalBlockSize = ntohl(additionalBlockSize);
242 
243     /* Read the additional block ID */
244     if (fread(&additionalBlockID,
245               sizeof(additionalBlockID),
246               1, fp) != 1) {
247       fclose(fp);
248       return -1;
249     }
250     additionalBlockID = ntohl(additionalBlockID);
251 
252     if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
253       if (fseeko(fp, oldPos, SEEK_SET)) {
254         fprintf(stderr, "Could not seek back to Product Information Block\n");
255         fclose(fp);
256         return -1;
257       }
258 
259       if (mar_concat_product_info_block(fp, NULL, infoBlock)) {
260         fprintf(stderr, "Could not concat Product Information Block\n");
261         fclose(fp);
262         return -1;
263       }
264 
265       fclose(fp);
266       return 0;
267     } else {
268       /* This is not the additional block you're looking for. Move along. */
269       if (fseek(fp, additionalBlockSize, SEEK_CUR)) {
270         fprintf(stderr, "ERROR: Could not seek past current block.\n");
271         fclose(fp);
272         return -1;
273       }
274     }
275   }
276 
277   /* If we had a product info block we would have already returned */
278   fclose(fp);
279   fprintf(stderr, "ERROR: Could not refresh because block does not exist\n");
280   return -1;
281 }
282 
283 /**
284  * Create a MAR file from a set of files.
285  * @param dest      The path to the file to create.  This path must be
286  *                  compatible with fopen.
287  * @param numfiles  The number of files to store in the archive.
288  * @param files     The list of null-terminated file paths.  Each file
289  *                  path must be compatible with fopen.
290  * @param infoBlock The information to store in the product information block.
291  * @return A non-zero value if an error occurs.
292  */
mar_create(const char * dest,int num_files,char ** files,struct ProductInformationBlock * infoBlock)293 int mar_create(const char *dest, int
294                num_files, char **files,
295                struct ProductInformationBlock *infoBlock) {
296   struct MarItemStack stack;
297   uint32_t offset_to_index = 0, size_of_index,
298     numSignatures, numAdditionalSections;
299   uint64_t sizeOfEntireMAR = 0;
300   struct stat st;
301   FILE *fp;
302   int i, rv = -1;
303 
304   memset(&stack, 0, sizeof(stack));
305 
306   fp = fopen(dest, "wb");
307   if (!fp) {
308     fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
309     return -1;
310   }
311 
312   if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1)
313     goto failure;
314   if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1)
315     goto failure;
316 
317   stack.last_offset = MAR_ID_SIZE +
318                       sizeof(offset_to_index) +
319                       sizeof(numSignatures) +
320                       sizeof(numAdditionalSections) +
321                       sizeof(sizeOfEntireMAR);
322 
323   /* We will circle back on this at the end of the MAR creation to fill it */
324   if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
325     goto failure;
326   }
327 
328   /* Write out the number of signatures, for now only at most 1 is supported */
329   numSignatures = 0;
330   if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
331     goto failure;
332   }
333 
334   /* Write out the number of additional sections, for now just 1
335      for the product info block */
336   numAdditionalSections = htonl(1);
337   if (fwrite(&numAdditionalSections,
338              sizeof(numAdditionalSections), 1, fp) != 1) {
339     goto failure;
340   }
341   numAdditionalSections = ntohl(numAdditionalSections);
342 
343   if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
344     goto failure;
345   }
346 
347   for (i = 0; i < num_files; ++i) {
348     if (stat(files[i], &st)) {
349       fprintf(stderr, "ERROR: file not found: %s\n", files[i]);
350       goto failure;
351     }
352 
353     if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i]))
354       goto failure;
355 
356     /* concatenate input file to archive */
357     if (mar_concat_file(fp, files[i]))
358       goto failure;
359   }
360 
361   /* write out the index (prefixed with length of index) */
362   size_of_index = htonl(stack.size_used);
363   if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1)
364     goto failure;
365   if (fwrite(stack.head, stack.size_used, 1, fp) != 1)
366     goto failure;
367 
368   /* To protect against invalid MAR files, we assume that the MAR file
369      size is less than or equal to MAX_SIZE_OF_MAR_FILE. */
370   if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) {
371     goto failure;
372   }
373 
374   /* write out offset to index file in network byte order */
375   offset_to_index = htonl(stack.last_offset);
376   if (fseek(fp, MAR_ID_SIZE, SEEK_SET))
377     goto failure;
378   if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1)
379     goto failure;
380   offset_to_index = ntohl(stack.last_offset);
381 
382   sizeOfEntireMAR = ((uint64_t)stack.last_offset) +
383                     stack.size_used +
384                     sizeof(size_of_index);
385   sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
386   if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1)
387     goto failure;
388   sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
389 
390   rv = 0;
391 failure:
392   if (stack.head)
393     free(stack.head);
394   fclose(fp);
395   if (rv)
396     remove(dest);
397   return rv;
398 }
399