1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2007-2013 Sourcefire, Inc.
4  *
5  *  Authors: Nigel Horne
6  *
7  *  Acknowledgements: The algorithm was based on
8  *                    kdepim/ktnef/lib/ktnefparser.cpp from KDE.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License version 2 as
12  *  published by the Free Software Foundation.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  *  MA 02110-1301, USA.
23  */
24 
25 #if HAVE_CONFIG_H
26 #include "clamav-config.h"
27 #endif
28 
29 #include <stdio.h>
30 #include <fcntl.h>
31 
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 
36 #include "clamav.h"
37 #include "others.h"
38 
39 #include "mbox.h"
40 #include "tnef.h"
41 
42 static int tnef_message(fmap_t *map, off_t *pos, uint16_t type, uint16_t tag, int32_t length, off_t fsize);
43 static int tnef_attachment(fmap_t *map, off_t *pos, uint16_t type, uint16_t tag, int32_t length, const char *dir, fileblob **fbref, off_t fsize);
44 static int tnef_header(fmap_t *map, off_t *pos, uint8_t *part, uint16_t *type, uint16_t *tag, int32_t *length);
45 
46 #define TNEF_SIGNATURE 0x223E9f78
47 #define LVL_MESSAGE 0x01
48 #define LVL_ATTACHMENT 0x02
49 
50 #define attMSGCLASS 0x8008
51 #define attBODY 0x800c
52 #define attATTACHDATA 0x800f  /* Attachment Data */
53 #define attATTACHTITLE 0x8010 /* Attachment File Name */
54 #define attDATEMODIFIED 0x8020
55 #define attTNEFVERSION 0x9006
56 #define attOEMCODEPAGE 0x9007
57 
58 #define host16(v) le16_to_host(v)
59 #define host32(v) le32_to_host(v)
60 
61 /* a TNEF file must be at least this size */
62 #define MIN_SIZE (sizeof(uint32_t) + sizeof(uint16_t))
63 
cli_tnef(const char * dir,cli_ctx * ctx)64 int cli_tnef(const char *dir, cli_ctx *ctx)
65 {
66     uint32_t i32;
67     uint16_t i16;
68     fileblob *fb;
69     int ret, alldone;
70     off_t fsize, pos = 0;
71 
72     fsize = ctx->fmap->len;
73 
74     if (fsize < (off_t)MIN_SIZE) {
75         cli_dbgmsg("cli_tngs: file too small, ignoring\n");
76         return CL_CLEAN;
77     }
78 
79     if (fmap_readn(ctx->fmap, &i32, pos, sizeof(uint32_t)) != sizeof(uint32_t)) {
80         /* The file is at least MIN_SIZE bytes, so it "can't" fail */
81         return CL_EREAD;
82     }
83     pos += sizeof(uint32_t);
84 
85     if (host32(i32) != TNEF_SIGNATURE) {
86         return CL_EFORMAT;
87     }
88 
89     if (fmap_readn(ctx->fmap, &i16, pos, sizeof(uint16_t)) != sizeof(uint16_t)) {
90         /* The file is at least MIN_SIZE bytes, so it "can't" fail */
91         return CL_EREAD;
92     }
93     pos += sizeof(uint16_t);
94 
95     fb      = NULL;
96     ret     = CL_CLEAN; /* we don't know if it's clean or not :-) */
97     alldone = 0;
98 
99     do {
100         uint8_t part  = 0;
101         uint16_t type = 0, tag = 0;
102         int32_t length = 0;
103 
104         switch (tnef_header(ctx->fmap, &pos, &part, &type, &tag, &length)) {
105             case 0:
106                 alldone = 1;
107                 break;
108             case 1:
109                 break;
110             default:
111                 /*
112 				 * Assume truncation, not file I/O error
113 				 */
114                 cli_warnmsg("cli_tnef: file truncated, returning CLEAN\n");
115                 ret     = CL_CLEAN;
116                 alldone = 1;
117                 break;
118         }
119         if (length == 0)
120             continue;
121         if (length < 0) {
122             cli_warnmsg("Corrupt TNEF header detected - length %d\n",
123                         (int)length);
124             ret = CL_EFORMAT;
125             break;
126         }
127         if (alldone)
128             break;
129         switch (part) {
130             case LVL_MESSAGE:
131                 cli_dbgmsg("TNEF - found message\n");
132                 if (fb != NULL) {
133                     fileblobDestroy(fb);
134                     fb = NULL;
135                 }
136                 fb = fileblobCreate();
137                 if (tnef_message(ctx->fmap, &pos, type, tag, length, fsize) != 0) {
138                     cli_dbgmsg("TNEF: Error reading TNEF message\n");
139                     ret     = CL_EFORMAT;
140                     alldone = 1;
141                 }
142                 break;
143             case LVL_ATTACHMENT:
144                 cli_dbgmsg("TNEF - found attachment\n");
145                 if (tnef_attachment(ctx->fmap, &pos, type, tag, length, dir, &fb, fsize) != 0) {
146                     cli_dbgmsg("TNEF: Error reading TNEF attachment\n");
147                     ret     = CL_EFORMAT;
148                     alldone = 1;
149                 }
150                 break;
151             case 0:
152                 break;
153             default:
154                 cli_warnmsg("TNEF - unknown level %d tag 0x%x\n", (int)part, (int)tag);
155 
156                 /*
157 				 * Dump the file incase it was part of an
158 				 * email that's about to be deleted
159 				 */
160                 if (cli_debug_flag) {
161                     int fout       = -1;
162                     char *filename = cli_gentemp(ctx->sub_tmpdir);
163                     char buffer[BUFSIZ];
164 
165                     if (filename)
166                         fout = open(filename, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_BINARY, 0600);
167 
168                     if (fout >= 0) {
169                         size_t count;
170 
171                         cli_warnmsg("Saving dump to %s:  refer to https://docs.clamav.net/manual/Installing.html\n", filename);
172 
173                         pos = 0;
174                         while ((count = fmap_readn(ctx->fmap, buffer, pos, sizeof(buffer))) != (size_t)-1) {
175                             pos += count;
176                             cli_writen(fout, buffer, count);
177                         }
178                         close(fout);
179                     }
180                     free(filename);
181                 }
182                 ret     = CL_EFORMAT;
183                 alldone = 1;
184                 break;
185         }
186     } while (!alldone);
187 
188     if (fb) {
189         cli_dbgmsg("cli_tnef: flushing final data\n");
190         if (fileblobGetFilename(fb) == NULL) {
191             cli_dbgmsg("Saving TNEF portion with an unknown name\n");
192             fileblobSetFilename(fb, dir, "tnef");
193         }
194         fileblobDestroy(fb);
195         fb = NULL;
196     }
197 
198     cli_dbgmsg("cli_tnef: returning %d\n", ret);
199     return ret;
200 }
201 
202 static int
tnef_message(fmap_t * map,off_t * pos,uint16_t type,uint16_t tag,int32_t length,off_t fsize)203 tnef_message(fmap_t *map, off_t *pos, uint16_t type, uint16_t tag, int32_t length, off_t fsize)
204 {
205     off_t offset;
206 #ifdef CL_DEBUG
207     uint32_t i32;
208     char *string;
209 #else
210     UNUSEDPARAM(map);
211 #endif
212 
213     cli_dbgmsg("message tag 0x%x, type 0x%x, length %d\n", tag, type,
214                (int)length);
215 
216     offset = *pos;
217 
218     /*
219 	 * a lot of this stuff should be only discovered in debug mode...
220 	 */
221     switch (tag) {
222         case attBODY:
223             cli_warnmsg("TNEF body not being scanned - if you believe this file contains a virus, submit it to www.clamav.net\n");
224             break;
225 #ifdef CL_DEBUG
226         case attTNEFVERSION:
227             /*assert(length == sizeof(uint32_t))*/
228             if (fmap_readn(map, &i32, *pos, sizeof(uint32_t)) != sizeof(uint32_t))
229                 return -1;
230             (*pos) += sizeof(uint32_t);
231             i32 = host32(i32);
232             cli_dbgmsg("TNEF version %d\n", i32);
233             break;
234         case attOEMCODEPAGE:
235             /* 8 bytes, but just print the first 4 */
236             /*assert(length == sizeof(uint32_t))*/
237             if (fmap_readn(map, &i32, *pos, sizeof(uint32_t)) != sizeof(uint32_t))
238                 return -1;
239             (*pos) += sizeof(uint32_t);
240             i32 = host32(i32);
241             cli_dbgmsg("TNEF codepage %d\n", i32);
242             break;
243         case attDATEMODIFIED:
244             /* 14 bytes, long */
245             break;
246         case attMSGCLASS:
247             if (length <= 0)
248                 return -1;
249             string = cli_malloc(length + 1);
250             if (string == NULL) {
251                 cli_errmsg("tnef_message: Unable to allocate memory for string\n");
252                 return -1;
253             }
254             if ((uint32_t)fmap_readn(map, string, *pos, (uint32_t)length) != (uint32_t)length) {
255                 free(string);
256                 return -1;
257             }
258             (*pos) += (uint32_t)length;
259             string[length] = '\0';
260             cli_dbgmsg("TNEF class %s\n", string);
261             free(string);
262             break;
263         default:
264             cli_dbgmsg("TNEF - unsupported message tag 0x%x type 0x%d length %d\n", tag, type, length);
265             break;
266 #endif
267     }
268 
269     /*cli_dbgmsg("%lu %lu\n", (long)(offset + length), ftell(fp));*/
270 
271     if (!CLI_ISCONTAINED_2(0, fsize, offset, length)) {
272         cli_dbgmsg("TNEF: Incorrect length field in tnef_message\n");
273         return -1;
274     }
275     (*pos) = offset + length;
276 
277     /* Checksum - TODO, verify */
278     (*pos) += 2;
279 
280     return 0;
281 }
282 
283 static int
tnef_attachment(fmap_t * map,off_t * pos,uint16_t type,uint16_t tag,int32_t length,const char * dir,fileblob ** fbref,off_t fsize)284 tnef_attachment(fmap_t *map, off_t *pos, uint16_t type, uint16_t tag, int32_t length, const char *dir, fileblob **fbref, off_t fsize)
285 {
286     uint32_t todo;
287     off_t offset;
288     char *string;
289 
290     cli_dbgmsg("attachment tag 0x%x, type 0x%x, length %d\n", tag, type,
291                (int)length);
292 
293     offset = *pos;
294 
295     switch (tag) {
296         case attATTACHTITLE:
297             if (length <= 0)
298                 return -1;
299             string = cli_malloc(length + 1);
300             if (string == NULL) {
301                 cli_errmsg("tnef_attachment: Unable to allocate memory for string\n");
302                 return -1;
303             }
304             if ((uint32_t)fmap_readn(map, string, *pos, (uint32_t)length) != (uint32_t)length) {
305                 free(string);
306                 return -1;
307             }
308             (*pos) += (uint32_t)length;
309             string[length] = '\0';
310             cli_dbgmsg("TNEF filename %s\n", string);
311             if (*fbref == NULL) {
312                 *fbref = fileblobCreate();
313                 if (*fbref == NULL) {
314                     free(string);
315                     return -1;
316                 }
317             }
318             fileblobSetFilename(*fbref, dir, string);
319             free(string);
320             break;
321         case attATTACHDATA:
322             if (*fbref == NULL) {
323                 *fbref = fileblobCreate();
324                 if (*fbref == NULL)
325                     return -1;
326             }
327             todo = length;
328             while (todo) {
329                 unsigned char buf[BUFSIZ];
330                 int32_t got = fmap_readn(map, buf, *pos, MIN(sizeof(buf), todo));
331                 if (got <= 0)
332                     break;
333                 (*pos) += got;
334 
335                 fileblobAddData(*fbref, buf, got);
336                 todo -= got;
337             }
338             break;
339         default:
340             cli_dbgmsg("TNEF - unsupported attachment tag 0x%x type 0x%d length %d\n",
341                        tag, type, (int)length);
342             break;
343     }
344 
345     /*cli_dbgmsg("%lu %lu\n", (long)(offset + length), ftell(fp));*/
346 
347     if (!CLI_ISCONTAINED_2(0, fsize, (off_t)offset, (off_t)length)) {
348         cli_dbgmsg("TNEF: Incorrect length field in tnef_attachment\n");
349         return -1;
350     }
351     (*pos) = (long)(offset + length); /* shouldn't be needed */
352 
353     (*pos) += 2;
354 
355     return 0;
356 }
357 
358 static int
tnef_header(fmap_t * map,off_t * pos,uint8_t * part,uint16_t * type,uint16_t * tag,int32_t * length)359 tnef_header(fmap_t *map, off_t *pos, uint8_t *part, uint16_t *type, uint16_t *tag, int32_t *length)
360 {
361     uint32_t i32;
362     int rc;
363 
364     if (fmap_readn(map, part, *pos, 1) != 1)
365         return 0;
366     (*pos)++;
367 
368     if (*part == (uint8_t)0)
369         return 0;
370 
371     rc = fmap_readn(map, &i32, *pos, sizeof(uint32_t));
372     if (rc != sizeof(uint32_t)) {
373         if (((*part == '\n') || (*part == '\r')) && (rc == 0)) {
374             /*
375 			 * trailing newline in the file, could be caused by
376 			 * broken quoted-printable encoding in the source
377 			 * message missing a final '='
378 			 */
379             cli_dbgmsg("tnef_header: ignoring trailing newline\n");
380             return 0;
381         }
382         return -1;
383     }
384     (*pos) += sizeof(uint32_t);
385 
386     i32   = host32(i32);
387     *tag  = (uint16_t)(i32 & 0xFFFF);
388     *type = (uint16_t)((i32 & 0xFFFF0000) >> 16);
389 
390     if (fmap_readn(map, &i32, *pos, sizeof(uint32_t)) != sizeof(uint32_t))
391         return -1;
392     (*pos) += sizeof(uint32_t);
393     *length = (int32_t)host32(i32);
394 
395     cli_dbgmsg("message tag 0x%x, type 0x%x, length %d\n",
396                *tag, *type, (int)*length);
397 
398     return 1;
399 }
400