1 /*
2  * tnef.c -- extract files from microsoft TNEF format
3  *
4  * Copyright (C)1999-2006 Mark Simpson <damned@theworld.com>
5  * Copyright (C)1997 Thomas Boll  <tb@boll.ch>	[ORIGINAL AUTHOR]
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you can either send email to this
19  * program's maintainer or write to: The Free Software Foundation,
20  * Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
21  *
22  * Commentary:
23  *       scans tnef file and extracts all attachments
24  *       attachments are written to their original file-names if possible
25  */
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif /* HAVE_CONFIG_H */
29 
30 #include "common.h"
31 
32 #include "tnef.h"
33 
34 #include "alloc.h"
35 #include "attr.h"
36 #include "debug.h"
37 #include "file.h"
38 #include "mapi_attr.h"
39 #include "options.h"
40 #include "path.h"
41 #include "rtf.h"
42 #include "util.h"
43 
44 typedef struct
45 {
46     VarLenData **text_body;
47     VarLenData **html_bodies;
48     VarLenData **rtf_bodies;
49 } MessageBody;
50 
51 typedef enum
52 {
53     TEXT = 't',
54     HTML = 'h',
55     RTF = 'r'
56 } MessageBodyTypes;
57 
58 /* Reads and decodes a object from the stream */
59 
60 static Attr*
read_object(FILE * in)61 read_object (FILE *in)
62 {
63     Attr *attr = NULL;
64 
65     /* peek to see if there is more to read from this stream */
66     int tmp_char = fgetc(in);
67     if (tmp_char == -1) return NULL;
68     ungetc(tmp_char, in);
69 
70     attr = attr_read (in);
71 
72     return attr;
73 }
74 
75 static void
free_bodies(VarLenData ** bodies,int len)76 free_bodies(VarLenData **bodies, int len)
77 {
78     while (len--)
79     {
80         XFREE(bodies[len]->data);
81         XFREE(bodies[len]);
82     }
83 }
84 
85 static File**
get_body_files(const char * filename,const char pref,const MessageBody * body)86 get_body_files (const char* filename,
87 		const char pref,
88 		const MessageBody* body)
89 {
90     File **files = NULL;
91     VarLenData **data;
92     char *ext = "";
93     char *type = "unknown";
94     int i;
95 
96     switch (pref)
97     {
98     case 'r':
99 	data = body->rtf_bodies;
100 	ext = ".rtf";
101             type = "text/rtf";
102 	break;
103     case 'h':
104 	data = body->html_bodies;
105 	ext = ".html";
106             type = "text/html";
107 	break;
108     case 't':
109 	data = body->text_body;
110 	ext = ".txt";
111             type = "text/plain";
112 	break;
113     default:
114 	data = NULL;
115 	break;
116     }
117 
118     if (data)
119     {
120 	int count = 0;
121 	char *tmp
122 	    = CHECKED_XCALLOC(char,
123 			      strlen(filename) + strlen(ext) + 1);
124 	strcpy (tmp, filename);
125 	strcat (tmp, ext);
126 
127         char *mime = CHECKED_XCALLOC(char, strlen(type) + 1);
128         strcpy (mime, type);
129 
130 	/* first get a count */
131 	while (data[count++]);
132 
133 	files = (File**)XCALLOC(File*, count + 1);
134 	for (i = 0; data[i]; i++)
135 	{
136 	    files[i] = (File*)XCALLOC(File, 1);
137 	    files[i]->name = tmp;
138             files[i]->mime_type = mime;
139 	    files[i]->len = data[i]->len;
140 	    files[i]->data
141 		= CHECKED_XMALLOC(unsigned char, data[i]->len);
142 	    memmove (files[i]->data, data[i]->data, data[i]->len);
143 	}
144     }
145     return files;
146 }
147 
148 static VarLenData**
get_text_data(Attr * attr)149 get_text_data (Attr *attr)
150 {
151     VarLenData **body = XCALLOC(VarLenData*, 2);
152 
153     body[0] = XCALLOC(VarLenData, 1);
154     body[0]->len = attr->len;
155     body[0]->data = CHECKED_XCALLOC(unsigned char, attr->len);
156     memmove (body[0]->data, attr->buf, attr->len);
157     return body;
158 }
159 
160 static VarLenData**
get_html_data(MAPI_Attr * a)161 get_html_data (MAPI_Attr *a)
162 {
163     VarLenData **body = XCALLOC(VarLenData*, a->num_values + 1);
164 
165     int j;
166     for (j = 0; j < a->num_values; j++)
167     {
168 	body[j] = XMALLOC(VarLenData, 1);
169 	body[j]->len = a->values[j].len;
170 	body[j]->data = CHECKED_XCALLOC(unsigned char, a->values[j].len);
171 	memmove (body[j]->data, a->values[j].data.buf, body[j]->len);
172     }
173     return body;
174 }
175 
176 int
data_left(FILE * input_file)177 data_left (FILE* input_file)
178 {
179     int retval = 1;
180 
181     if (feof(input_file)) retval = 0;
182     else if (input_file != stdin)
183     {
184 	/* check if there is enough data left */
185 	struct stat statbuf;
186 	size_t pos, data_left;
187 	fstat (fileno(input_file), &statbuf);
188 	pos = ftell(input_file);
189 	data_left = (statbuf.st_size - pos);
190 
191 	if (data_left > 0 && data_left < MINIMUM_ATTR_LENGTH)
192 	{
193 	    if ( CRUFT_SKIP )
194 	    {
195 		/* look for specific flavor of cruft -- trailing "\r\n" */
196 
197 		if ( data_left == 2 )
198 		{
199 		    int c = fgetc( input_file );
200 
201 		    if ( c < 0 )	/* this should never happen */
202 		    {
203 			fprintf( stderr, "ERROR: confused beyond all redemption.\n" );
204 			exit (1);
205 		    }
206 
207 		    ungetc( c, input_file );
208 
209 		    if ( c == 0x0d )		/* test for "\r" part of "\r\n" */
210 		    {
211 			/* "trust" that next char is 0x0a and ignore this cruft */
212 
213 			if ( VERBOSE_ON )
214 			    fprintf( stderr, "WARNING: garbage at end of file (ignored)\n" );
215 
216 			if ( DEBUG_ON )
217 			    debug_print( "!!garbage at end of file (ignored)\n" );
218 		    }
219 		    else
220 		    {
221 			fprintf( stderr, "ERROR: garbage at end of file.\n" );
222 		    }
223 		}
224 		else
225 		{
226 		    fprintf (stderr, "ERROR: garbage at end of file.\n");
227 		}
228 	    }
229 	    else
230 	    {
231 		fprintf (stderr, "ERROR: garbage at end of file.\n");
232 	    }
233 
234 	    retval = 0;
235 	}
236     }
237     return retval;
238 }
239 
240 
241 /* The entry point into this module.  This parses an entire TNEF file. */
242 int
parse_file(FILE * input_file,char * directory,char * body_filename,char * body_pref,int flags)243 parse_file (FILE* input_file, char* directory,
244 	    char *body_filename, char *body_pref,
245 	    int flags)
246 {
247     uint32 d;
248     uint16 key;
249     Attr *attr = NULL;
250     File *file = NULL;
251     int rtf_size = 0, html_size = 0;
252     MessageBody body;
253     memset (&body, '\0', sizeof (MessageBody));
254 
255     /* store the program options in our file global variables */
256     g_flags = flags;
257 
258     /* check that this is in fact a TNEF file */
259     d = geti32(input_file);
260     if (d != TNEF_SIGNATURE)
261     {
262 	fprintf (stdout, "Seems not to be a TNEF file\n");
263 	return 1;
264     }
265 
266     /* Get the key */
267     key = geti16(input_file);
268     debug_print ("TNEF Key: %hx\n", key);
269 
270     /* The rest of the file is a series of 'messages' and 'attachments' */
271     while ( data_left( input_file ) )
272     {
273 	attr = read_object( input_file );
274 
275 	if ( attr == NULL ) break;
276 
277 	/* This signals the beginning of a file */
278 	if (attr->name == attATTACHRENDDATA)
279 	{
280 	    if (file)
281 	    {
282 		file_write (file, directory);
283 		file_free (file);
284 	    }
285 	    else
286 	    {
287 		file = CHECKED_XCALLOC (File, 1);
288 	    }
289 	}
290 
291 	/* Add the data to our lists. */
292 	switch (attr->lvl_type)
293 	{
294 	case LVL_MESSAGE:
295 	    if (attr->name == attBODY)
296 	    {
297 		body.text_body = get_text_data (attr);
298 	    }
299 	    else if (attr->name == attMAPIPROPS)
300 	    {
301 		MAPI_Attr **mapi_attrs
302 		    = mapi_attr_read (attr->len, attr->buf);
303 		if (mapi_attrs)
304 		{
305 		    int i;
306 		    for (i = 0; mapi_attrs[i]; i++)
307 		    {
308 			MAPI_Attr *a = mapi_attrs[i];
309 
310 			if (a->name == MAPI_BODY_HTML)
311 			{
312 			    body.html_bodies = get_html_data (a);
313                                 html_size = a->num_values;
314 			}
315 			else if (a->name == MAPI_RTF_COMPRESSED)
316 			{
317 			    body.rtf_bodies = get_rtf_data (a);
318                                 rtf_size = a->num_values;
319 			}
320 		    }
321 		    /* cannot save attributes to file, since they
322 		     * are not attachment attributes */
323 		    /* file_add_mapi_attrs (file, mapi_attrs); */
324 		    mapi_attr_free_list (mapi_attrs);
325 		    XFREE (mapi_attrs);
326 		}
327 	    }
328 	    break;
329 	case LVL_ATTACHMENT:
330 	    file_add_attr (file, attr);
331 	    break;
332 	default:
333 	    fprintf (stderr, "Invalid lvl type on attribute: %d\n",
334 		     attr->lvl_type);
335 	    return 1;
336 	    break;
337 	}
338 	attr_free (attr);
339 	XFREE (attr);
340     }
341 
342     if (file)
343     {
344 	file_write (file, directory);
345 	file_free (file);
346 	XFREE (file);
347     }
348 
349     /* Write the message body */
350     if (flags & SAVEBODY)
351     {
352 	int i = 0;
353 	int all_flag = 0;
354 	if (strcmp (body_pref, "all") == 0)
355 	{
356 	    all_flag = 1;
357 	    body_pref = "rht";
358 	}
359 
360 	for (; i < 3; i++)
361 	{
362 	    File **files
363 		= get_body_files (body_filename, body_pref[i], &body);
364 	    if (files)
365 	    {
366 		int j = 0;
367 		for (; files[j]; j++)
368 		{
369 		    file_write(files[j], directory);
370 		    file_free (files[j]);
371                     XFREE(files[j]);
372 		}
373 		XFREE(files);
374 		if (!all_flag) break;
375 	    }
376 	}
377     }
378 
379     if (body.text_body)
380     {
381         free_bodies(body.text_body, 1);
382         XFREE(body.text_body);
383     }
384     if (rtf_size > 0)
385     {
386         free_bodies(body.rtf_bodies, rtf_size);
387         XFREE(body.rtf_bodies);
388     }
389     if (html_size > 0)
390     {
391         free_bodies(body.html_bodies, html_size);
392         XFREE(body.html_bodies);
393     }
394     return 0;
395 }
396 
397