OSDN Git Service

Fix warning message on extracting symlink.
[lha/lha.git] / src / lhext.c
index 7e9c17e..ad6ec1b 100644 (file)
@@ -22,9 +22,17 @@ static char    *methods[] =
     LZHUFF4_METHOD, LZHUFF5_METHOD, LZHUFF6_METHOD, LZHUFF7_METHOD,
     LARC_METHOD, LARC5_METHOD, LARC4_METHOD,
     LZHDIRS_METHOD,
+    PMARC0_METHOD, PMARC2_METHOD,
     NULL
 };
 
+static void add_dirinfo(char* name, LzHeader* hdr);
+static void adjust_dirinfo();
+
+#ifdef HAVE_LIBAPPLEFILE
+static boolean decode_macbinary(FILE *ofp, size_t size, const char *outPath);
+#endif
+
 /* ------------------------------------------------------------------------ */
 static          boolean
 inquire_extract(name)
@@ -75,6 +83,49 @@ inquire_extract(name)
     return TRUE;
 }
 
+static boolean
+make_name_with_pathcheck(char *name, size_t namesz, const char *q)
+{
+    int offset = 0;
+    const char *p;
+    int sz;
+    struct stat stbuf;
+
+    if (extract_directory) {
+        sz = xsnprintf(name, namesz, "%s/", extract_directory);
+        if (sz == -1) {
+            return FALSE;
+        }
+        offset += sz;
+    }
+
+#ifdef S_IFLNK
+    while ((p = strchr(q, '/')) != NULL) {
+        if (namesz - offset < (p - q) + 2) {
+            return FALSE;
+        }
+        memcpy(name + offset, q, (p - q));
+        name[offset + (p - q)] = 0;
+
+        offset += (p - q);
+        q = p + 1;
+
+        if (lstat(name, &stbuf) < 0) {
+            name[offset++] = '/';
+            break;
+        }
+        if (is_symlink(&stbuf)) {
+            return FALSE;
+        }
+        name[offset++] = '/';
+    }
+#endif
+
+    str_safe_copy(name + offset, q, namesz - offset);
+
+    return TRUE;
+}
+
 /* ------------------------------------------------------------------------ */
 static          boolean
 make_parent_path(name)
@@ -85,7 +136,7 @@ make_parent_path(name)
     register char  *p;
 
     /* make parent directory name into PATH for recursive call */
-    strcpy(path, name);
+    str_safe_copy(path, name, sizeof(path));
     for (p = path + strlen(path); p > path; p--)
         if (p[-1] == '/') {
             *--p = '\0';
@@ -147,6 +198,23 @@ open_with_make_path(name)
 }
 
 /* ------------------------------------------------------------------------ */
+static int
+symlink_with_make_path(realname, name)
+    const char     *realname;
+    const char     *name;
+{
+    int l_code;
+
+    l_code = symlink(realname, name);
+    if (l_code < 0) {
+        make_parent_path(name);
+        l_code = symlink(realname, name);
+    }
+
+    return l_code;
+}
+
+/* ------------------------------------------------------------------------ */
 static void
 adjust_info(name, hdr)
     char           *name;
@@ -163,12 +231,11 @@ adjust_info(name, hdr)
     if (hdr->extend_type == EXTEND_UNIX
         || hdr->extend_type == EXTEND_OS68K
         || hdr->extend_type == EXTEND_XOSK) {
-#ifdef NOT_COMPATIBLE_MODE
-        Please need your modification in this space.
-#else
-        if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_SYMLINK)
+
+        if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_SYMLINK) {
             chmod(name, hdr->unix_mode);
-#endif
+        }
+
         if (!getuid()){
             uid_t uid = hdr->unix_uid;
             gid_t gid = hdr->unix_gid;
@@ -204,26 +271,35 @@ adjust_info(name, hdr)
 }
 
 /* ------------------------------------------------------------------------ */
