#include #include #include #include #include /* Header for ustar-format tar archive. See the documentation of the "pax" utility in [SUSv3] for the the "ustar" format specification. */ struct ustar_header { char name[100]; /* File name. Null-terminated if room. */ char mode[8]; /* Permissions as octal string. */ char uid[8]; /* User ID as octal string. */ char gid[8]; /* Group ID as octal string. */ char size[12]; /* File size in bytes as octal string. */ char mtime[12]; /* Modification time in seconds from Jan 1, 1970, as octal string. */ char chksum[8]; /* Sum of octets in header as octal string. */ char typeflag; /* An enum ustar_type value. */ char linkname[100]; /* Name of link target. Null-terminated if room. */ char magic[6]; /* "ustar\0" */ char version[2]; /* "00" */ char uname[32]; /* User name, always null-terminated. */ char gname[32]; /* Group name, always null-terminated. */ char devmajor[8]; /* Device major number as octal string. */ char devminor[8]; /* Device minor number as octal string. */ char prefix[155]; /* Prefix to file name. Null-terminated if room. */ char padding[12]; /* Pad to 512 bytes. */ } PACKED; /* Returns the checksum for the given ustar format HEADER. */ static unsigned int calculate_chksum (const struct ustar_header *h) { const uint8_t *header = (const uint8_t *) h; unsigned int chksum; size_t i; chksum = 0; for (i = 0; i < USTAR_HEADER_SIZE; i++) { /* The ustar checksum is calculated as if the chksum field were all spaces. */ const size_t chksum_start = offsetof (struct ustar_header, chksum); const size_t chksum_end = chksum_start + sizeof h->chksum; bool in_chksum_field = i >= chksum_start && i < chksum_end; chksum += in_chksum_field ? ' ' : header[i]; } return chksum; } /* Drop possibly dangerous prefixes from FILE_NAME and return the stripped name. An archive with file names that start with "/" or "../" could cause a naive tar extractor to write to arbitrary parts of the file system, not just the destination directory. We don't want to create such archives or be such a naive extractor. The return value can be a suffix of FILE_NAME or a string literal. */ static const char * strip_antisocial_prefixes (const char *file_name) { while (*file_name == '/' || !memcmp (file_name, "./", 2) || !memcmp (file_name, "../", 3)) file_name = strchr (file_name, '/') + 1; return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name; } /* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive header in ustar format for a SIZE-byte file named FILE_NAME of the given TYPE. The caller is responsible for writing the header to a file or device. If successful, returns true. On failure (due to an excessively long file name), returns false. */ bool ustar_make_header (const char *file_name, enum ustar_type type, int size, char header[USTAR_HEADER_SIZE]) { struct ustar_header *h = (struct ustar_header *) header; ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY); /* Check file name. */ file_name = strip_antisocial_prefixes (file_name); if (strlen (file_name) > 99) { printf ("%s: file name too long\n", file_name); return false; } /* Fill in header except for final checksum. */ memset (h, 0, sizeof *h); strlcpy (h->name, file_name, sizeof h->name); snprintf (h->mode, sizeof h->mode, "%07o", type == USTAR_REGULAR ? 0644 : 0755); strlcpy (h->uid, "0000000", sizeof h->uid); strlcpy (h->gid, "0000000", sizeof h->gid); snprintf (h->size, sizeof h->size, "%011o", size); snprintf (h->mtime, sizeof h->size, "%011o", 1136102400); h->typeflag = type; strlcpy (h->magic, "ustar", sizeof h->magic); h->version[0] = h->version[1] = '0'; strlcpy (h->gname, "root", sizeof h->gname); strlcpy (h->uname, "root", sizeof h->uname); /* Compute and fill in final checksum. */ snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h)); return true; } /* Parses a SIZE-byte octal field in S in the format used by ustar format. If successful, stores the field's value in *VALUE and returns true; on failure, returns false. ustar octal fields consist of a sequence of octal digits terminated by a space or a null byte. The ustar specification seems ambiguous as to whether these fields must be padded on the left with '0's, so we accept any field that fits in the available space, regardless of whether it fills the space. */ static bool parse_octal_field (const char *s, size_t size, unsigned long int *value) { size_t ofs; *value = 0; for (ofs = 0; ofs < size; ofs++) { char c = s[ofs]; if (c >= '0' && c <= '7') { if (*value > ULONG_MAX / 8) { /* Overflow. */ return false; } *value = c - '0' + *value * 8; } else if (c == ' ' || c == '\0') { /* End of field, but disallow completely empty fields. */ return ofs > 0; } else { /* Bad character. */ return false; } } /* Field did not end in space or null byte. */ return false; } /* Returns true if the CNT bytes starting at BLOCK are all zero, false otherwise. */ static bool is_all_zeros (const char *block, size_t cnt) { while (cnt-- > 0) if (*block++ != 0) return false; return true; } /* Parses HEADER as a ustar-format archive header for a regular file or directory. If successful, stores the archived file's name in *FILE_NAME (as a pointer into HEADER or a string literal), its type in *TYPE, and its size in bytes in *SIZE, and returns a null pointer. On failure, returns a human-readable error message. */ const char * ustar_parse_header (const char header[USTAR_HEADER_SIZE], const char **file_name, enum ustar_type *type, int *size) { const struct ustar_header *h = (const struct ustar_header *) header; unsigned long int chksum, size_ul; ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); /* Detect end of archive. */ if (is_all_zeros (header, USTAR_HEADER_SIZE)) { *file_name = NULL; *type = USTAR_EOF; *size = 0; return NULL; } /* Validate ustar header. */ if (memcmp (h->magic, "ustar", 6)) return "not a ustar archive"; else if (h->version[0] != '0' || h->version[1] != '0') return "invalid ustar version"; else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum)) return "corrupt chksum field"; else if (chksum != calculate_chksum (h)) return "checksum mismatch"; else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0') return "file name too long"; else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY) return "unimplemented file type"; if (h->typeflag == USTAR_REGULAR) { if (!parse_octal_field (h->size, sizeof h->size, &size_ul)) return "corrupt file size field"; else if (size_ul > INT_MAX) return "file too large"; } else size_ul = 0; /* Success. */ *file_name = strip_antisocial_prefixes (h->name); *type = h->typeflag; *size = size_ul; return NULL; }