888f5794 |
/* |
c442ca9c |
* Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
70ef8414 |
* Copyright (C) 2007-2013 Sourcefire, Inc. |
7021b545 |
* |
2023340a |
* Authors: Tomasz Kojm |
888f5794 |
*
* This program is free software; you can redistribute it and/or modify |
bb34cb31 |
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. |
888f5794 |
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software |
48b7b4a7 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA. |
888f5794 |
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h> |
ad3c01bf |
#include <sys/types.h> |
4e9ab8ed |
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif |
888f5794 |
#include "clamav.h"
#include "filetypes.h" |
8000d078 |
#include "others.h"
#include "readdb.h" |
c6fb0b98 |
#include "matcher-ac.h" |
bd988961 |
#include "str.h" |
c8f2d060 |
#include "textdet.h" |
589d8d8e |
#include "default.h" |
6b7c7dc6 |
#include "iowrap.h" |
6c2feae2 |
#include "mbr.h" |
00acb79c |
#include "gpt.h" |
b5641e9c |
#include "ooxml.h" |
888f5794 |
|
3506ac49 |
#include "htmlnorm.h"
#include "entconv.h" |
e21657df |
#include "mpool.h" |
70ef8414 |
#define UNZIP_PRIVATE
#include "unzip.h" |
3506ac49 |
|
7021b545 |
static const struct ftmap_s {
const char *name;
cli_file_t code;
} ftmap[] = { |
c8f2d060 |
{ "CL_TYPE_TEXT_ASCII", CL_TYPE_TEXT_ASCII },
{ "CL_TYPE_TEXT_UTF8", CL_TYPE_TEXT_UTF8 },
{ "CL_TYPE_TEXT_UTF16LE", CL_TYPE_TEXT_UTF16LE },
{ "CL_TYPE_TEXT_UTF16BE", CL_TYPE_TEXT_UTF16BE },
{ "CL_TYPE_BINARY_DATA", CL_TYPE_BINARY_DATA }, |
7021b545 |
{ "CL_TYPE_IGNORED", CL_TYPE_IGNORED }, |
55094a9c |
{ "CL_TYPE_ANY", CL_TYPE_ANY }, |
7021b545 |
{ "CL_TYPE_MSEXE", CL_TYPE_MSEXE },
{ "CL_TYPE_ELF", CL_TYPE_ELF }, |
89c14869 |
{ "CL_TYPE_MACHO", CL_TYPE_MACHO }, |
3222a096 |
{ "CL_TYPE_MACHO_UNIBIN", CL_TYPE_MACHO_UNIBIN }, |
7021b545 |
{ "CL_TYPE_POSIX_TAR", CL_TYPE_POSIX_TAR },
{ "CL_TYPE_OLD_TAR", CL_TYPE_OLD_TAR }, |
75e46945 |
{ "CL_TYPE_CPIO_OLD", CL_TYPE_CPIO_OLD },
{ "CL_TYPE_CPIO_ODC", CL_TYPE_CPIO_ODC },
{ "CL_TYPE_CPIO_NEWC", CL_TYPE_CPIO_NEWC },
{ "CL_TYPE_CPIO_CRC", CL_TYPE_CPIO_CRC }, |
7021b545 |
{ "CL_TYPE_GZ", CL_TYPE_GZ },
{ "CL_TYPE_ZIP", CL_TYPE_ZIP },
{ "CL_TYPE_BZ", CL_TYPE_BZ },
{ "CL_TYPE_RAR", CL_TYPE_RAR },
{ "CL_TYPE_ARJ", CL_TYPE_ARJ },
{ "CL_TYPE_MSSZDD", CL_TYPE_MSSZDD },
{ "CL_TYPE_MSOLE2", CL_TYPE_MSOLE2 },
{ "CL_TYPE_MSCAB", CL_TYPE_MSCAB },
{ "CL_TYPE_MSCHM", CL_TYPE_MSCHM },
{ "CL_TYPE_SIS", CL_TYPE_SIS },
{ "CL_TYPE_SCRENC", CL_TYPE_SCRENC },
{ "CL_TYPE_GRAPHICS", CL_TYPE_GRAPHICS },
{ "CL_TYPE_RIFF", CL_TYPE_RIFF },
{ "CL_TYPE_BINHEX", CL_TYPE_BINHEX },
{ "CL_TYPE_TNEF", CL_TYPE_TNEF },
{ "CL_TYPE_CRYPTFF", CL_TYPE_CRYPTFF },
{ "CL_TYPE_PDF", CL_TYPE_PDF },
{ "CL_TYPE_UUENCODED", CL_TYPE_UUENCODED },
{ "CL_TYPE_HTML_UTF16", CL_TYPE_HTML_UTF16 }, |
015ce4a8 |
{ "CL_TYPE_SCRIPT", CL_TYPE_SCRIPT }, |
7021b545 |
{ "CL_TYPE_RTF", CL_TYPE_RTF },
{ "CL_TYPE_HTML", CL_TYPE_HTML },
{ "CL_TYPE_MAIL", CL_TYPE_MAIL },
{ "CL_TYPE_SFX", CL_TYPE_SFX },
{ "CL_TYPE_ZIPSFX", CL_TYPE_ZIPSFX },
{ "CL_TYPE_RARSFX", CL_TYPE_RARSFX },
{ "CL_TYPE_CABSFX", CL_TYPE_CABSFX },
{ "CL_TYPE_ARJSFX", CL_TYPE_ARJSFX },
{ "CL_TYPE_NULSFT", CL_TYPE_NULSFT },
{ "CL_TYPE_AUTOIT", CL_TYPE_AUTOIT }, |
cadaa703 |
{ "CL_TYPE_ISHIELD_MSI", CL_TYPE_ISHIELD_MSI }, |
81fded11 |
{ "CL_TYPE_7Z", CL_TYPE_7Z }, |
9a47aa20 |
{ "CL_TYPE_7ZSFX", CL_TYPE_7ZSFX }, |
44a3e21a |
{ "CL_TYPE_SWF", CL_TYPE_SWF }, |
583cd65f |
{ "CL_TYPE_ISO9660", CL_TYPE_ISO9660 }, |
703a9258 |
{ "CL_TYPE_JAVA", CL_TYPE_JAVA }, |
ca019d6d |
{ "CL_TYPE_DMG", CL_TYPE_DMG }, |
904fe155 |
{ "CL_TYPE_MBR", CL_TYPE_MBR },
{ "CL_TYPE_GPT", CL_TYPE_GPT },
{ "CL_TYPE_APM", CL_TYPE_APM }, |
ca019d6d |
{ "CL_TYPE_XAR", CL_TYPE_XAR }, |
1d1c4b15 |
{ "CL_TYPE_PART_ANY", CL_TYPE_PART_ANY },
{ "CL_TYPE_PART_HFSPLUS", CL_TYPE_PART_HFSPLUS }, |
43d7f6f6 |
{ "CL_TYPE_XZ", CL_TYPE_XZ }, |
70ef8414 |
{ "CL_TYPE_OOXML_WORD", CL_TYPE_OOXML_WORD },
{ "CL_TYPE_OOXML_PPT", CL_TYPE_OOXML_PPT },
{ "CL_TYPE_OOXML_XL", CL_TYPE_OOXML_XL }, |
de46d3e3 |
{ "CL_TYPE_INTERNAL", CL_TYPE_INTERNAL }, |
904fe155 |
{ "CL_TYPE_XDP", CL_TYPE_XDP },
{ "CL_TYPE_XML_WORD", CL_TYPE_XML_WORD },
{ "CL_TYPE_XML_XL", CL_TYPE_XML_XL },
{ "CL_TYPE_HWP3", CL_TYPE_HWP3 },
{ "CL_TYPE_XML_HWP", CL_TYPE_XML_HWP }, |
6cd5a9dc |
{ "CL_TYPE_HWPOLE2", CL_TYPE_HWPOLE2 }, |
c6f7be55 |
{ "CL_TYPE_OOXML_HWP", CL_TYPE_OOXML_HWP }, |
aedd18ac |
{ "CL_TYPE_PS", CL_TYPE_PS }, |
ef48d7cb |
{ "CL_TYPE_MHTML", CL_TYPE_MHTML }, |
56bb195e |
{ "CL_TYPE_LNK", CL_TYPE_LNK }, |
c8f2d060 |
{ NULL, CL_TYPE_IGNORED } |
888f5794 |
};
|
cd94be7a |
cli_file_t cli_partitiontype(const unsigned char *buf, size_t buflen, const struct cl_engine *engine);
|
7021b545 |
cli_file_t cli_ftcode(const char *name)
{
unsigned int i; |
888f5794 |
|
7021b545 |
for(i = 0; ftmap[i].name; i++)
if(!strcmp(ftmap[i].name, name))
return ftmap[i].code; |
888f5794 |
|
7021b545 |
return CL_TYPE_ERROR;
} |
888f5794 |
|
c27d4056 |
const char *cli_ftname(cli_file_t code)
{
unsigned int i;
for(i = 0; ftmap[i].name; i++)
if(ftmap[i].code == code)
return ftmap[i].name;
return NULL;
}
|
0d9dbdef |
void cli_ftfree(const struct cl_engine *engine) |
7021b545 |
{ |
0d9dbdef |
struct cli_ftype *ftypes=engine->ftypes, *pt; |
7021b545 |
while(ftypes) {
pt = ftypes;
ftypes = ftypes->next; |
47d40feb |
mpool_free(engine->mempool, pt->magic);
mpool_free(engine->mempool, pt->tname);
mpool_free(engine->mempool, pt); |
7021b545 |
} |
1d1c4b15 |
ftypes = engine->ptypes;
while(ftypes) {
pt = ftypes;
ftypes = ftypes->next;
mpool_free(engine->mempool, pt->magic);
mpool_free(engine->mempool, pt->tname);
mpool_free(engine->mempool, pt);
}
}
cli_file_t cli_partitiontype(const unsigned char *buf, size_t buflen, const struct cl_engine *engine)
{
struct cli_ftype *ptype = engine->ptypes;
while(ptype) {
if(ptype->offset + ptype->length <= buflen) {
if(!memcmp(buf + ptype->offset, ptype->magic, ptype->length)) {
cli_dbgmsg("Recognized %s partition\n", ptype->tname);
return ptype->type;
}
}
ptype = ptype->next;
}
|
f290ffd3 |
cli_dbgmsg("Partition type is potentially unsupported\n"); |
1d1c4b15 |
return CL_TYPE_PART_ANY; |
7021b545 |
} |
e88f97f3 |
|
7021b545 |
cli_file_t cli_filetype(const unsigned char *buf, size_t buflen, const struct cl_engine *engine) |
888f5794 |
{ |
7021b545 |
struct cli_ftype *ftype = engine->ftypes; |
216a697f |
|
888f5794 |
|
7021b545 |
while(ftype) {
if(ftype->offset + ftype->length <= buflen) {
if(!memcmp(buf + ftype->offset, ftype->magic, ftype->length)) {
cli_dbgmsg("Recognized %s file\n", ftype->tname);
return ftype->type; |
888f5794 |
}
} |
7021b545 |
ftype = ftype->next; |
888f5794 |
}
|
c8f2d060 |
return cli_texttype(buf, buflen); |
888f5794 |
}
|
f304dc68 |
int is_tar(const unsigned char *buf, unsigned int nbytes); |
a7f5fd00 |
|
5f20aa74 |
/* organize by length, cannot exceed SIZEOF_LOCAL_HEADER */ |
8059ffb7 |
const struct ooxml_ftcodes {
const char *entry;
size_t len;
cli_file_t type;
} ooxml_detect[] = {
{ "xl/", 3, CL_TYPE_OOXML_XL },
{ "ppt/", 4, CL_TYPE_OOXML_PPT },
{ "word/", 5, CL_TYPE_OOXML_WORD },
{ "BinData", 7, CL_TYPE_ZIP }, /* HWP */
{ "mimetype", 8, CL_TYPE_ZIP }, /* HWP */
{ "Contents", 8, CL_TYPE_ZIP }, /* HWP */
{ "docProps/", 9, CL_TYPE_ZIP }, /* MS */ |
ce174c71 |
{ "customXml/", 10, CL_TYPE_ZIP }, /* MS */ |
8059ffb7 |
{ "version.xml", 11, CL_TYPE_ZIP }, /* HWP */
{ "settings.xml", 12, CL_TYPE_ZIP }, /* HWP */
{ "_.rels/.rels", 12, CL_TYPE_ZIP }, /* MS */
{ "[ContentTypes].xml", 18, CL_TYPE_ZIP }, /* MS */
{ "[Content_Types].xml", 19, CL_TYPE_ZIP }, /* MS */
{ "Preview/PrvText.txt", 19, CL_TYPE_ZIP }, /* HWP */
{ "Contents/content.hpf", 20, CL_TYPE_OOXML_HWP },
{ "META-INF/container.xml", 22, CL_TYPE_ZIP }, /* HWP */
{ NULL, 0, CL_TYPE_ANY }
}; |
8250b1a7 |
/* set to biggest ooxml_detect len */
#define OOXML_DETECT_MAXLEN 22 |
8059ffb7 |
#define OOXML_FTIDENTIFIED(type) \
do { \
if (type != CL_TYPE_ZIP) { \
switch (type) { \
case CL_TYPE_OOXML_XL: \
cli_dbgmsg("Recognized OOXML XL file\n"); \
return CL_TYPE_OOXML_XL; \
case CL_TYPE_OOXML_PPT: \
cli_dbgmsg("Recognized OOXML PPT file\n"); \
return CL_TYPE_OOXML_PPT; \
case CL_TYPE_OOXML_WORD: \
cli_dbgmsg("Recognized OOXML WORD file\n"); \
return CL_TYPE_OOXML_WORD; \
case CL_TYPE_OOXML_HWP: \
cli_dbgmsg("Recognized OOXML HWP file\n"); \
return CL_TYPE_OOXML_HWP; \
default: \
cli_dbgmsg("unexpected ooxml_filetype return: %i\n", type); \
} \
} \
} while(0) |
c6f7be55 |
|
1d1c4b15 |
cli_file_t cli_filetype2(fmap_t *map, const struct cl_engine *engine, cli_file_t basetype) |
a7f5fd00 |
{ |
6b7c7dc6 |
unsigned char buffer[MAGIC_BUFFER_SIZE]; |
f304dc68 |
const unsigned char *buff;
unsigned char *decoded; |
1d1c4b15 |
int bread, sret; |
c8f2d060 |
cli_file_t ret = CL_TYPE_BINARY_DATA; |
bd988961 |
struct cli_matcher *root; |
4e9ab8ed |
struct cli_ac_data mdata; |
a7f5fd00 |
|
c8f2d060 |
if(!engine) {
cli_errmsg("cli_filetype2: engine == NULL\n");
return CL_TYPE_ERROR;
}
|
1d1c4b15 |
if(basetype == CL_TYPE_PART_ANY) {
bread = MIN(map->len, CL_PART_MBUFF_SIZE);
}
else {
bread = MIN(map->len, CL_FILE_MBUFF_SIZE);
}
if(bread > MAGIC_BUFFER_SIZE) {
/* Save anyone who tampered with the header */
bread = MAGIC_BUFFER_SIZE;
}
|
048d7677 |
buff = fmap_need_off_once(map, 0, bread); |
6b7c7dc6 |
if(buff) {
sret = cli_memcpy(buffer, buff, bread);
if(sret) {
cli_errmsg("cli_filetype2: fileread error!\n");
return CL_TYPE_ERROR;
}
sret = 0;
} else {
return CL_TYPE_ERROR;
} |
1d1c4b15 |
if(basetype == CL_TYPE_PART_ANY) { /* typing a partition */
ret = cli_partitiontype(buff, bread, engine);
}
else { /* typing a file */
ret = cli_filetype(buff, bread, engine);
if(ret == CL_TYPE_BINARY_DATA) {
switch(is_tar(buff, bread)) {
case 1:
cli_dbgmsg("Recognized old fashioned tar file\n");
return CL_TYPE_OLD_TAR;
case 2:
cli_dbgmsg("Recognized POSIX tar file\n");
return CL_TYPE_POSIX_TAR;
} |
5f20aa74 |
} else if (ret == CL_TYPE_ZIP && bread > 2*(SIZEOF_LOCAL_HEADER+5)) { |
70ef8414 |
const char lhdr_magic[4] = {0x50,0x4b,0x03,0x04}; |
44006f3e |
const unsigned char *zbuff = buff;
uint32_t zread = bread;
uint64_t zoff = bread;
const unsigned char * znamep = buff;
int32_t zlen = bread;
int lhc = 0; |
8059ffb7 |
int zi, i, likely_ooxml = 0; |
c8c80ddf |
cli_file_t ret2; |
5f20aa74 |
|
44006f3e |
for (zi=0; zi<32; zi++) { |
cd94be7a |
znamep = (const unsigned char *)cli_memstr((const char *)znamep, zlen, lhdr_magic, 4); |
44006f3e |
if (NULL != znamep) { |
5f20aa74 |
znamep += SIZEOF_LOCAL_HEADER; |
44006f3e |
zlen = zread - (znamep - zbuff); |
8250b1a7 |
if (zlen > OOXML_DETECT_MAXLEN) { |
8059ffb7 |
for (i = 0; ooxml_detect[i].entry; i++) { |
8250b1a7 |
if (0 == memcmp(znamep, ooxml_detect[i].entry, ooxml_detect[i].len)) {
if (ooxml_detect[i].type != CL_TYPE_ZIP) {
OOXML_FTIDENTIFIED(ooxml_detect[i].type);
/* returns any unexpected type detection */
return ooxml_detect[i].type; |
8059ffb7 |
} |
8250b1a7 |
likely_ooxml = 1; |
c6f7be55 |
}
} |
8250b1a7 |
/* only check first three readable zip headers */ |
c8c80ddf |
if (++lhc > 2) { |
8250b1a7 |
/* if likely, check full archive */ |
c8c80ddf |
if (likely_ooxml) {
cli_dbgmsg("Likely OOXML, checking additional zip headers\n");
if ((ret2 = cli_ooxml_filetype(NULL, map)) != CL_SUCCESS) {
/* either an error or retyping has occurred, return error or just CL_TYPE_ZIP? */ |
8059ffb7 |
OOXML_FTIDENTIFIED(ret2); |
8250b1a7 |
/* falls-through to additional filetyping */ |
c8c80ddf |
}
}
break; |
44006f3e |
} |
8250b1a7 |
}
else {
znamep = NULL; /* force to map more */
} |
44006f3e |
}
if (znamep == NULL) { |
5f20aa74 |
if (map->len-zoff > SIZEOF_LOCAL_HEADER) {
zoff -= SIZEOF_LOCAL_HEADER+OOXML_DETECT_MAXLEN+1; /* remap for SIZEOF_LOCAL_HEADER+filelen for header overlap map boundary */ |
44006f3e |
zread = MIN(MAGIC_BUFFER_SIZE, map->len-zoff);
zbuff = fmap_need_off_once(map, zoff, zread);
if (zbuff == NULL) {
cli_dbgmsg("cli_filetype2: error mapping data for OOXML check\n");
return CL_TYPE_ERROR;
}
zoff += zread;
znamep = zbuff;
zlen = zread;
}
else {
break; /* end of data */ |
70ef8414 |
}
} |
44006f3e |
} |
6c2feae2 |
} else if (ret == CL_TYPE_MBR) { |
e5d13808 |
/* given filetype sig type 0 */
int iret = cli_mbr_check(buff, bread, map->len);
if (iret == CL_TYPE_GPT) {
cli_dbgmsg("Recognized GUID Partition Table file\n");
return CL_TYPE_GPT;
}
else if (iret == CL_CLEAN) {
return CL_TYPE_MBR; |
6c2feae2 |
}
/* re-detect type */
cli_dbgmsg("Recognized binary data\n");
ret = CL_TYPE_BINARY_DATA; |
70ef8414 |
} |
11dbe195 |
}
|
c8f2d060 |
if(ret >= CL_TYPE_TEXT_ASCII && ret <= CL_TYPE_BINARY_DATA) {
/* HTML files may contain special characters and could be
* misidentified as BINARY_DATA by cli_filetype()
*/ |
bd988961 |
root = engine->root[0];
if(!root)
return ret;
|
aca9ea82 |
if(cli_ac_initdata(&mdata, root->ac_partsigs, root->ac_lsigs, root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) |
bd988961 |
return ret;
|
33872a43 |
sret = cli_ac_scanbuff(buff, bread, NULL, NULL, NULL, engine->root[0], &mdata, 0, ret, NULL, AC_SCAN_FT, NULL); |
4e9ab8ed |
cli_ac_freedata(&mdata); |
bd988961 |
if(sret >= CL_TYPENO) {
ret = sret;
} else { |
aca9ea82 |
if(cli_ac_initdata(&mdata, root->ac_partsigs, root->ac_lsigs, root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) |
4e9ab8ed |
return ret;
|
72ce4b70 |
decoded = (unsigned char *) cli_utf16toascii((char *) buff, bread); |
bd988961 |
if(decoded) { |
020ba3ce |
sret = cli_ac_scanbuff(decoded, bread / 2, NULL, NULL, NULL, engine->root[0], &mdata, 0, CL_TYPE_TEXT_ASCII, NULL, AC_SCAN_FT, NULL); |
bd988961 |
free(decoded);
if(sret == CL_TYPE_HTML)
ret = CL_TYPE_HTML_UTF16;
} |
4e9ab8ed |
cli_ac_freedata(&mdata); |
3506ac49 |
|
692bda68 |
if((((struct cli_dconf*) engine->dconf)->phishing & PHISHING_CONF_ENTCONV) && ret != CL_TYPE_HTML_UTF16) { |
b3fc7f97 |
const char* encoding;
/* check if we can autodetect this encoding.
* If we can't don't try to detect HTML sig, since
* we just tried that above, and failed */ |
72ce4b70 |
if((encoding = encoding_detect_bom(buff, bread))) { |
306d7ac7 |
unsigned char decodedbuff[(MAGIC_BUFFER_SIZE+1)*2]; |
b3fc7f97 |
m_area_t in_area, out_area; |
5f20aa74 |
|
d9cd80c0 |
memset(decodedbuff, 0, sizeof(decodedbuff)); |
b3fc7f97 |
|
72ce4b70 |
in_area.buffer = (unsigned char *) buff; |
b3fc7f97 |
in_area.length = bread;
in_area.offset = 0;
out_area.buffer = decodedbuff;
out_area.length = sizeof(decodedbuff);
out_area.offset = 0;
|
5f20aa74 |
/* in htmlnorm we simply skip over \0 chars, allowing HTML parsing in any unicode |
b3fc7f97 |
* (multibyte characters will not be exactly handled, but that is not a problem).
* However when detecting whether a file is HTML or not, we need exact conversion.
* (just eliminating zeros and matching would introduce false positives */
if(encoding_normalize_toascii(&in_area, encoding, &out_area) >= 0 && out_area.length > 0) { |
aca9ea82 |
if(cli_ac_initdata(&mdata, root->ac_partsigs, root->ac_lsigs, root->ac_reloff_num, CLI_DEFAULT_AC_TRACKLEN)) |
b3fc7f97 |
return ret;
if(out_area.length > 0) { |
33872a43 |
sret = cli_ac_scanbuff(decodedbuff, out_area.length, NULL, NULL, NULL, engine->root[0], &mdata, 0, 0, NULL, AC_SCAN_FT, NULL); /* FIXME: can we use CL_TYPE_TEXT_ASCII instead of 0? */ |
b3fc7f97 |
if(sret == CL_TYPE_HTML) {
cli_dbgmsg("cli_filetype2: detected HTML signature in Unicode file\n");
/* htmlnorm is able to handle any unicode now, since it skips null chars */
ret = CL_TYPE_HTML;
} |
4e1127c5 |
} |
3506ac49 |
|
b3fc7f97 |
cli_ac_freedata(&mdata);
} |
4e1127c5 |
} |
3506ac49 |
} |
bd988961 |
}
}
|
a7f5fd00 |
return ret;
} |