1/*
2 * Copyright (c) Christos Zoulas 2003.
3 * All Rights Reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice immediately at the beginning of the file, without modification,
10 *    this list of conditions, and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
19 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include "file.h"
29
30#ifndef lint
31FILE_RCSID("@(#)$File: magic.c,v 1.81 2013/11/29 15:42:51 christos Exp $")
32#endif  /* lint */
33
34#include "magic.h"
35
36#include <stdlib.h>
37#ifdef PHP_WIN32
38#include "win32/unistd.h"
39#else
40#include <unistd.h>
41#endif
42#include <string.h>
43#ifdef PHP_WIN32
44# include "config.w32.h"
45#else
46# include "php_config.h"
47#endif
48
49#ifdef PHP_WIN32
50#include <shlwapi.h>
51#endif
52
53#include <limits.h> /* for PIPE_BUF */
54
55#if defined(HAVE_UTIMES)
56# include <sys/time.h>
57#elif defined(HAVE_UTIME)
58# if defined(HAVE_SYS_UTIME_H)
59#  include <sys/utime.h>
60# elif defined(HAVE_UTIME_H)
61#  include <utime.h>
62# endif
63#endif
64
65#ifdef HAVE_UNISTD_H
66#include <unistd.h> /* for read() */
67#endif
68
69#ifndef PIPE_BUF
70/* Get the PIPE_BUF from pathconf */
71#ifdef _PC_PIPE_BUF
72#define PIPE_BUF pathconf(".", _PC_PIPE_BUF)
73#else
74#define PIPE_BUF 512
75#endif
76#endif
77
78#ifdef PHP_WIN32
79# undef S_IFLNK
80# undef S_IFIFO
81#endif
82
83private void close_and_restore(const struct magic_set *, const char *, int,
84    const zend_stat_t *);
85private int unreadable_info(struct magic_set *, mode_t, const char *);
86#if 0
87private const char* get_default_magic(void);
88#endif
89private const char *file_or_stream(struct magic_set *, const char *, php_stream *);
90
91#ifndef STDIN_FILENO
92#define STDIN_FILENO    0
93#endif
94
95/* XXX this functionality is excluded in php, enable it in apprentice.c:340 */
96#if 0
97private const char *
98get_default_magic(void)
99{
100    static const char hmagic[] = "/.magic/magic.mgc";
101    static char *default_magic;
102    char *home, *hmagicpath;
103
104#ifndef PHP_WIN32
105    struct stat st;
106
107    if (default_magic) {
108        free(default_magic);
109        default_magic = NULL;
110    }
111    if ((home = getenv("HOME")) == NULL)
112        return MAGIC;
113
114    if (asprintf(&hmagicpath, "%s/.magic.mgc", home) < 0)
115        return MAGIC;
116    if (stat(hmagicpath, &st) == -1) {
117        free(hmagicpath);
118    if (asprintf(&hmagicpath, "%s/.magic", home) < 0)
119        return MAGIC;
120    if (stat(hmagicpath, &st) == -1)
121        goto out;
122    if (S_ISDIR(st.st_mode)) {
123        free(hmagicpath);
124        if (asprintf(&hmagicpath, "%s/%s", home, hmagic) < 0)
125            return MAGIC;
126        if (access(hmagicpath, R_OK) == -1)
127            goto out;
128    }
129    }
130
131    if (asprintf(&default_magic, "%s:%s", hmagicpath, MAGIC) < 0)
132        goto out;
133    free(hmagicpath);
134    return default_magic;
135out:
136    default_magic = NULL;
137    free(hmagicpath);
138    return MAGIC;
139#else
140    char *hmagicp = hmagicpath;
141    char *tmppath = NULL;
142    LPTSTR dllpath;
143
144#define APPENDPATH() \
145    do { \
146        if (tmppath && access(tmppath, R_OK) != -1) { \
147            if (hmagicpath == NULL) \
148                hmagicpath = tmppath; \
149            else { \
150                if (asprintf(&hmagicp, "%s%c%s", hmagicpath, \
151                    PATHSEP, tmppath) >= 0) { \
152                    free(hmagicpath); \
153                    hmagicpath = hmagicp; \
154                } \
155                free(tmppath); \
156            } \
157            tmppath = NULL; \
158        } \
159    } while (/*CONSTCOND*/0)
160
161    if (default_magic) {
162        free(default_magic);
163        default_magic = NULL;
164    }
165
166    /* First, try to get user-specific magic file */
167    if ((home = getenv("LOCALAPPDATA")) == NULL) {
168        if ((home = getenv("USERPROFILE")) != NULL)
169            if (asprintf(&tmppath,
170                "%s/Local Settings/Application Data%s", home,
171                hmagic) < 0)
172                tmppath = NULL;
173    } else {
174        if (asprintf(&tmppath, "%s%s", home, hmagic) < 0)
175            tmppath = NULL;
176    }
177
178    APPENDPATH();
179
180    /* Second, try to get a magic file from Common Files */
181    if ((home = getenv("COMMONPROGRAMFILES")) != NULL) {
182        if (asprintf(&tmppath, "%s%s", home, hmagic) >= 0)
183            APPENDPATH();
184    }
185
186    /* Third, try to get magic file relative to dll location */
187    dllpath = malloc(sizeof(*dllpath) * (MAX_PATH + 1));
188    dllpath[MAX_PATH] = 0;  /* just in case long path gets truncated and not null terminated */
189    if (GetModuleFileNameA(NULL, dllpath, MAX_PATH)){
190        PathRemoveFileSpecA(dllpath);
191        if (strlen(dllpath) > 3 &&
192            stricmp(&dllpath[strlen(dllpath) - 3], "bin") == 0) {
193            if (asprintf(&tmppath,
194                "%s/../share/misc/magic.mgc", dllpath) >= 0)
195                APPENDPATH();
196        } else {
197            if (asprintf(&tmppath,
198                "%s/share/misc/magic.mgc", dllpath) >= 0)
199                APPENDPATH();
200            else if (asprintf(&tmppath,
201                "%s/magic.mgc", dllpath) >= 0)
202                APPENDPATH();
203        }
204    }
205
206    /* Don't put MAGIC constant - it likely points to a file within MSys
207    tree */
208    default_magic = hmagicpath;
209    return default_magic;
210#endif
211}
212
213public const char *
214magic_getpath(const char *magicfile, int action)
215{
216    if (magicfile != NULL)
217        return magicfile;
218
219    magicfile = getenv("MAGIC");
220    if (magicfile != NULL)
221        return magicfile;
222
223    return action == FILE_LOAD ? get_default_magic() : MAGIC;
224}
225#endif
226
227public struct magic_set *
228magic_open(int flags)
229{
230    return file_ms_alloc(flags);
231}
232
233private int
234unreadable_info(struct magic_set *ms, mode_t md, const char *file)
235{
236    /* We cannot open it, but we were able to stat it. */
237    if (access(file, W_OK) == 0)
238        if (file_printf(ms, "writable, ") == -1)
239            return -1;
240    if (access(file, X_OK) == 0)
241        if (file_printf(ms, "executable, ") == -1)
242            return -1;
243    if (S_ISREG(md))
244        if (file_printf(ms, "regular file, ") == -1)
245            return -1;
246    if (file_printf(ms, "no read permission") == -1)
247        return -1;
248    return 0;
249}
250
251public void
252magic_close(struct magic_set *ms)
253{
254    if (ms == NULL)
255        return;
256    file_ms_free(ms);
257}
258
259/*
260 * load a magic file
261 */
262public int
263magic_load(struct magic_set *ms, const char *magicfile)
264{
265    if (ms == NULL)
266        return -1;
267    return file_apprentice(ms, magicfile, FILE_LOAD);
268}
269
270public int
271magic_compile(struct magic_set *ms, const char *magicfile)
272{
273    if (ms == NULL)
274        return -1;
275    return file_apprentice(ms, magicfile, FILE_COMPILE);
276}
277
278
279public int
280magic_list(struct magic_set *ms, const char *magicfile)
281{
282    if (ms == NULL)
283        return -1;
284    return file_apprentice(ms, magicfile, FILE_LIST);
285}
286
287private void
288close_and_restore(const struct magic_set *ms, const char *name, int fd,
289    const zend_stat_t *sb)
290{
291
292    if ((ms->flags & MAGIC_PRESERVE_ATIME) != 0) {
293        /*
294         * Try to restore access, modification times if read it.
295         * This is really *bad* because it will modify the status
296         * time of the file... And of course this will affect
297         * backup programs
298         */
299#ifdef HAVE_UTIMES
300        struct timeval  utsbuf[2];
301        (void)memset(utsbuf, 0, sizeof(utsbuf));
302        utsbuf[0].tv_sec = sb->st_atime;
303        utsbuf[1].tv_sec = sb->st_mtime;
304
305        (void) utimes(name, utsbuf); /* don't care if loses */
306#elif defined(HAVE_UTIME_H) || defined(HAVE_SYS_UTIME_H)
307        struct utimbuf  utbuf;
308
309        (void)memset(&utbuf, 0, sizeof(utbuf));
310        utbuf.actime = sb->st_atime;
311        utbuf.modtime = sb->st_mtime;
312        (void) utime(name, &utbuf); /* don't care if loses */
313#endif
314    }
315}
316
317
318/*
319 * find type of descriptor
320 */
321public const char *
322magic_descriptor(struct magic_set *ms, int fd)
323{
324    if (ms == NULL)
325        return NULL;
326    return file_or_stream(ms, NULL, NULL);
327}
328
329/*
330 * find type of named file
331 */
332public const char *
333magic_file(struct magic_set *ms, const char *inname)
334{
335    if (ms == NULL)
336        return NULL;
337    return file_or_stream(ms, inname, NULL);
338}
339
340public const char *
341magic_stream(struct magic_set *ms, php_stream *stream)
342{
343    if (ms == NULL)
344        return NULL;
345    return file_or_stream(ms, NULL, stream);
346}
347
348private const char *
349file_or_stream(struct magic_set *ms, const char *inname, php_stream *stream)
350{
351    int rv = -1;
352    unsigned char *buf;
353    zend_stat_t   sb;
354    ssize_t nbytes = 0; /* number of bytes read from a datafile */
355    int no_in_stream = 0;
356    TSRMLS_FETCH();
357
358    if (!inname && !stream) {
359        return NULL;
360    }
361
362    /*
363     * one extra for terminating '\0', and
364     * some overlapping space for matches near EOF
365     */
366#define SLOP (1 + sizeof(union VALUETYPE))
367    buf = emalloc(HOWMANY + SLOP);
368
369    if (file_reset(ms) == -1)
370        goto done;
371
372    switch (file_fsmagic(ms, inname, &sb, stream)) {
373    case -1:        /* error */
374        goto done;
375    case 0:         /* nothing found */
376        break;
377    default:        /* matched it and printed type */
378        rv = 0;
379        goto done;
380    }
381
382    errno = 0;
383
384    if (!stream && inname) {
385        no_in_stream = 1;
386        stream = php_stream_open_wrapper((char *)inname, "rb", REPORT_ERRORS, NULL);
387    }
388
389    if (!stream) {
390        if (unreadable_info(ms, sb.st_mode, inname) == -1)
391            goto done;
392        rv = 0;
393        goto done;
394    }
395
396#ifdef O_NONBLOCK
397/* we should be already be in non blocking mode for network socket */
398#endif
399
400    /*
401     * try looking at the first HOWMANY bytes
402     */
403    if ((nbytes = php_stream_read(stream, (char *)buf, HOWMANY)) < 0) {
404        file_error(ms, errno, "cannot read `%s'", inname);
405        goto done;
406    }
407
408    (void)memset(buf + nbytes, 0, SLOP); /* NUL terminate */
409    if (file_buffer(ms, stream, inname, buf, (size_t)nbytes) == -1)
410        goto done;
411    rv = 0;
412done:
413    efree(buf);
414
415    if (no_in_stream && stream) {
416        php_stream_close(stream);
417    }
418
419    close_and_restore(ms, inname, 0, &sb);
420    return rv == 0 ? file_getbuffer(ms) : NULL;
421}
422
423
424public const char *
425magic_buffer(struct magic_set *ms, const void *buf, size_t nb)
426{
427    if (ms == NULL)
428        return NULL;
429    if (file_reset(ms) == -1)
430        return NULL;
431    /*
432     * The main work is done here!
433     * We have the file name and/or the data buffer to be identified.
434     */
435    if (file_buffer(ms, NULL, NULL, buf, nb) == -1) {
436        return NULL;
437    }
438    return file_getbuffer(ms);
439}
440
441public const char *
442magic_error(struct magic_set *ms)
443{
444    if (ms == NULL)
445        return "Magic database is not open";
446    return (ms->event_flags & EVENT_HAD_ERR) ? ms->o.buf : NULL;
447}
448
449public int
450magic_errno(struct magic_set *ms)
451{
452    if (ms == NULL)
453        return EINVAL;
454    return (ms->event_flags & EVENT_HAD_ERR) ? ms->error : 0;
455}
456
457public int
458magic_setflags(struct magic_set *ms, int flags)
459{
460    if (ms == NULL)
461        return -1;
462#if !defined(HAVE_UTIME) && !defined(HAVE_UTIMES)
463    if (flags & MAGIC_PRESERVE_ATIME)
464        return -1;
465#endif
466    ms->flags = flags;
467    return 0;
468}
469
470public int
471magic_version(void)
472{
473    return MAGIC_VERSION;
474}
475