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