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