1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Colin Leroy <colin@colino.net>
4  * and the Claws Mail Team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #include "claws-features.h"
24 #endif
25 
26 #include <unistd.h>
27 #include <stdio.h>
28 
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32 
33 #ifdef YTNEF_H_SUBDIR
34 #include <libytnef/tnef-types.h>
35 #include <libytnef/ytnef.h>
36 #include <libytnef/mapi.h>
37 #include <libytnef/mapidefs.h>
38 #else
39 #include <tnef-types.h>
40 #include <ytnef.h>
41 #include <mapi.h>
42 #include <mapidefs.h>
43 #endif
44 
45 #include "common/claws.h"
46 #include "common/version.h"
47 #include "main.h"
48 #include "plugin.h"
49 #include "procmime.h"
50 #include "utils.h"
51 #include "file-utils.h"
52 
53 #include "tnef_dump.h"
54 
55 static MimeParser *tnef_parser = NULL;
56 
tnef_broken_mimeinfo(const gchar * reason)57 static MimeInfo *tnef_broken_mimeinfo(const gchar *reason)
58 {
59 	MimeInfo *sub_info = NULL;
60 	gchar *tmpfilename = NULL;
61 	FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
62 	GStatBuf statbuf;
63 
64 	if (!fp) {
65 		g_free(tmpfilename);
66 		return NULL;
67 	}
68 	sub_info = procmime_mimeinfo_new();
69 	sub_info->content = MIMECONTENT_FILE;
70 	sub_info->data.filename = tmpfilename;
71 	sub_info->type = MIMETYPE_TEXT;
72 	sub_info->subtype = g_strdup("plain");
73 
74 	fprintf(fp, _("\n"
75 			"Claws Mail TNEF parser:\n\n"
76 			"%s\n"), reason?reason:_("Unknown error"));
77 
78 	claws_fclose(fp);
79 	if (g_stat(tmpfilename, &statbuf) < 0) {
80 		claws_unlink(tmpfilename);
81 		procmime_mimeinfo_free_all(&sub_info);
82 		return NULL;
83 
84 	}
85 
86 	sub_info->tmp = TRUE;
87 	sub_info->length = statbuf.st_size;
88 	sub_info->encoding_type = ENC_BINARY;
89 
90 	return sub_info;
91 
92 }
93 
tnef_dump_file(const gchar * filename,char * data,size_t size)94 static MimeInfo *tnef_dump_file(const gchar *filename, char *data, size_t size)
95 {
96 	MimeInfo *sub_info = NULL;
97 	gchar *tmpfilename = NULL;
98 	FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
99 	GStatBuf statbuf;
100 	gchar *content_type = NULL;
101 	if (!fp) {
102 		g_free(tmpfilename);
103 		return NULL;
104 	}
105 	sub_info = procmime_mimeinfo_new();
106 	sub_info->content = MIMECONTENT_FILE;
107 	sub_info->data.filename = tmpfilename;
108 	sub_info->type = MIMETYPE_APPLICATION;
109 	sub_info->subtype = g_strdup("octet-stream");
110 
111 	if (filename) {
112 		g_hash_table_insert(sub_info->typeparameters,
113 				    g_strdup("filename"),
114 				    g_strdup(filename));
115 
116 		content_type = procmime_get_mime_type(filename);
117 		if (content_type && strchr(content_type, '/')) {
118 			g_free(sub_info->subtype);
119 			sub_info->subtype = g_strdup(strchr(content_type, '/')+1);
120 			*(strchr(content_type, '/')) = '\0';
121 			sub_info->type = procmime_get_media_type(content_type);
122 			g_free(content_type);
123 		}
124 	}
125 
126 	if (claws_fwrite(data, 1, size, fp) < size) {
127 		FILE_OP_ERROR(tmpfilename, "claws_fwrite");
128 		claws_fclose(fp);
129 		claws_unlink(tmpfilename);
130 		procmime_mimeinfo_free_all(&sub_info);
131 		return tnef_broken_mimeinfo(_("Failed to write the part data."));
132 	}
133 	claws_fclose(fp);
134 
135 	if (g_stat(tmpfilename, &statbuf) < 0) {
136 		claws_unlink(tmpfilename);
137 		procmime_mimeinfo_free_all(&sub_info);
138 		return tnef_broken_mimeinfo(_("Failed to write the part data."));
139 	} else {
140 		sub_info->tmp = TRUE;
141 		sub_info->length = statbuf.st_size;
142 		sub_info->encoding_type = ENC_BINARY;
143 	}
144 
145 	return sub_info;
146 }
147 
tnef_parse_vcal(TNEFStruct * tnef)148 MimeInfo *tnef_parse_vcal(TNEFStruct *tnef)
149 {
150 	MimeInfo *sub_info = NULL;
151 	gchar *tmpfilename = NULL;
152 	FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
153 	GStatBuf statbuf;
154 	gboolean result = FALSE;
155 	if (!fp) {
156 		g_free(tmpfilename);
157 		return NULL;
158 	}
159 	sub_info = procmime_mimeinfo_new();
160 	sub_info->content = MIMECONTENT_FILE;
161 	sub_info->data.filename = tmpfilename;
162 	sub_info->type = MIMETYPE_TEXT;
163 	sub_info->subtype = g_strdup("calendar");
164 	g_hash_table_insert(sub_info->typeparameters,
165 			    g_strdup("filename"),
166 			    g_strdup("calendar.ics"));
167 
168 	result = SaveVCalendar(fp, tnef);
169 
170 	claws_fclose(fp);
171 
172 	if (g_stat(tmpfilename, &statbuf) < 0) {
173 		result = FALSE;
174 	} else {
175 		sub_info->tmp = TRUE;
176 		sub_info->length = statbuf.st_size;
177 		sub_info->encoding_type = ENC_BINARY;
178 	}
179 
180 	if (!result) {
181 		claws_unlink(tmpfilename);
182 		procmime_mimeinfo_free_all(&sub_info);
183 		return tnef_broken_mimeinfo(_("Failed to parse VCalendar data."));
184 	}
185 	return sub_info;
186 }
187 
tnef_parse_vtask(TNEFStruct * tnef)188 MimeInfo *tnef_parse_vtask(TNEFStruct *tnef)
189 {
190 	MimeInfo *sub_info = NULL;
191 	gchar *tmpfilename = NULL;
192 	FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
193 	GStatBuf statbuf;
194 	gboolean result = FALSE;
195 	if (!fp) {
196 		g_free(tmpfilename);
197 		return NULL;
198 	}
199 	sub_info = procmime_mimeinfo_new();
200 	sub_info->content = MIMECONTENT_FILE;
201 	sub_info->data.filename = tmpfilename;
202 	sub_info->type = MIMETYPE_TEXT;
203 	sub_info->subtype = g_strdup("calendar");
204 	g_hash_table_insert(sub_info->typeparameters,
205 			    g_strdup("filename"),
206 			    g_strdup("task.ics"));
207 
208 	result = SaveVTask(fp, tnef);
209 
210 	claws_fclose(fp);
211 
212 	if (g_stat(tmpfilename, &statbuf) < 0) {
213 		result = FALSE;
214 	} else {
215 		sub_info->tmp = TRUE;
216 		sub_info->length = statbuf.st_size;
217 		sub_info->encoding_type = ENC_BINARY;
218 	}
219 	if (!result) {
220 		claws_unlink(tmpfilename);
221 		procmime_mimeinfo_free_all(&sub_info);
222 		return tnef_broken_mimeinfo(_("Failed to parse VTask data."));
223 	}
224 	return sub_info;
225 }
226 
tnef_parse_rtf(TNEFStruct * tnef,variableLength * tmp_var)227 MimeInfo *tnef_parse_rtf(TNEFStruct *tnef, variableLength *tmp_var)
228 {
229 	variableLength buf;
230 	MimeInfo *info = NULL;
231 	buf.data = DecompressRTF(tmp_var, &(buf.size));
232 	if (buf.data) {
233 		info = tnef_dump_file("message.rtf", buf.data, buf.size);
234 		free(buf.data);
235 		return info;
236 	} else {
237 		return NULL;
238 	}
239 }
240 
tnef_parse_vcard(TNEFStruct * tnef)241 MimeInfo *tnef_parse_vcard(TNEFStruct *tnef)
242 {
243 	MimeInfo *sub_info = NULL;
244 	gchar *tmpfilename = NULL;
245 	FILE *fp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
246 	GStatBuf statbuf;
247 	gboolean result = FALSE;
248 	gint ret;
249 	if (!fp) {
250 		g_free(tmpfilename);
251 		return NULL;
252 	}
253 	sub_info = procmime_mimeinfo_new();
254 	sub_info->content = MIMECONTENT_FILE;
255 	sub_info->data.filename = tmpfilename;
256 	sub_info->type = MIMETYPE_TEXT;
257 	sub_info->subtype = g_strdup("x-vcard");
258 	g_hash_table_insert(sub_info->typeparameters,
259 			    g_strdup("filename"),
260 			    g_strdup("contact.vcf"));
261 
262 	result = SaveVCard(fp, tnef);
263 
264 	claws_fclose(fp);
265 
266 	ret = g_stat(tmpfilename, &statbuf);
267 	if (ret == -1) {
268 		debug_print("couldn't stat tmpfilename '%s'\n", tmpfilename);
269 	}
270 
271 	if ((ret == -1) || !result) {
272 		claws_unlink(tmpfilename);
273 		procmime_mimeinfo_free_all(&sub_info);
274 		return tnef_broken_mimeinfo(_("Failed to parse VCard data."));
275 	}
276 
277 	sub_info->tmp = TRUE;
278 	sub_info->length = statbuf.st_size;
279 	sub_info->encoding_type = ENC_BINARY;
280 	return sub_info;
281 }
282 
tnef_parse(MimeParser * parser,MimeInfo * mimeinfo)283 static gboolean tnef_parse (MimeParser *parser, MimeInfo *mimeinfo)
284 {
285 	TNEFStruct *tnef;
286 	MimeInfo *sub_info = NULL;
287 	variableLength *tmp_var;
288 	Attachment *att;
289 	int parse_result = 0;
290 	gboolean cal_done = FALSE;
291 
292 	if (!procmime_decode_content(mimeinfo)) {
293 		debug_print("error decoding\n");
294 		return FALSE;
295 	}
296 	debug_print("Tnef parser parsing part (%d).\n", mimeinfo->length);
297 	if (mimeinfo->content == MIMECONTENT_FILE)
298 		debug_print("content: %s\n", mimeinfo->data.filename);
299 	else
300 		debug_print("contents in memory (len %"G_GSIZE_FORMAT")\n",
301 			strlen(mimeinfo->data.mem));
302 
303 	tnef = g_new0(TNEFStruct, 1);
304 	TNEFInitialize(tnef);
305 
306 	tnef->Debug = debug_get_mode();
307 
308 	if (mimeinfo->content == MIMECONTENT_MEM)
309 		parse_result = TNEFParseMemory(mimeinfo->data.mem, mimeinfo->length, tnef);
310 	else
311 		parse_result = TNEFParseFile(mimeinfo->data.filename, tnef);
312 
313 	mimeinfo->type = MIMETYPE_MULTIPART;
314 	mimeinfo->subtype = g_strdup("mixed");
315 	g_hash_table_insert(mimeinfo->typeparameters,
316 			    g_strdup("description"),
317 			    g_strdup("Parsed from MS-TNEF"));
318 
319 	if (parse_result != 0) {
320 		g_warning("Failed to parse TNEF data.");
321 		TNEFFree(tnef);
322 		return FALSE;
323 	}
324 
325 	sub_info = NULL;
326 	if (tnef->messageClass[0] != '\0') {
327 		if (strcmp(tnef->messageClass, "IPM.Contact") == 0)
328 			sub_info = tnef_parse_vcard(tnef);
329 		else if (strcmp(tnef->messageClass, "IPM.Task") == 0)
330 			sub_info = tnef_parse_vtask(tnef);
331 		else if (strcmp(tnef->messageClass, "IPM.Appointment") == 0) {
332 			sub_info = tnef_parse_vcal(tnef);
333 			cal_done = TRUE;
334 		}
335 	}
336 
337 	if (sub_info)
338 		g_node_append(mimeinfo->node, sub_info->node);
339 	sub_info = NULL;
340 
341 	if (tnef->MapiProperties.count > 0) {
342 		tmp_var = MAPIFindProperty (&(tnef->MapiProperties), PROP_TAG(PT_BINARY,PR_RTF_COMPRESSED));
343 		if (tmp_var != MAPI_UNDEFINED) {
344 			sub_info = tnef_parse_rtf(tnef, tmp_var);
345 		}
346 	}
347 
348 	if (sub_info)
349 		g_node_append(mimeinfo->node, sub_info->node);
350 	sub_info = NULL;
351 
352 	tmp_var = MAPIFindUserProp(&(tnef->MapiProperties), PROP_TAG(PT_STRING8,0x24));
353 	if (tmp_var != MAPI_UNDEFINED) {
354 		if (!cal_done && strcmp(tmp_var->data, "IPM.Appointment") == 0) {
355 			sub_info = tnef_parse_vcal(tnef);
356 		}
357 	}
358 
359 	if (sub_info)
360 		g_node_append(mimeinfo->node, sub_info->node);
361 	sub_info = NULL;
362 
363 	att = tnef->starting_attach.next;
364 	while (att) {
365 		gchar *filename = NULL;
366 		gboolean is_object = TRUE;
367 		DWORD signature;
368 
369 		tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(30,0x3707));
370 		if (tmp_var == MAPI_UNDEFINED)
371 			tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(30,0x3001));
372 		if (tmp_var == MAPI_UNDEFINED)
373 			tmp_var = &(att->Title);
374 
375 		if (tmp_var->data)
376 			filename = g_strdup(tmp_var->data);
377 
378 		tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(PT_OBJECT, PR_ATTACH_DATA_OBJ));
379 		if (tmp_var == MAPI_UNDEFINED)
380 			tmp_var = MAPIFindProperty(&(att->MAPI), PROP_TAG(PT_BINARY, PR_ATTACH_DATA_OBJ));
381 		if (tmp_var == MAPI_UNDEFINED) {
382 			tmp_var = &(att->FileData);
383 			is_object = FALSE;
384 		}
385 
386 		sub_info = tnef_dump_file(filename,
387 			tmp_var->data + (is_object ? 16:0),
388 			tmp_var->size - (is_object ? 16:0));
389 
390 		if (sub_info)
391 			g_node_append(mimeinfo->node, sub_info->node);
392 
393 		memcpy(&signature, tmp_var->data+(is_object ? 16:0), sizeof(DWORD));
394 
395 		if (TNEFCheckForSignature(signature) == 0) {
396 			debug_print("that's TNEF stuff, process it\n");
397 			tnef_parse(parser, sub_info);
398 		}
399 
400 		sub_info = NULL;
401 
402 		att = att->next;
403 
404 		g_free(filename);
405 	}
406 
407 	TNEFFree(tnef);
408 	return TRUE;
409 }
410 
plugin_init(gchar ** error)411 gint plugin_init(gchar **error)
412 {
413 	if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
414 				VERSION_NUMERIC, _("TNEF Parser"), error))
415 		return -1;
416 
417 	tnef_parser = g_new0(MimeParser, 1);
418 	tnef_parser->type = MIMETYPE_APPLICATION;
419 	tnef_parser->sub_type = "ms-tnef";
420 	tnef_parser->parse = tnef_parse;
421 
422 	procmime_mimeparser_register(tnef_parser);
423 
424 	return 0;
425 }
426 
plugin_done(void)427 gboolean plugin_done(void)
428 {
429 	procmime_mimeparser_unregister(tnef_parser);
430 	g_free(tnef_parser);
431 	tnef_parser = NULL;
432 
433 	return TRUE;
434 }
435 
plugin_name(void)436 const gchar *plugin_name(void)
437 {
438 	return _("TNEF Parser");
439 }
440 
plugin_desc(void)441 const gchar *plugin_desc(void)
442 {
443 	return _("This Claws Mail plugin allows you to read application/ms-tnef attachments.\n\n"
444 		 "The plugin uses the Ytnef library, which is copyright 2002-2007 by "
445 		 "Randall Hand <yerase@yerot.com>");
446 }
447 
plugin_type(void)448 const gchar *plugin_type(void)
449 {
450 	return "GTK2";
451 }
452 
plugin_licence(void)453 const gchar *plugin_licence(void)
454 {
455 	return "GPL3+";
456 }
457 
plugin_version(void)458 const gchar *plugin_version(void)
459 {
460 	return VERSION;
461 }
462 
plugin_provides(void)463 struct PluginFeature *plugin_provides(void)
464 {
465 	static struct PluginFeature features[] =
466 		{ {PLUGIN_MIMEPARSER, "application/ms-tnef"},
467 		  {PLUGIN_NOTHING, NULL}};
468 	return features;
469 }
470