-static size_t
+static off_t
 extract_one(afp, hdr)
     FILE           *afp;    /* archive file */
     LzHeader       *hdr;
 {
     FILE           *fp; /* output file */
+#if HAVE_LIBAPPLEFILE
+    FILE           *tfp; /* temporary output file */
+#endif
     struct stat     stbuf;
     char            name[FILENAME_LENGTH];
     unsigned int crc;
     int             method;
     boolean         save_quiet, save_verbose, up_flag;
     char           *q = hdr->name, c;
-    size_t read_size = 0;
+    off_t read_size = 0;
 
     if (ignore_directory && strrchr(hdr->name, '/')) {
         q = (char *) strrchr(hdr->name, '/') + 1;
     }
     else {
+        if (is_directory_traversal(q)) {
+            error("Possible directory traversal hack attempt in %s", q);
+            exit(1);
+        }
+
         if (*q == '/') {
-            q++;
+            while (*q == '/') { q++; }
+
             /*
              * if OSK then strip device name
              */
@@ -238,17 +314,16 @@ extract_one(afp, hdr)
         }
     }
 
-    if (extract_directory)
-        xsnprintf(name, sizeof(name), "%s/%s", extract_directory, q);
-    else
-        strcpy(name, q);
-
+    if (!make_name_with_pathcheck(name, sizeof(name), q)) {
+        error("Possible symlink traversal hack attempt in %s", q);
+        exit(1);
+    }
 
-    /* LZHDIRS_METHOD¤ò»ý¤Ä¥Ø¥Ã¥À¤ò¥Á¥§¥Ã¥¯¤¹¤ë */
+    /* LZHDIRS_METHODを持つヘッダをチェックする */
     /* 1999.4.30 t.okamoto */
     for (method = 0;; method++) {
         if (methods[method] == NULL) {
-            error("Unknown method \"%.*s\"; \"%s\" will be skiped ...",
+            error("Unknown method \"%.*s\"; \"%s\" will be skipped ...",
                   5, hdr->method, name);
             return read_size;
         }
@@ -262,7 +337,7 @@ extract_one(afp, hdr)
 #if 0
         for (method = 0;; method++) {
             if (methods[method] == NULL) {
-                error("Unknown method \"%.*s\"; \"%s\" will be skiped ...",
+                error("Unknown method \"%.*s\"; \"%s\" will be skipped ...",
                       5, hdr->method, name);
                 return read_size;
             }
@@ -274,6 +349,14 @@ extract_one(afp, hdr)
         reading_filename = archive_name;
         writing_filename = name;
         if (output_to_stdout || verify_mode) {
+            /* "Icon\r" should be a resource fork file encoded in MacBinary
+               format, so that it should be skipped. */
+            if (hdr->extend_type == EXTEND_MACOS
+                && strcmp(basename(name), "Icon\r") == 0
+                && decode_macbinary_contents) {
+                return read_size;
+            }
+
             if (noexec) {
                 printf("%s %s\n", verify_mode ? "VERIFY" : "EXTRACT", name);
                 return read_size;
@@ -291,17 +374,37 @@ extract_one(afp, hdr)
                 verbose = TRUE;
             }
 
-#if __MINGW32__
+#if defined(__MINGW32__) || defined(__DJGPP__)
             {
                 int old_mode;
                 fflush(stdout);
                 old_mode = setmode(fileno(stdout), O_BINARY);
 #endif
 
+#if HAVE_LIBAPPLEFILE
+            /* On default, MacLHA encodes into MacBinary. */
+            if (hdr->extend_type == EXTEND_MACOS && !verify_mode && decode_macbinary_contents) {
+                /* build temporary file */
+                tfp = NULL; /* avoid compiler warnings `uninitialized' */
+                tfp = build_temporary_file();
+
+                crc = decode_lzhuf(afp, tfp,
+                                   hdr->original_size, hdr->packed_size,
+                                   name, method, &read_size);
+                fclose(tfp);
+                decode_macbinary(stdout, hdr->original_size, name);
+                unlink(temporary_name);
+            } else {
+                crc = decode_lzhuf(afp, stdout,
+                                   hdr->original_size, hdr->packed_size,
+                                   name, method, &read_size);
+            }
+#else
             crc = decode_lzhuf(afp, stdout,
                                hdr->original_size, hdr->packed_size,
                                name, method, &read_size);
-#if __MINGW32__
+#endif /* HAVE_LIBAPPLEFILE */
+#if defined(__MINGW32__) || defined(__DJGPP__)
                 fflush(stdout);
                 setmode(fileno(stdout), old_mode);
             }
@@ -310,6 +413,16 @@ extract_one(afp, hdr)
             verbose = save_verbose;
         }
         else {
+#ifndef __APPLE__
+            /* "Icon\r" should be a resource fork of parent folder's icon,
+               so that it can be skipped when system is not Mac OS X. */
+            if (hdr->extend_type == EXTEND_MACOS
+                && strcmp(basename(name), "Icon\r") == 0
+                && decode_macbinary_contents) {
+                make_parent_path(name); /* create directory only */
+                return read_size;
+            }
+#endif /* __APPLE__ */
             if (skip_flg == FALSE)  {
                 up_flag = inquire_extract(name);
                 if (up_flag == FALSE && force == FALSE) {
@@ -339,9 +452,35 @@ extract_one(afp, hdr)
             remove_extracting_file_when_interrupt = TRUE;
 
             if ((fp = open_with_make_path(name)) != NULL) {
+#if HAVE_LIBAPPLEFILE
+                if (hdr->extend_type == EXTEND_MACOS && !verify_mode && decode_macbinary_contents) {
+                    /* build temporary file */
+                    tfp = NULL; /* avoid compiler warnings `uninitialized' */
+                    tfp = build_temporary_file();
+
+                    crc = decode_lzhuf(afp, tfp,
+                                       hdr->original_size, hdr->packed_size,
+                                       name, method, &read_size);
+                    fclose(tfp);
+                    decode_macbinary(fp, hdr->original_size, name);
+#ifdef __APPLE__
+                    /* TODO: set resource fork */
+                    /* after processing, "Icon\r" is not needed. */
+                    if (strcmp(basename(name), "Icon\r") == 0) {
+                        unlink(name);
+                    }
+#endif /* __APPLE__ */
+                    unlink(temporary_name);
+                } else {
+                    crc = decode_lzhuf(afp, fp,
+                                       hdr->original_size, hdr->packed_size,
+                                       name, method, &read_size);
+                }
+#else /* HAVE_LIBAPPLEFILE */
                 crc = decode_lzhuf(afp, fp,
                                    hdr->original_size, hdr->packed_size,
                                    name, method, &read_size);
+#endif /* HAVE_LIBAPPLEFILE */
                 fclose(fp);
             }
             remove_extracting_file_when_interrupt = FALSE;
@@ -359,8 +498,8 @@ extract_one(afp, hdr)
     else if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_DIRECTORY
              || (hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK
              || method == LZHDIRS_METHOD_NUM) {
-        /* ¢¬¤³¤ì¤Ç¡¢Symbolic Link ¤Ï¡¢Âç¾æÉפ«¡© */
-        if (!ignore_directory && !verify_mode) {
+        /* ↑これで、Symbolic Link は、大丈夫か? */
+        if (!ignore_directory && !verify_mode && !output_to_stdout) {
             if (noexec) {
                 if (quiet != TRUE)
                     printf("EXTRACT %s (directory)\n", name);
@@ -387,25 +526,27 @@ extract_one(afp, hdr)
                 }
 
                 unlink(name);
-                make_parent_path(name);
-                l_code = symlink(hdr->realname, name);
+                l_code = symlink_with_make_path(hdr->realname, name);
                 if (l_code < 0) {
                     if (quiet != TRUE)
                         warning("Can't make Symbolic Link \"%s\" -> \"%s\"",
-                                hdr->realname, name);
+                                name, hdr->realname);
                 }
                 if (quiet != TRUE) {
                     message("Symbolic Link %s -> %s",
-                            hdr->realname, name);
+                            name, hdr->realname);
                 }
 #else
                 warning("Can't make Symbolic Link %s -> %s",
-                        hdr->realname, name);
+                        name, hdr->realname);
                 return read_size;
 #endif
-            } else { /* make directory */
-                if (!output_to_stdout && !make_parent_path(name))
+            }
+            else { /* make directory */
+                if (!make_parent_path(name))
                     return read_size;
+                /* save directory information */
+                add_dirinfo(name, hdr);
             }
         }
     }
@@ -416,12 +557,33 @@ extract_one(afp, hdr)
             error("Unknown file type: \"%s\". use `f' option to force extract.", name);
     }
 
-    if (!output_to_stdout)
-        adjust_info(name, hdr);
+    if (!output_to_stdout && !verify_mode) {
+        if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_DIRECTORY)
+            adjust_info(name, hdr);
+    }
 
     return read_size;
 }
 
+static int
+skip_to_nextpos(FILE *fp, off_t pos, off_t off, off_t read_size)
+{
+    if (pos != -1) {
+        if (fseeko(fp, pos + off, SEEK_SET) != 0) {
+            return -1;
+        }
+    }
+    else {
+        off_t i = off - read_size;
+        while (i--) {
+            if (fgetc(fp) == EOF) {
+                return -1;
+            }
+        }
+    }
+    return 0;
+}
+
 /* ------------------------------------------------------------------------ */
 /* EXTRACT COMMAND MAIN                                                     */
 /* ------------------------------------------------------------------------ */
@@ -431,7 +593,7 @@ cmd_extract()
     LzHeader        hdr;
     off_t           pos;
     FILE           *afp;
-    size_t read_size;
+    off_t read_size;
 
     /* open archive file */
     if ((afp = open_old_archive()) == NULL)
@@ -442,25 +604,19 @@ cmd_extract()
 
     /* extract each files */
     while (get_header(afp, &hdr)) {
+        pos = ftello(afp);
         if (need_file(hdr.name)) {
-            pos = ftello(afp);
             read_size = extract_one(afp, &hdr);
             if (read_size != hdr.packed_size) {
                 /* when error occurred in extract_one(), should adjust
                    point of file stream */
-                if (pos != -1 && afp != stdin)
-                    fseeko(afp, pos + hdr.packed_size - read_size, SEEK_SET);
-                else {
-                    size_t i = hdr.packed_size - read_size;
-                    while (i--) fgetc(afp);
+                if (skip_to_nextpos(afp, pos, hdr.packed_size, read_size) == -1) {
+                    fatal_error("Cannot seek to next header position from \"%s\"", hdr.name);
                 }
             }
         } else {
-            if (afp != stdin)
-                fseeko(afp, hdr.packed_size, SEEK_CUR);
-            else {
-                size_t i = hdr.packed_size;
-                while (i--) fgetc(afp);
+            if (skip_to_nextpos(afp, pos, hdr.packed_size, 0) == -1) {
+                fatal_error("Cannot seek to next header position from \"%s\"", hdr.name);
             }
         }
     }
@@ -468,5 +624,153 @@ cmd_extract()
     /* close archive file */
     fclose(afp);
 
+    /* adjust directory information */
+    adjust_dirinfo();
+
     return;
 }
+
+int
+is_directory_traversal(char *path)
+{
+    int state = 0;
+
+    for (; *path; path++) {
+        switch (state) {
+        case 0:
+            if (*path == '.') state = 1;
+            else state = 3;
+            break;
+        case 1:
+            if (*path == '.') state = 2;
+            else if (*path == '/') state = 0;
+            else state = 3;
+            break;
+        case 2:
+            if (*path == '/') return 1;
+            else state = 3;
+            break;
+        case 3:
+            if (*path == '/') state = 0;
+            break;
+        }
+    }
+
+    return state == 2;
+}
+
+/*
+ * restore directory information (timestamp, permission and uid/gid).
+ * added by A.Iriyama  2003.12.12
+ */
+
+typedef struct LzHeaderList_t {
+    struct LzHeaderList_t *next;
+    LzHeader hdr;
+} LzHeaderList;
+
+static LzHeaderList *dirinfo;
+
+static void add_dirinfo(char *name, LzHeader *hdr)
+{
+    LzHeaderList *p, *tmp, top;
+
+    if (memcmp(hdr->method, LZHDIRS_METHOD, 5) != 0)
+        return;
+
+    p = xmalloc(sizeof(LzHeaderList));
+
+    memcpy(&p->hdr, hdr, sizeof(LzHeader));
+    strncpy(p->hdr.name, name, sizeof(p->hdr.name));
+    p->hdr.name[sizeof(p->hdr.name)-1] = 0;
+
+#if 0
+    /* push front */
+    {
+        tmp = dirinfo;
+        dirinfo = p;
+        dirinfo->next = tmp;
+    }
+#else
+
+    /*
+      reverse sorted by pathname order
+
+         p->hdr.name = "a"
+
+         dirinfo->hdr.name             = "a/b/d"
+         dirinfo->next->hdr.name       = "a/b/c"
+         dirinfo->next->next->hdr.name = "a/b"
+
+       result:
+
+         dirinfo->hdr.name                   = "a/b/d"
+         dirinfo->next->hdr.name             = "a/b/c"
+         dirinfo->next->next->hdr.name       = "a/b"
+         dirinfo->next->next->next->hdr.name = "a"
+    */
+
+    top.next = dirinfo;
+
+    for (tmp = &top; tmp->next; tmp = tmp->next) {
+        if (strcmp(p->hdr.name, tmp->next->hdr.name) > 0) {
+            p->next = tmp->next;
+            tmp->next = p;
+            break;
+        }
+    }
+    if (tmp->next == NULL) {
+        p->next = NULL;
+        tmp->next = p;
+    }
+
+    dirinfo = top.next;
+#endif
+}
+
+static void adjust_dirinfo()
+{
+    while (dirinfo) {
+        /* message("adjusting [%s]", dirinfo->hdr.name); */
+        adjust_info(dirinfo->hdr.name, &dirinfo->hdr);
+
+        {
+            LzHeaderList *tmp = dirinfo;
+            dirinfo = dirinfo->next;
+            free(tmp);
+        }
+    }
+}
+
+#if HAVE_LIBAPPLEFILE
+static boolean
+decode_macbinary(ofp, size, outPath)
+    FILE *ofp;
+    off_t size;
+    const char *outPath;
+{
+    af_file_t *afp = NULL;
+    FILE *ifp = NULL;
+    unsigned char *datap;
+    off_t dlen;
+
+    if ((afp = af_open(temporary_name)) != NULL) {
+        /* fetch datafork */
+        datap = af_data(afp, &dlen);
+        fwrite(datap, sizeof(unsigned char), dlen, ofp);
+        af_close(afp);
+        return TRUE;
+    } else { /* it may be not encoded in MacBinary */
+        /* try to copy */
+        if ((ifp = fopen(temporary_name, READ_BINARY)) == NULL) {
+            error("Cannot open a temporary file \"%s\"", temporary_name);
+            return FALSE;
+        }
+        copyfile(ifp, ofp, size, 0, 0);
+        fclose(ifp);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+#endif /* HAVE_LIBAPPLEFILE */