magic.c revision 226048
1133359Sobrien/* 2133359Sobrien * Copyright (c) Christos Zoulas 2003. 3133359Sobrien * All Rights Reserved. 4186690Sobrien * 5133359Sobrien * Redistribution and use in source and binary forms, with or without 6133359Sobrien * modification, are permitted provided that the following conditions 7133359Sobrien * are met: 8133359Sobrien * 1. Redistributions of source code must retain the above copyright 9133359Sobrien * notice immediately at the beginning of the file, without modification, 10133359Sobrien * this list of conditions, and the following disclaimer. 11133359Sobrien * 2. Redistributions in binary form must reproduce the above copyright 12133359Sobrien * notice, this list of conditions and the following disclaimer in the 13133359Sobrien * documentation and/or other materials provided with the distribution. 14186690Sobrien * 15133359Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16133359Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17133359Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18133359Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 19133359Sobrien * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20133359Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21133359Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22133359Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23133359Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24133359Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25133359Sobrien * SUCH DAMAGE. 26133359Sobrien */ 27133359Sobrien 28226048Sobrien#ifdef WIN32 29226048Sobrien#include <windows.h> 30226048Sobrien#include <shlwapi.h> 31226048Sobrien#endif 32226048Sobrien 33133359Sobrien#include "file.h" 34191736Sobrien 35191736Sobrien#ifndef lint 36226048SobrienFILE_RCSID("@(#)$File: magic.c,v 1.74 2011/05/26 01:27:59 christos Exp $") 37191736Sobrien#endif /* lint */ 38191736Sobrien 39133359Sobrien#include "magic.h" 40133359Sobrien 41133359Sobrien#include <stdlib.h> 42133359Sobrien#include <unistd.h> 43133359Sobrien#include <string.h> 44133359Sobrien#ifdef QUICK 45133359Sobrien#include <sys/mman.h> 46133359Sobrien#endif 47186690Sobrien#ifdef HAVE_LIMITS_H 48169942Sobrien#include <limits.h> /* for PIPE_BUF */ 49186690Sobrien#endif 50133359Sobrien 51133359Sobrien#if defined(HAVE_UTIMES) 52133359Sobrien# include <sys/time.h> 53133359Sobrien#elif defined(HAVE_UTIME) 54133359Sobrien# if defined(HAVE_SYS_UTIME_H) 55133359Sobrien# include <sys/utime.h> 56133359Sobrien# elif defined(HAVE_UTIME_H) 57133359Sobrien# include <utime.h> 58133359Sobrien# endif 59133359Sobrien#endif 60133359Sobrien 61133359Sobrien#ifdef HAVE_UNISTD_H 62133359Sobrien#include <unistd.h> /* for read() */ 63133359Sobrien#endif 64133359Sobrien 65186690Sobrien#ifndef PIPE_BUF 66186690Sobrien/* Get the PIPE_BUF from pathconf */ 67186690Sobrien#ifdef _PC_PIPE_BUF 68186690Sobrien#define PIPE_BUF pathconf(".", _PC_PIPE_BUF) 69186690Sobrien#else 70186690Sobrien#define PIPE_BUF 512 71186690Sobrien#endif 72186690Sobrien#endif 73186690Sobrien 74133359Sobrienprivate void free_mlist(struct mlist *); 75133359Sobrienprivate void close_and_restore(const struct magic_set *, const char *, int, 76133359Sobrien const struct stat *); 77186690Sobrienprivate int unreadable_info(struct magic_set *, mode_t, const char *); 78226048Sobrienprivate const char* get_default_magic(void); 79175296Sobrien#ifndef COMPILE_ONLY 80175296Sobrienprivate const char *file_or_fd(struct magic_set *, const char *, int); 81175296Sobrien#endif 82133359Sobrien 83159764Sobrien#ifndef STDIN_FILENO 84159764Sobrien#define STDIN_FILENO 0 85159764Sobrien#endif 86159764Sobrien 87226048Sobrienprivate const char * 88226048Sobrienget_default_magic(void) 89226048Sobrien{ 90226048Sobrien static const char hmagic[] = "/.magic/magic.mgc"; 91226048Sobrien static char *default_magic; 92226048Sobrien char *home, *hmagicpath; 93226048Sobrien 94226048Sobrien#ifndef WIN32 95226048Sobrien struct stat st; 96226048Sobrien 97226048Sobrien if (default_magic) { 98226048Sobrien free(default_magic); 99226048Sobrien default_magic = NULL; 100226048Sobrien } 101226048Sobrien if ((home = getenv("HOME")) == NULL) 102226048Sobrien return MAGIC; 103226048Sobrien 104226048Sobrien if (asprintf(&hmagicpath, "%s/.magic", home) < 0) 105226048Sobrien return MAGIC; 106226048Sobrien if (stat(hmagicpath, &st) == -1) 107226048Sobrien goto out; 108226048Sobrien if (S_ISDIR(st.st_mode)) { 109226048Sobrien free(hmagicpath); 110226048Sobrien if (asprintf(&hmagicpath, "%s/%s", home, hmagic) < 0) 111226048Sobrien return MAGIC; 112226048Sobrien if (access(hmagicpath, R_OK) == -1) 113226048Sobrien goto out; 114226048Sobrien } 115226048Sobrien 116226048Sobrien if (asprintf(&default_magic, "%s:%s", hmagicpath, MAGIC) < 0) 117226048Sobrien goto out; 118226048Sobrien free(hmagicpath); 119226048Sobrien return default_magic; 120226048Sobrienout: 121226048Sobrien default_magic = NULL; 122226048Sobrien free(hmagicpath); 123226048Sobrien return MAGIC; 124226048Sobrien#else 125226048Sobrien char *hmagicp = hmagicpath; 126226048Sobrien char *tmppath = NULL; 127226048Sobrien 128226048Sobrien#define APPENDPATH() \ 129226048Sobrien do { \ 130226048Sobrien if (tmppath && access(tmppath, R_OK) != -1) { \ 131226048Sobrien if (hmagicpath == NULL) \ 132226048Sobrien hmagicpath = tmppath; \ 133226048Sobrien else { \ 134226048Sobrien if (asprintf(&hmagicp, "%s%c%s", hmagicpath, \ 135226048Sobrien PATHSEP, tmppath) >= 0) { \ 136226048Sobrien free(hmagicpath); \ 137226048Sobrien hmagicpath = hmagicp; \ 138226048Sobrien } \ 139226048Sobrien free(tmppath); \ 140226048Sobrien } \ 141226048Sobrien tmppath = NULL; \ 142226048Sobrien } \ 143226048Sobrien } while (/*CONSTCOND*/0) 144226048Sobrien 145226048Sobrien if (default_magic) { 146226048Sobrien free(default_magic); 147226048Sobrien default_magic = NULL; 148226048Sobrien } 149226048Sobrien 150226048Sobrien /* First, try to get user-specific magic file */ 151226048Sobrien if ((home = getenv("LOCALAPPDATA")) == NULL) { 152226048Sobrien if ((home = getenv("USERPROFILE")) != NULL) 153226048Sobrien if (asprintf(&tmppath, 154226048Sobrien "%s/Local Settings/Application Data%s", home, 155226048Sobrien hmagic) < 0) 156226048Sobrien tmppath = NULL; 157226048Sobrien } else { 158226048Sobrien if (asprintf(&tmppath, "%s%s", home, hmagic) < 0) 159226048Sobrien tmppath = NULL; 160226048Sobrien } 161226048Sobrien 162226048Sobrien APPENDPATH(); 163226048Sobrien 164226048Sobrien /* Second, try to get a magic file from Common Files */ 165226048Sobrien if ((home = getenv("COMMONPROGRAMFILES")) != NULL) { 166226048Sobrien if (asprintf(&tmppath, "%s%s", home, hmagic) >= 0) 167226048Sobrien APPENDPATH(); 168226048Sobrien } 169226048Sobrien 170226048Sobrien /* Third, try to get magic file relative to dll location */ 171226048Sobrien LPTSTR dllpath = malloc(sizeof(*dllpath) * (MAX_PATH + 1)); 172226048Sobrien dllpath[MAX_PATH] = 0; /* just in case long path gets truncated and not null terminated */ 173226048Sobrien if (GetModuleFileNameA(NULL, dllpath, MAX_PATH)){ 174226048Sobrien PathRemoveFileSpecA(dllpath); 175226048Sobrien if (strlen(dllpath) > 3 && 176226048Sobrien stricmp(&dllpath[strlen(dllpath) - 3], "bin") == 0) { 177226048Sobrien if (asprintf(&tmppath, 178226048Sobrien "%s/../share/misc/magic.mgc", dllpath) >= 0) 179226048Sobrien APPENDPATH(); 180226048Sobrien } else { 181226048Sobrien if (asprintf(&tmppath, 182226048Sobrien "%s/share/misc/magic.mgc", dllpath) >= 0) 183226048Sobrien APPENDPATH(); 184226048Sobrien else if (asprintf(&tmppath, 185226048Sobrien "%s/magic.mgc", dllpath) >= 0) 186226048Sobrien APPENDPATH(); 187226048Sobrien } 188226048Sobrien } 189226048Sobrien 190226048Sobrien /* Don't put MAGIC constant - it likely points to a file within MSys 191226048Sobrien tree */ 192226048Sobrien default_magic = hmagicpath; 193226048Sobrien return default_magic; 194226048Sobrien#endif 195226048Sobrien} 196226048Sobrien 197226048Sobrienpublic const char * 198226048Sobrienmagic_getpath(const char *magicfile, int action) 199226048Sobrien{ 200226048Sobrien if (magicfile != NULL) 201226048Sobrien return magicfile; 202226048Sobrien 203226048Sobrien magicfile = getenv("MAGIC"); 204226048Sobrien if (magicfile != NULL) 205226048Sobrien return magicfile; 206226048Sobrien 207226048Sobrien return action == FILE_LOAD ? get_default_magic() : MAGIC; 208226048Sobrien} 209226048Sobrien 210133359Sobrienpublic struct magic_set * 211133359Sobrienmagic_open(int flags) 212133359Sobrien{ 213133359Sobrien struct magic_set *ms; 214186690Sobrien size_t len; 215133359Sobrien 216226048Sobrien if ((ms = CAST(struct magic_set *, calloc((size_t)1, 217186690Sobrien sizeof(struct magic_set)))) == NULL) 218133359Sobrien return NULL; 219133359Sobrien 220133359Sobrien if (magic_setflags(ms, flags) == -1) { 221133359Sobrien errno = EINVAL; 222186690Sobrien goto free; 223133359Sobrien } 224133359Sobrien 225186690Sobrien ms->o.buf = ms->o.pbuf = NULL; 226186690Sobrien len = (ms->c.len = 10) * sizeof(*ms->c.li); 227139368Sobrien 228186690Sobrien if ((ms->c.li = CAST(struct level_info *, malloc(len))) == NULL) 229186690Sobrien goto free; 230139368Sobrien 231191736Sobrien ms->event_flags = 0; 232133359Sobrien ms->error = -1; 233133359Sobrien ms->mlist = NULL; 234169962Sobrien ms->file = "unknown"; 235169962Sobrien ms->line = 0; 236133359Sobrien return ms; 237186690Sobrienfree: 238139368Sobrien free(ms); 239139368Sobrien return NULL; 240133359Sobrien} 241133359Sobrien 242133359Sobrienprivate void 243133359Sobrienfree_mlist(struct mlist *mlist) 244133359Sobrien{ 245133359Sobrien struct mlist *ml; 246133359Sobrien 247133359Sobrien if (mlist == NULL) 248133359Sobrien return; 249133359Sobrien 250133359Sobrien for (ml = mlist->next; ml != mlist;) { 251133359Sobrien struct mlist *next = ml->next; 252133359Sobrien struct magic *mg = ml->magic; 253133359Sobrien file_delmagic(mg, ml->mapped, ml->nmagic); 254133359Sobrien free(ml); 255133359Sobrien ml = next; 256133359Sobrien } 257133359Sobrien free(ml); 258133359Sobrien} 259133359Sobrien 260169942Sobrienprivate int 261186690Sobrienunreadable_info(struct magic_set *ms, mode_t md, const char *file) 262169942Sobrien{ 263169942Sobrien /* We cannot open it, but we were able to stat it. */ 264186690Sobrien if (access(file, W_OK) == 0) 265169942Sobrien if (file_printf(ms, "writable, ") == -1) 266169942Sobrien return -1; 267186690Sobrien if (access(file, X_OK) == 0) 268169942Sobrien if (file_printf(ms, "executable, ") == -1) 269169942Sobrien return -1; 270169942Sobrien if (S_ISREG(md)) 271169942Sobrien if (file_printf(ms, "regular file, ") == -1) 272169942Sobrien return -1; 273169942Sobrien if (file_printf(ms, "no read permission") == -1) 274169942Sobrien return -1; 275169942Sobrien return 0; 276169942Sobrien} 277169942Sobrien 278133359Sobrienpublic void 279169942Sobrienmagic_close(struct magic_set *ms) 280133359Sobrien{ 281133359Sobrien free_mlist(ms->mlist); 282139368Sobrien free(ms->o.pbuf); 283133359Sobrien free(ms->o.buf); 284169962Sobrien free(ms->c.li); 285133359Sobrien free(ms); 286133359Sobrien} 287133359Sobrien 288133359Sobrien/* 289133359Sobrien * load a magic file 290133359Sobrien */ 291133359Sobrienpublic int 292133359Sobrienmagic_load(struct magic_set *ms, const char *magicfile) 293133359Sobrien{ 294133359Sobrien struct mlist *ml = file_apprentice(ms, magicfile, FILE_LOAD); 295133359Sobrien if (ml) { 296133359Sobrien free_mlist(ms->mlist); 297133359Sobrien ms->mlist = ml; 298133359Sobrien return 0; 299133359Sobrien } 300133359Sobrien return -1; 301133359Sobrien} 302133359Sobrien 303133359Sobrienpublic int 304133359Sobrienmagic_compile(struct magic_set *ms, const char *magicfile) 305133359Sobrien{ 306133359Sobrien struct mlist *ml = file_apprentice(ms, magicfile, FILE_COMPILE); 307133359Sobrien free_mlist(ml); 308133359Sobrien return ml ? 0 : -1; 309133359Sobrien} 310133359Sobrien 311133359Sobrienpublic int 312133359Sobrienmagic_check(struct magic_set *ms, const char *magicfile) 313133359Sobrien{ 314133359Sobrien struct mlist *ml = file_apprentice(ms, magicfile, FILE_CHECK); 315133359Sobrien free_mlist(ml); 316133359Sobrien return ml ? 0 : -1; 317133359Sobrien} 318133359Sobrien 319226048Sobrienpublic int 320226048Sobrienmagic_list(struct magic_set *ms, const char *magicfile) 321226048Sobrien{ 322226048Sobrien struct mlist *ml = file_apprentice(ms, magicfile, FILE_LIST); 323226048Sobrien free_mlist(ml); 324226048Sobrien return ml ? 0 : -1; 325226048Sobrien} 326226048Sobrien 327133359Sobrienprivate void 328133359Sobrienclose_and_restore(const struct magic_set *ms, const char *name, int fd, 329133359Sobrien const struct stat *sb) 330133359Sobrien{ 331159764Sobrien if (fd == STDIN_FILENO) 332159764Sobrien return; 333133359Sobrien (void) close(fd); 334159764Sobrien 335159764Sobrien if ((ms->flags & MAGIC_PRESERVE_ATIME) != 0) { 336133359Sobrien /* 337133359Sobrien * Try to restore access, modification times if read it. 338133359Sobrien * This is really *bad* because it will modify the status 339133359Sobrien * time of the file... And of course this will affect 340133359Sobrien * backup programs 341133359Sobrien */ 342133359Sobrien#ifdef HAVE_UTIMES 343133359Sobrien struct timeval utsbuf[2]; 344186690Sobrien (void)memset(utsbuf, 0, sizeof(utsbuf)); 345133359Sobrien utsbuf[0].tv_sec = sb->st_atime; 346133359Sobrien utsbuf[1].tv_sec = sb->st_mtime; 347133359Sobrien 348133359Sobrien (void) utimes(name, utsbuf); /* don't care if loses */ 349133359Sobrien#elif defined(HAVE_UTIME_H) || defined(HAVE_SYS_UTIME_H) 350133359Sobrien struct utimbuf utbuf; 351133359Sobrien 352191736Sobrien (void)memset(&utbuf, 0, sizeof(utbuf)); 353133359Sobrien utbuf.actime = sb->st_atime; 354133359Sobrien utbuf.modtime = sb->st_mtime; 355133359Sobrien (void) utime(name, &utbuf); /* don't care if loses */ 356133359Sobrien#endif 357133359Sobrien } 358133359Sobrien} 359133359Sobrien 360133359Sobrien#ifndef COMPILE_ONLY 361175296Sobrien 362133359Sobrien/* 363175296Sobrien * find type of descriptor 364175296Sobrien */ 365175296Sobrienpublic const char * 366175296Sobrienmagic_descriptor(struct magic_set *ms, int fd) 367175296Sobrien{ 368175296Sobrien return file_or_fd(ms, NULL, fd); 369175296Sobrien} 370175296Sobrien 371175296Sobrien/* 372133359Sobrien * find type of named file 373133359Sobrien */ 374133359Sobrienpublic const char * 375133359Sobrienmagic_file(struct magic_set *ms, const char *inname) 376133359Sobrien{ 377175296Sobrien return file_or_fd(ms, inname, STDIN_FILENO); 378175296Sobrien} 379175296Sobrien 380175296Sobrienprivate const char * 381175296Sobrienfile_or_fd(struct magic_set *ms, const char *inname, int fd) 382175296Sobrien{ 383159764Sobrien int rv = -1; 384159764Sobrien unsigned char *buf; 385133359Sobrien struct stat sb; 386133359Sobrien ssize_t nbytes = 0; /* number of bytes read from a datafile */ 387169942Sobrien int ispipe = 0; 388133359Sobrien 389159764Sobrien /* 390159764Sobrien * one extra for terminating '\0', and 391159764Sobrien * some overlapping space for matches near EOF 392159764Sobrien */ 393159764Sobrien#define SLOP (1 + sizeof(union VALUETYPE)) 394186690Sobrien if ((buf = CAST(unsigned char *, malloc(HOWMANY + SLOP))) == NULL) 395133359Sobrien return NULL; 396133359Sobrien 397159764Sobrien if (file_reset(ms) == -1) 398159764Sobrien goto done; 399159764Sobrien 400133359Sobrien switch (file_fsmagic(ms, inname, &sb)) { 401169942Sobrien case -1: /* error */ 402159764Sobrien goto done; 403169942Sobrien case 0: /* nothing found */ 404133359Sobrien break; 405169942Sobrien default: /* matched it and printed type */ 406159764Sobrien rv = 0; 407159764Sobrien goto done; 408133359Sobrien } 409133359Sobrien 410169942Sobrien if (inname == NULL) { 411169942Sobrien if (fstat(fd, &sb) == 0 && S_ISFIFO(sb.st_mode)) 412169942Sobrien ispipe = 1; 413169942Sobrien } else { 414169942Sobrien int flags = O_RDONLY|O_BINARY; 415169942Sobrien 416169942Sobrien if (stat(inname, &sb) == 0 && S_ISFIFO(sb.st_mode)) { 417226048Sobrien#ifdef O_NONBLOCK 418169942Sobrien flags |= O_NONBLOCK; 419226048Sobrien#endif 420169942Sobrien ispipe = 1; 421169942Sobrien } 422169942Sobrien 423169942Sobrien errno = 0; 424169942Sobrien if ((fd = open(inname, flags)) < 0) { 425192348Sdelphij if (unreadable_info(ms, sb.st_mode, inname) == -1) 426175296Sobrien goto done; 427192348Sdelphij rv = 0; 428192348Sdelphij goto done; 429169942Sobrien } 430169942Sobrien#ifdef O_NONBLOCK 431169942Sobrien if ((flags = fcntl(fd, F_GETFL)) != -1) { 432169942Sobrien flags &= ~O_NONBLOCK; 433169942Sobrien (void)fcntl(fd, F_SETFL, flags); 434169942Sobrien } 435169942Sobrien#endif 436133359Sobrien } 437133359Sobrien 438133359Sobrien /* 439133359Sobrien * try looking at the first HOWMANY bytes 440133359Sobrien */ 441169942Sobrien if (ispipe) { 442169942Sobrien ssize_t r = 0; 443169942Sobrien 444169942Sobrien while ((r = sread(fd, (void *)&buf[nbytes], 445169962Sobrien (size_t)(HOWMANY - nbytes), 1)) > 0) { 446169942Sobrien nbytes += r; 447169942Sobrien if (r < PIPE_BUF) break; 448169942Sobrien } 449169942Sobrien 450169942Sobrien if (nbytes == 0) { 451169942Sobrien /* We can not read it, but we were able to stat it. */ 452186690Sobrien if (unreadable_info(ms, sb.st_mode, inname) == -1) 453169942Sobrien goto done; 454169942Sobrien rv = 0; 455169942Sobrien goto done; 456169942Sobrien } 457169942Sobrien 458169942Sobrien } else { 459169942Sobrien if ((nbytes = read(fd, (char *)buf, HOWMANY)) == -1) { 460169942Sobrien file_error(ms, errno, "cannot read `%s'", inname); 461169942Sobrien goto done; 462169942Sobrien } 463133359Sobrien } 464133359Sobrien 465175296Sobrien (void)memset(buf + nbytes, 0, SLOP); /* NUL terminate */ 466175296Sobrien if (file_buffer(ms, fd, inname, buf, (size_t)nbytes) == -1) 467175296Sobrien goto done; 468159764Sobrien rv = 0; 469133359Sobriendone: 470159764Sobrien free(buf); 471133359Sobrien close_and_restore(ms, inname, fd, &sb); 472159764Sobrien return rv == 0 ? file_getbuffer(ms) : NULL; 473133359Sobrien} 474133359Sobrien 475133359Sobrien 476133359Sobrienpublic const char * 477133359Sobrienmagic_buffer(struct magic_set *ms, const void *buf, size_t nb) 478133359Sobrien{ 479133359Sobrien if (file_reset(ms) == -1) 480133359Sobrien return NULL; 481133359Sobrien /* 482133359Sobrien * The main work is done here! 483186690Sobrien * We have the file name and/or the data buffer to be identified. 484133359Sobrien */ 485169962Sobrien if (file_buffer(ms, -1, NULL, buf, nb) == -1) { 486133359Sobrien return NULL; 487133359Sobrien } 488133359Sobrien return file_getbuffer(ms); 489133359Sobrien} 490133359Sobrien#endif 491133359Sobrien 492133359Sobrienpublic const char * 493133359Sobrienmagic_error(struct magic_set *ms) 494133359Sobrien{ 495191736Sobrien return (ms->event_flags & EVENT_HAD_ERR) ? ms->o.buf : NULL; 496133359Sobrien} 497133359Sobrien 498133359Sobrienpublic int 499133359Sobrienmagic_errno(struct magic_set *ms) 500133359Sobrien{ 501191736Sobrien return (ms->event_flags & EVENT_HAD_ERR) ? ms->error : 0; 502133359Sobrien} 503133359Sobrien 504133359Sobrienpublic int 505133359Sobrienmagic_setflags(struct magic_set *ms, int flags) 506133359Sobrien{ 507133359Sobrien#if !defined(HAVE_UTIME) && !defined(HAVE_UTIMES) 508133359Sobrien if (flags & MAGIC_PRESERVE_ATIME) 509133359Sobrien return -1; 510133359Sobrien#endif 511133359Sobrien ms->flags = flags; 512133359Sobrien return 0; 513133359Sobrien} 514