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 "mar_private.h"
13 #include "mar_cmdline.h"
14 #include "mar.h"
15 
16 #ifdef XP_WIN
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   int64_t oldPos;
202 
203   rv = get_mar_file_info(path,
204                          &hasSignatureBlock,
205                          &numSignatures,
206                          &additionalBlocks,
207                          &offsetAdditionalBlocks,
208                          &numAdditionalBlocks);
209   if (rv) {
210     fprintf(stderr, "ERROR: Could not obtain MAR information.\n");
211     return -1;
212   }
213 
214   if (hasSignatureBlock && numSignatures) {
215     fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n");
216     return -1;
217   }
218 
219   fp = fopen(path, "r+b");
220   if (!fp) {
221     fprintf(stderr, "ERROR: could not open target file: %s\n", path);
222     return -1;
223   }
224 
225   if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) {
226     fprintf(stderr, "ERROR: could not seek to additional blocks\n");
227     fclose(fp);
228     return -1;
229   }
230 
231   for (i = 0; i < numAdditionalBlocks; ++i) {
232     /* Get the position of the start of this block */
233     oldPos = ftello(fp);
234 
235     /* Read the additional block size */
236     if (fread(&additionalBlockSize,
237               sizeof(additionalBlockSize),
238               1, fp) != 1) {
239       fclose(fp);
240       return -1;
241     }
242     additionalBlockSize = ntohl(additionalBlockSize);
243 
244     /* Read the additional block ID */
245     if (fread(&additionalBlockID,
246               sizeof(additionalBlockID),
247               1, fp) != 1) {
248       fclose(fp);
249       return -1;
250     }
251     additionalBlockID = ntohl(additionalBlockID);
252 
253     if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) {
254       if (fseeko(fp, oldPos, SEEK_SET)) {
255         fprintf(stderr, "Could not seek back to Product Information Block\n");
256         fclose(fp);
257         return -1;
258       }
259 
260       if (mar_concat_product_info_block(fp, NULL, infoBlock)) {
261         fprintf(stderr, "Could not concat Product Information Block\n");
262         fclose(fp);
263         return -1;
264       }
265 
266       fclose(fp);
267       return 0;
268     } else {
269       /* This is not the additional block you're looking for. Move along. */
270       if (fseek(fp, additionalBlockSize, SEEK_CUR)) {
271         fprintf(stderr, "ERROR: Could not seek past current block.\n");
272         fclose(fp);
273         return -1;
274       }
275     }
276   }
277 
278   /* If we had a product info block we would have already returned */
279   fclose(fp);
280   fprintf(stderr, "ERROR: Could not refresh because block does not exist\n");
281   return -1;
282 }
283 
284 /**
285  * Create a MAR file from a set of files.
286  * @param dest      The path to the file to create.  This path must be
287  *                  compatible with fopen.
288  * @param numfiles  The number of files to store in the archive.
289  * @param files     The list of null-terminated file paths.  Each file
290  *                  path must be compatible with fopen.
291  * @param infoBlock The information to store in the product information block.
292  * @return A non-zero value if an error occurs.
293  */
mar_create(const char * dest,int num_files,char ** files,struct ProductInformationBlock * infoBlock)294 int mar_create(const char *dest, int
295                num_files, char **files,
296                struct ProductInformationBlock *infoBlock) {
297   struct MarItemStack stack;
298   uint32_t offset_to_index = 0, size_of_index,
299     numSignatures, numAdditionalSections;
300   uint64_t sizeOfEntireMAR = 0;
301   struct stat st;
302   FILE *fp;
303   int i, rv = -1;
304 
305   memset(&stack, 0, sizeof(stack));
306 
307   fp = fopen(dest, "wb");
308   if (!fp) {
309     fprintf(stderr, "ERROR: could not create target file: %s\n", dest);
310     return -1;
311   }
312 
313   if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1)
314     goto failure;
315   if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1)
316     goto failure;
317 
318   stack.last_offset = MAR_ID_SIZE +
319                       sizeof(offset_to_index) +
320                       sizeof(numSignatures) +
321                       sizeof(numAdditionalSections) +
322                       sizeof(sizeOfEntireMAR);
323 
324   /* We will circle back on this at the end of the MAR creation to fill it */
325   if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) {
326     goto failure;
327   }
328 
329   /* Write out the number of signatures, for now only at most 1 is supported */
330   numSignatures = 0;
331   if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) {
332     goto failure;
333   }
334 
335   /* Write out the number of additional sections, for now just 1
336      for the product info block */
337   numAdditionalSections = htonl(1);
338   if (fwrite(&numAdditionalSections,
339              sizeof(numAdditionalSections), 1, fp) != 1) {
340     goto failure;
341   }
342   numAdditionalSections = ntohl(numAdditionalSections);
343 
344   if (mar_concat_product_info_block(fp, &stack, infoBlock)) {
345     goto failure;
346   }
347 
348   for (i = 0; i < num_files; ++i) {
349     if (stat(files[i], &st)) {
350       fprintf(stderr, "ERROR: file not found: %s\n", files[i]);
351       goto failure;
352     }
353 
354     if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i]))
355       goto failure;
356 
357     /* concatenate input file to archive */
358     if (mar_concat_file(fp, files[i]))
359       goto failure;
360   }
361 
362   /* write out the index (prefixed with length of index) */
363   size_of_index = htonl(stack.size_used);
364   if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1)
365     goto failure;
366   if (fwrite(stack.head, stack.size_used, 1, fp) != 1)
367     goto failure;
368 
369   /* To protect against invalid MAR files, we assumes that the MAR file
370      size is less than or equal to MAX_SIZE_OF_MAR_FILE. */
371   if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) {
372     goto failure;
373   }
374 
375   /* write out offset to index file in network byte order */
376   offset_to_index = htonl(stack.last_offset);
377   if (fseek(fp, MAR_ID_SIZE, SEEK_SET))
378     goto failure;
379   if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1)
380     goto failure;
381   offset_to_index = ntohl(stack.last_offset);
382 
383   sizeOfEntireMAR = ((uint64_t)stack.last_offset) +
384                     stack.size_used +
385                     sizeof(size_of_index);
386   sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR);
387   if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1)
388     goto failure;
389   sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR);
390 
391   rv = 0;
392 failure:
393   if (stack.head)
394     free(stack.head);
395   fclose(fp);
396   if (rv)
397     remove(dest);
398   return rv;
399 }
400