/* ** Copyright 1998-2003 University of Illinois Board of Trustees ** Copyright 1998-2003 Mark D. Roth ** All rights reserved. ** ** block.c - libtar code to handle tar archive header blocks ** ** Mark D. Roth ** Campus Information Technologies and Educational Services ** University of Illinois at Urbana-Champaign */ #include #include #ifdef STDC_HEADERS # include # include #endif #define BIT_ISSET(bitmask, bit) ((bitmask) & (bit)) // Used to identify selinux_context in extended ('x') // metadata. From RedHat implementation. #define SELINUX_TAG "RHT.security.selinux=" #define SELINUX_TAG_LEN 21 /* read a header block */ int th_read_internal(TAR *t) { int i; int num_zero_blocks = 0; #ifdef DEBUG printf("==> th_read_internal(TAR=\"%s\")\n", t->pathname); #endif while ((i = tar_block_read(t, &(t->th_buf))) == T_BLOCKSIZE) { /* two all-zero blocks mark EOF */ if (t->th_buf.name[0] == '\0') { num_zero_blocks++; if (!BIT_ISSET(t->options, TAR_IGNORE_EOT) && num_zero_blocks >= 2) return 0; /* EOF */ else continue; } /* verify magic and version */ if (BIT_ISSET(t->options, TAR_CHECK_MAGIC) && strncmp(t->th_buf.magic, TMAGIC, TMAGLEN - 1) != 0) { #ifdef DEBUG puts("!!! unknown magic value in tar header"); #endif return -2; } if (BIT_ISSET(t->options, TAR_CHECK_VERSION) && strncmp(t->th_buf.version, TVERSION, TVERSLEN) != 0) { #ifdef DEBUG puts("!!! unknown version value in tar header"); #endif return -2; } /* check chksum */ if (!BIT_ISSET(t->options, TAR_IGNORE_CRC) && !th_crc_ok(t)) { #ifdef DEBUG puts("!!! tar header checksum error"); #endif return -2; } break; } #ifdef DEBUG printf("<== th_read_internal(): returning %d\n", i); #endif return i; } /* wrapper function for th_read_internal() to handle GNU extensions */ int th_read(TAR *t) { int i, j; size_t sz; char *ptr; #ifdef DEBUG printf("==> th_read(t=0x%lx)\n", t); #endif if (t->th_buf.gnu_longname != NULL) free(t->th_buf.gnu_longname); if (t->th_buf.gnu_longlink != NULL) free(t->th_buf.gnu_longlink); #ifdef HAVE_SELINUX if (t->th_buf.selinux_context != NULL) free(t->th_buf.selinux_context); #endif memset(&(t->th_buf), 0, sizeof(struct tar_header)); i = th_read_internal(t); if (i == 0) return 1; else if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } /* check for GNU long link extention */ if (TH_ISLONGLINK(t)) { sz = th_get_size(t); j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0); #ifdef DEBUG printf(" th_read(): GNU long linkname detected " "(%ld bytes, %d blocks)\n", sz, j); #endif t->th_buf.gnu_longlink = (char *)malloc(j * T_BLOCKSIZE); if (t->th_buf.gnu_longlink == NULL) return -1; for (ptr = t->th_buf.gnu_longlink; j > 0; j--, ptr += T_BLOCKSIZE) { #ifdef DEBUG printf(" th_read(): reading long linkname " "(%d blocks left, ptr == %ld)\n", j, ptr); #endif i = tar_block_read(t, ptr); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } #ifdef DEBUG printf(" th_read(): read block == \"%s\"\n", ptr); #endif } #ifdef DEBUG printf(" th_read(): t->th_buf.gnu_longlink == \"%s\"\n", t->th_buf.gnu_longlink); #endif i = th_read_internal(t); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } } /* check for GNU long name extention */ if (TH_ISLONGNAME(t)) { sz = th_get_size(t); j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0); #ifdef DEBUG printf(" th_read(): GNU long filename detected " "(%ld bytes, %d blocks)\n", sz, j); #endif t->th_buf.gnu_longname = (char *)malloc(j * T_BLOCKSIZE); if (t->th_buf.gnu_longname == NULL) return -1; for (ptr = t->th_buf.gnu_longname; j > 0; j--, ptr += T_BLOCKSIZE) { #ifdef DEBUG printf(" th_read(): reading long filename " "(%d blocks left, ptr == %ld)\n", j, ptr); #endif i = tar_block_read(t, ptr); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } #ifdef DEBUG printf(" th_read(): read block == \"%s\"\n", ptr); #endif } #ifdef DEBUG printf(" th_read(): t->th_buf.gnu_longname == \"%s\"\n", t->th_buf.gnu_longname); #endif i = th_read_internal(t); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } } #ifdef HAVE_SELINUX if(TH_ISEXTHEADER(t)) { sz = th_get_size(t); if(sz >= T_BLOCKSIZE) // Not supported { #ifdef DEBUG printf(" th_read(): Extended header is too long!\n"); #endif } else { char buf[T_BLOCKSIZE]; i = tar_block_read(t, buf); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } // To be sure buf[T_BLOCKSIZE-1] = 0; int len = strlen(buf); char *start = strstr(buf, SELINUX_TAG); if(start && start+SELINUX_TAG_LEN < buf+len) { start += SELINUX_TAG_LEN; char *end = strchr(start, '\n'); if(end) { t->th_buf.selinux_context = strndup(start, end-start); #ifdef DEBUG printf(" th_read(): SELinux context xattr detected: %s\n", t->th_buf.selinux_context); #endif } } } i = th_read_internal(t); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } } #endif #if 0 /* ** work-around for old archive files with broken typeflag fields ** NOTE: I fixed this in the TH_IS*() macros instead */ /* ** (directories are signified with a trailing '/') */ if (t->th_buf.typeflag == AREGTYPE && t->th_buf.name[strlen(t->th_buf.name) - 1] == '/') t->th_buf.typeflag = DIRTYPE; /* ** fallback to using mode bits */ if (t->th_buf.typeflag == AREGTYPE) { mode = (mode_t)oct_to_int(t->th_buf.mode); if (S_ISREG(mode)) t->th_buf.typeflag = REGTYPE; else if (S_ISDIR(mode)) t->th_buf.typeflag = DIRTYPE; else if (S_ISFIFO(mode)) t->th_buf.typeflag = FIFOTYPE; else if (S_ISCHR(mode)) t->th_buf.typeflag = CHRTYPE; else if (S_ISBLK(mode)) t->th_buf.typeflag = BLKTYPE; else if (S_ISLNK(mode)) t->th_buf.typeflag = SYMTYPE; } #endif return 0; } /* write a header block */ int th_write(TAR *t) { int i, j; char type2; size_t sz, sz2; char *ptr; char buf[T_BLOCKSIZE]; #ifdef DEBUG printf("==> th_write(TAR=\"%s\")\n", t->pathname); th_print(t); #endif if ((t->options & TAR_GNU) && t->th_buf.gnu_longlink != NULL) { #ifdef DEBUG printf("th_write(): using gnu_longlink (\"%s\")\n", t->th_buf.gnu_longlink); #endif /* save old size and type */ type2 = t->th_buf.typeflag; sz2 = th_get_size(t); /* write out initial header block with fake size and type */ t->th_buf.typeflag = GNU_LONGLINK_TYPE; sz = strlen(t->th_buf.gnu_longlink); th_set_size(t, sz); th_finish(t); i = tar_block_write(t, &(t->th_buf)); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } /* write out extra blocks containing long name */ for (j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0), ptr = t->th_buf.gnu_longlink; j > 1; j--, ptr += T_BLOCKSIZE) { i = tar_block_write(t, ptr); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } } memset(buf, 0, T_BLOCKSIZE); strncpy(buf, ptr, T_BLOCKSIZE); i = tar_block_write(t, &buf); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } /* reset type and size to original values */ t->th_buf.typeflag = type2; th_set_size(t, sz2); } if ((t->options & TAR_GNU) && t->th_buf.gnu_longname != NULL) { #ifdef DEBUG printf("th_write(): using gnu_longname (\"%s\")\n", t->th_buf.gnu_longname); #endif /* save old size and type */ type2 = t->th_buf.typeflag; sz2 = th_get_size(t); /* write out initial header block with fake size and type */ t->th_buf.typeflag = GNU_LONGNAME_TYPE; sz = strlen(t->th_buf.gnu_longname); th_set_size(t, sz); th_finish(t); i = tar_block_write(t, &(t->th_buf)); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } /* write out extra blocks containing long name */ for (j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0), ptr = t->th_buf.gnu_longname; j > 1; j--, ptr += T_BLOCKSIZE) { i = tar_block_write(t, ptr); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } } memset(buf, 0, T_BLOCKSIZE); strncpy(buf, ptr, T_BLOCKSIZE); i = tar_block_write(t, &buf); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } /* reset type and size to original values */ t->th_buf.typeflag = type2; th_set_size(t, sz2); } #ifdef HAVE_SELINUX if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL) { #ifdef DEBUG printf("th_write(): using selinux_context (\"%s\")\n", t->th_buf.selinux_context); #endif /* save old size and type */ type2 = t->th_buf.typeflag; sz2 = th_get_size(t); /* write out initial header block with fake size and type */ t->th_buf.typeflag = TH_EXT_TYPE; /* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */ // size newline sz = SELINUX_TAG_LEN + strlen(t->th_buf.selinux_context) + 3 + 1; if(sz >= 100) // another ascci digit for size ++sz; if(sz >= T_BLOCKSIZE) // impossible { errno = EINVAL; return -1; } th_set_size(t, sz); th_finish(t); i = tar_block_write(t, &(t->th_buf)); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } memset(buf, 0, T_BLOCKSIZE); snprintf(buf, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", sz, t->th_buf.selinux_context); i = tar_block_write(t, &buf); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } /* reset type and size to original values */ t->th_buf.typeflag = type2; th_set_size(t, sz2); } #endif th_finish(t); #ifdef DEBUG /* print tar header */ th_print(t); #endif i = tar_block_write(t, &(t->th_buf)); if (i != T_BLOCKSIZE) { if (i != -1) errno = EINVAL; return -1; } #ifdef DEBUG puts("th_write(): returning 0"); #endif return 0; }