1 /***************************************************************************
2  * tnef2txt
3  *   A program to decode application/ms-tnef MIME attachments into text
4  *   for those fortunate enough not to be running either a Microsoft
5  *   operating system or mailer.
6  *
7  * Brandon Long (blong@uiuc.edu), April 1997
8  * 1.0 Version
9  *   Supports most types, but doesn't decode properties.  Maybe some other
10  *   time.
11  *
12  * 1.1 Version (7/1/97)
13  *   Supports saving of attAttachData to a file given by attAttachTitle
14  *   start of property decoding support
15  *
16  * 1.2 Version (7/19/97)
17  *   Some architectures don't like reading 16/32 bit data on unaligned
18  *   boundaries.  Fixed, losing efficiency, but this doesn't really
19  *   need efficiency anyways.  (Still...)
20  *   Also, the #pragma pack from the MSVC include file wasn't liked
21  *   by most Unix compilers, replaced with a GCCism.  This should work
22  *   with GCC, but other compilers I don't know.
23  *
24  * 1.3 Version (7/22/97)
25  *   Ok, take out the DTR over the stream, now uses read_16.
26  *
27  * NOTE: THIS SOFTWARE IS FOR YOUR PERSONAL GRATIFICATION ONLY.  I DON'T
28  * IMPLY IN ANY LEGAL SENSE THAT THIS SOFTWARE DOES ANYTHING OR THAT IT WILL
29  * BE USEFULL IN ANY WAY.  But, you can send me fixes to it, I don't mind.
30  ***************************************************************************/
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include "config.h"
37 #include "tnef.h"
38 #include "mapidefs.h"
39 #include "mapitags.h"
40 
41 #define VERSION "tnef2txt/1.3"
42 
43 int save_attach_data(char *title, uint8 *tsp, uint32 size);
44 
45 int Verbose = FALSE;
46 int SaveData = FALSE;
47 
48 /* Some systems don't like to read unaligned data */
read_32(uint8 * tsp)49 uint32 read_32(uint8 *tsp)
50 {
51   uint8 a,b,c,d;
52   uint32 ret;
53 
54   a = *tsp;
55   b = *(tsp+1);
56   c = *(tsp+2);
57   d = *(tsp+3);
58 
59   ret = long_little_endian(a<<24 | b<<16 | c<<8 | d);
60 
61   return ret;
62 }
63 
read_16(uint8 * tsp)64 uint16 read_16(uint8 *tsp)
65 {
66   uint8 a,b;
67   uint16 ret;
68 
69   a = *tsp;
70   b = *(tsp + 1);
71 
72   ret = little_endian(a<<8 | b);
73 
74   return ret;
75 }
76 
77 
attribute_string(uint32 attribute)78 char *attribute_string(uint32 attribute)
79 {
80   switch (attribute) {
81     case attNull:
82       return ("attNull");
83       break;
84     case attFrom:
85       return ("attFrom");
86       break;
87     case attSubject:
88       return ("attSubject");
89       break;
90     case attDateSent:
91       return ("attDateSent");
92       break;
93     case attDateRecd:
94       return ("attDateRecd");
95       break;
96     case attMessageStatus:
97       return ("attMessageStatus");
98       break;
99     case attMessageClass:
100       return ("attMessageClass");
101       break;
102     case attMessageID:
103       return ("attMessageID");
104       break;
105     case attParentID:
106       return ("attParentID");
107       break;
108     case attConversationID:
109       return ("attConversationID");
110       break;
111     case attBody:
112       return ("attBody");
113       break;
114     case attPriority:
115       return ("attPriority");
116       break;
117     case attAttachData:
118       return ("attAttachData");
119       break;
120     case attAttachTitle:
121       return ("attAttachTitle");
122       break;
123     case attAttachMetaFile:
124       return ("attAttachMetaFile");
125       break;
126     case attAttachCreateDate:
127       return ("attAttachCreateDate");
128       break;
129     case attAttachModifyDate:
130       return ("attAttachModifyDate");
131       break;
132     case attDateModified:
133       return ("attDateModified");
134       break;
135     case attAttachTransportFilename:
136       return ("attAttachTransportFilename");
137       break;
138     case attAttachRenddata:
139       return ("attAttachRenddata");
140       break;
141     case attMAPIProps:
142       return ("attMAPIProps");
143       break;
144     case attRecipTable:
145       return ("attRecipTable");
146       break;
147     case attAttachment:
148       return ("attAttachment");
149       break;
150     case attTnefVersion:
151       return ("attTnefVersion");
152       break;
153     case attOemCodepage:
154       return ("attOemCodepage");
155       break;
156     case attOriginalMessageClass:
157       return ("attOriginalMessageClass");
158       break;
159     case attOwner:
160       return ("attOwner");
161       break;
162     case attSentFor:
163       return ("attSentFor");
164       break;
165     case attDelegate:
166       return ("attDelegate");
167       break;
168     case attDateStart:
169       return ("attDateStart");
170       break;
171     case attDateEnd:
172       return ("attDateEnd");
173       break;
174     case attAidOwner:
175       return ("attAidOwner");
176       break;
177     case attRequestRes:
178       return ("attRequestRes");
179       break;
180     default:
181       return ("Unknown");
182       break;
183   }
184   return ("Unknown");
185 }
186 
type_string(uint16 type)187 char *type_string(uint16 type)
188 {
189   switch (type) {
190     case atpTriples:
191       return ("atpTriples");
192       break;
193     case atpString:
194       return ("atpString");
195       break;
196     case atpText:
197       return ("atpText");
198       break;
199     case atpDate:
200       return ("atpDate");
201       break;
202     case atpShort:
203       return ("atpShort");
204       break;
205     case atpLong:
206       return ("atpLong");
207       break;
208     case atpByte:
209       return ("atpByte");
210       break;
211     case atpWord:
212       return ("atpWord");
213       break;
214     case atpDword:
215       return ("atpDword");
216       break;
217     default:
218       return ("unknown");
219       break;
220   }
221   return ("unknown");
222 }
223 
prop_type_string(uint32 prop_tag)224 char *prop_type_string(uint32 prop_tag)
225 {
226   switch(prop_tag)
227   {
228     case PT_BINARY:
229       return ("PT_BINARY");
230       break;
231     case PT_STRING8:
232       return ("PT_STRING8");
233       break;
234     case PT_UNICODE:
235       return ("PT_UNICODE");
236       break;
237     case PT_OBJECT:
238       return ("PT_OBJECT");
239       break;
240     case PT_I2:
241       return ("PT_I2");
242       break;
243     case PT_LONG:
244       return ("PT_LONG");
245       break;
246     case PT_R4:
247       return ("PT_R4");
248       break;
249     case PT_DOUBLE:
250       return ("PT_DOUBLE");
251       break;
252     case PT_CURRENCY:
253       return ("PT_CURRENCY");
254       break;
255     case PT_APPTIME:
256       return ("PT_APPTIME");
257       break;
258     case PT_ERROR:
259       return ("PT_ERROR");
260       break;
261     case PT_BOOLEAN:
262       return ("PT_BOOLEAN");
263       break;
264     case PT_I8:
265       return ("PT_I8");
266       break;
267     case PT_SYSTIME:
268       return ("PT_SYSTIME");
269       break;
270   }
271   return ("unknown");
272 }
273 
274 char *weekday[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
275                     "Friday", "Saturday" };
276 
date_string(uint8 * tsp)277 char *date_string(uint8 *tsp)
278 {
279   static char date_string[256] = "";
280   DTR date;
281   int bytes = 0;
282 
283   date.wYear = read_16(tsp);
284   bytes += 2;
285   date.wMonth = read_16(tsp+bytes);
286   bytes += 2;
287   date.wDay = read_16(tsp+bytes);
288   bytes += 2;
289   date.wHour = read_16(tsp+bytes);
290   bytes += 2;
291   date.wMinute = read_16(tsp+bytes);
292   bytes += 2;
293   date.wSecond = read_16(tsp+bytes);
294   bytes += 2;
295   date.wDayOfWeek = read_16(tsp+bytes);
296   bytes += 2;
297 
298   sprintf(date_string, "%s, %d/%d/%d ", weekday[date.wDayOfWeek],
299       date.wMonth, date.wDay, date.wYear);
300   return date_string;
301 }
302 
make_string(uint8 * tsp,int size)303 char *make_string(uint8 *tsp, int size)
304 {
305   static char s[256] = "";
306   int len = (size>sizeof(s)-1) ? sizeof(s)-1 : size;
307 
308   strncpy(s,tsp, len);
309   s[len] = '\0';
310   return s;
311 }
312 
priority_string(uint16 pri)313 char *priority_string(uint16 pri)
314 {
315   switch(pri) {
316     case prioLow:
317       return "(Low)";
318     case prioNorm:
319       return "(Normal)";
320     case prioHigh:
321       return "(High)";
322     default:
323       return "";
324   }
325   return  "";
326 }
327 
status_string(uint8 status)328 char *status_string(uint8 status)
329 {
330   static char s[256] = "";
331 
332   s[0] = '\0';
333   if (status & fmsModified)
334     sprintf(s,"%s%s ",s,"Modified");
335   if (status & fmsLocal)
336     sprintf(s,"%s%s ",s,"Local");
337   if (status & fmsSubmitted)
338     sprintf(s,"%s%s ",s,"Submitted");
339   if (status & fmsRead)
340     sprintf(s,"%s%s ",s,"Read");
341   if (status & fmsHasAttach)
342     sprintf(s,"%s%s ",s,"HasAttach");
343   return s;
344 }
345 
handle_props(uint8 * tsp)346 int handle_props(uint8 *tsp)
347 {
348   int bytes = 0;
349   uint32 num_props = 0;
350   uint32 x = 0;
351 
352 
353   num_props = read_32(tsp);
354   bytes += sizeof(num_props);
355 
356   printf("\tNumber of Properties: %ld\n", num_props);
357   while (x < num_props)
358   {
359     uint32 prop_tag;
360     uint32 num;
361     char filename[256];
362     static int file_num = 0;
363 
364     prop_tag = read_32(tsp+bytes);
365     bytes += sizeof(prop_tag);
366     if (Verbose)
367       printf("\tProperty: %08lx (%s)\n", prop_tag,
368 	  prop_type_string(prop_tag & PROP_TYPE_MASK));
369     switch (prop_tag & PROP_TYPE_MASK)
370     {
371       case PT_BINARY:
372 	num = read_32(tsp+bytes);
373 	bytes += sizeof(num);
374 	num = read_32(tsp+bytes);
375 	bytes += sizeof(num);
376 	if (prop_tag == PR_RTF_COMPRESSED)
377 	{
378 	  sprintf (filename, "tnef_%d_%0lx.rtf", file_num, num);
379 	  file_num++;
380 	  if (SaveData) {
381 	    printf ("\tSaving Compressed RTF to %s\n", filename);
382 	    save_attach_data(filename, tsp+bytes, num);
383 	  } else {
384 	    printf ("\tContains Compressed RTF MAPI property\n");
385 	  }
386 	}
387 	/* num + PAD */
388 	bytes += num + ((num % 4) ? (4 - num%4) : 0);
389 	break;
390       case PT_STRING8:
391 	switch (prop_tag)
392 	{
393 	  case PR_CONVERSATION_TOPIC:
394 	    printf ("\tConversation Topic: ");
395 	    break;
396 	  case PR_SENDER_ADDRTYPE:
397 	    printf ("\tSender Address Type: ");
398 	    break;
399 	  case PR_SENDER_EMAIL_ADDRESS:
400 	    printf ("\tSender Email Address: ");
401 	    break;
402 	  case PR_RTF_SYNC_BODY_TAG:
403 	    printf ("\tRTF Sync body tag: ");
404 	    break;
405 	  case PR_SUBJECT_PREFIX:
406 	    printf ("\tSubject Prefix: ");
407 	    break;
408 	  default:
409 	    printf ("\tUnknown String: ");
410 	    break;
411 	}
412 	num = read_32(tsp+bytes);
413 	bytes += sizeof(num);
414 	num = read_32(tsp+bytes);
415 	bytes += sizeof(num);
416 	printf ("%s\n", make_string(tsp+bytes,num));
417 	bytes += num + ((num % 4) ? (4 - num%4) : 0);
418 	break;
419       case PT_UNICODE:
420       case PT_OBJECT:
421 	break;
422       case PT_I2:
423 	bytes += 2;
424 	break;
425       case PT_LONG:
426 	bytes += 4;
427 	break;
428       case PT_R4:
429 	bytes += 4;
430 	break;
431       case PT_DOUBLE:
432 	bytes += 8;
433 	break;
434       case PT_CURRENCY:
435       case PT_APPTIME:
436       case PT_ERROR:
437 	bytes += 4;
438 	break;
439       case PT_BOOLEAN:
440 	bytes += 4;
441 	break;
442       case PT_I8:
443 	bytes += 8;
444       case PT_SYSTIME:
445 	bytes += 8;
446 	break;
447     }
448     x++;
449   }
450 
451 }
452 
save_attach_data(char * title,uint8 * tsp,uint32 size)453 int save_attach_data(char *title, uint8 *tsp, uint32 size)
454 {
455   FILE *out;
456   char filename[256];
457 
458   strcpy(filename, title);
459   out = fopen(filename, "w");
460   if (out == NULL) {
461     fprintf(stderr, "Error openning file %s for writing\n", filename);
462     return -1;
463   }
464   fwrite(tsp, sizeof(uint8), size, out);
465   fclose(out);
466   return 0;
467 }
468 
default_handler(uint32 attribute,uint8 * tsp,uint32 size)469 int default_handler(uint32 attribute, uint8 *tsp, uint32 size)
470 {
471   uint16 type = ATT_TYPE(attribute);
472 
473   switch (type) {
474     case atpTriples:
475       break;
476     case atpString:
477     case atpText:
478       printf("Attribute %s: ", attribute_string(attribute));
479       printf("%s\n",make_string(tsp,size));
480       break;
481     case atpDate:
482       printf("Attribute %s: ", attribute_string(attribute));
483       printf("%s\n",date_string(tsp));
484       break;
485     case atpShort:
486       break;
487     case atpLong:
488       break;
489     case atpByte:
490       break;
491     case atpWord:
492       break;
493     case atpDword:
494       break;
495     default:
496       printf("Attribute %s: ", attribute_string(attribute));
497       printf("Unknown type\n");
498       break;
499   }
500   return 0;
501 }
502 
read_attribute(uint8 * tsp)503 int read_attribute(uint8 *tsp)
504 {
505   int bytes = 0, header = 0;
506   uint32 attribute;
507   uint8 component = 0;
508   uint32 size = 0;
509   uint16 checksum = 0;
510   static char attach_title[256] = "default.dat";
511   uint8 *ptr;
512 
513   component = *tsp;
514   bytes += sizeof(uint8);
515 
516   attribute = read_32(tsp+bytes);
517   bytes += sizeof(attribute);
518   size = read_32(tsp+bytes);
519   bytes += sizeof(size);
520   header = bytes;
521   bytes += size;
522   checksum = read_16(tsp+bytes);
523   bytes += sizeof(checksum);
524 
525   if (Verbose) {
526     printf("Attribute: %s, (%08lx)\n",attribute_string(attribute),attribute);
527     if(component == LVL_MESSAGE)
528       printf("\tComponent:\tMessage\n");
529     else
530       printf("\tComponent:\tAttachment\n");
531     printf("\tType:\t\t%s\n",type_string(ATT_TYPE(attribute)));
532     printf("\tSize:\t\t%d\n",size);
533     printf("\tChecksum:\t%04x\n",checksum);
534   }
535 
536   switch (attribute) {
537     case attNull:
538       default_handler(attribute, tsp+header, size);
539       break;
540     case attFrom:
541       default_handler(attribute, tsp+header, size);
542       break;
543     case attSubject:
544       printf("Subject: %s\n",make_string(tsp+header,size));
545       break;
546     case attDateSent:
547       printf("Date Sent: %s\n",date_string(tsp+header));
548       break;
549     case attDateRecd:
550       printf("Date Recieved: %s\n",date_string(tsp+header));
551       break;
552     case attMessageStatus:
553       printf("Message Status: %s\n",status_string(*(uint8 *)(tsp+header)));
554       break;
555     case attMessageClass:
556       printf("Message Class: %s\n",make_string(tsp+header,size));
557       break;
558     case attMessageID:
559       printf("Message Id: %s\n",make_string(tsp+header,size));
560       break;
561     case attParentID:
562       printf("Parent Id: %s\n",make_string(tsp+header,size));
563       break;
564     case attConversationID:
565       printf("Conversation Id: %s\n",make_string(tsp+header,size));
566       break;
567     case attBody:
568       default_handler(attribute, tsp+header, size);
569       break;
570     case attPriority:
571       printf("Priority: %d %s\n", read_16(tsp+header),
572 	  priority_string(read_16(tsp+header)));
573       break;
574     case attAttachData:
575       if (SaveData) {
576 	if (!save_attach_data(attach_title, tsp+header,size))
577 	  printf("Attachment saved to file %s\n", attach_title);
578 	 else
579 	   printf("Failure saving attachment to file %s\n", attach_title);
580       } else {
581 	printf("TNEF Contains attached file %s\n", attach_title);
582       }
583       break;
584     case attAttachTitle:
585       strcpy(attach_title, make_string(tsp+header,size));
586       printf("Attachment Title: %s\n",attach_title);
587       break;
588     case attAttachMetaFile:
589       default_handler(attribute, tsp+header, size);
590       break;
591     case attAttachCreateDate:
592       printf("Attachment Date Created: %s\n",date_string(tsp+header));
593       break;
594     case attAttachModifyDate:
595       printf("Attachment Date Modified: %s\n",date_string(tsp+header));
596       break;
597     case attDateModified:
598       printf("Date Modified: %s\n",date_string(tsp+header));
599       break;
600     case attAttachTransportFilename:
601       default_handler(attribute, tsp+header, size);
602       break;
603     case attAttachRenddata:
604       default_handler(attribute, tsp+header, size);
605       break;
606     case attMAPIProps:
607       printf("MAPI Properties\n");
608       handle_props(tsp+header);
609       break;
610     case attRecipTable:
611       default_handler(attribute, tsp+header, size);
612       break;
613     case attAttachment:
614       default_handler(attribute, tsp+header, size);
615       break;
616     case attTnefVersion:
617       {
618 	uint32 version;
619 	version = read_32(tsp+header);
620 	printf("TNEF Version: %d.%d\n",((version & 0xFFFF0000)>>16),
621 	    (version & 0x0000FFFF));
622       }
623       break;
624     case attOemCodepage:
625       default_handler(attribute, tsp+header, size);
626       break;
627     case attOriginalMessageClass:
628       printf("Original Message Class: %s\n",make_string(tsp+header,size));
629       break;
630     case attOwner:
631       default_handler(attribute, tsp+header, size);
632       break;
633     case attSentFor:
634       default_handler(attribute, tsp+header, size);
635       break;
636     case attDelegate:
637       default_handler(attribute, tsp+header, size);
638       break;
639     case attDateStart:
640       printf("Start Date: %s\n",date_string(tsp+header));
641       break;
642     case attDateEnd:
643       printf("End Date: %s\n",date_string(tsp+header));
644       break;
645     case attAidOwner:
646       default_handler(attribute, tsp+header, size);
647       break;
648     case attRequestRes:
649       default_handler(attribute, tsp+header, size);
650       break;
651     default:
652       default_handler(attribute, tsp+header, size);
653       break;
654   }
655   return bytes;
656 }
657 
decode_tnef(uint8 * tnef_stream,int size)658 int decode_tnef(uint8 *tnef_stream, int size)
659 {
660   uint8 *tsp;
661 
662   tsp = tnef_stream;
663 
664   if (TNEF_SIGNATURE == read_32(tsp)) {
665     printf("Good TNEF Signature\n");
666   } else {
667     printf("Bad TNEF Signature, Expecting: %lx  Got: %lx\n",TNEF_SIGNATURE,
668 	read_32(tsp));
669   }
670   tsp += sizeof(TNEF_SIGNATURE);
671 
672   printf("TNEF Attach Key: %x\n",read_16(tsp));
673   tsp += sizeof(uint16);
674 
675   while ((tsp - tnef_stream) < size) {
676     tsp += read_attribute(tsp);
677   }
678   return 0;
679 }
680 
usage(char * argv0)681 void usage(char *argv0)
682 {
683   fprintf(stderr, "\n%s by Brandon Long\n", VERSION);
684   fprintf(stderr, "Translation Neutral Encapsulation Format (TNEF) decoder\n");
685   fprintf(stderr, "\nUsage: %s [-v][-d][-h][-s] <filename>\n", argv0);
686   fprintf(stderr, "\t-v: Verbose output\n");
687   fprintf(stderr, "\t-d: Dump hex contents\n");
688   fprintf(stderr, "\t-h: This information\n");
689   fprintf(stderr, "\t-s: Save any attached files in the TNEF stream to disk\n");
690   fprintf(stderr, "\n");
691 }
692 
main(int argc,char * argv[])693 int main(int argc, char *argv[])
694 {
695   FILE *fp;
696   struct stat sb;
697   int size, nread;
698   uint8 *tnef_stream;
699   int dump = 0;
700   char *filename;
701   int x;
702 
703   if (argc < 2 || argc > 4) {
704     usage(argv[0]);
705     exit(-1);
706   }
707 
708   x = 1;
709   while (x < argc && argv[x]) {
710     if (!strcmp(argv[x], "-v")) {
711       Verbose = TRUE;
712     }
713     else if (!strcmp(argv[x], "-s")) {
714       SaveData = TRUE;
715     }
716     else if (!strcmp(argv[x], "-h")) {
717       usage(argv[0]);
718       exit(0);
719     }
720     else if (!strcmp(argv[x], "-d")) {
721       dump = TRUE;
722     } else {
723       filename = argv[x];
724     }
725     x++;
726   }
727 
728   if (stat(filename,&sb) == -1) {
729     fprintf(stderr, "Error stating file %s\n", filename);
730     perror("stat");
731     usage(argv[0]);
732     exit(-1);
733   }
734 
735   size = sb.st_size;
736 
737   tnef_stream = (uint8 *)malloc(size);
738 
739   if (tnef_stream == NULL) {
740     fprintf(stderr, "Error allocating %d bytes for loading file\n", size);
741     usage(argv[0]);
742     exit(-1);
743   }
744 
745   if ((fp = fopen(filename,"r")) == NULL) {
746     fprintf(stderr, "Error openning file %s\n", filename);
747     perror("fopen");
748     usage(argv[0]);
749     exit(-1);
750   }
751 
752   nread = fread(tnef_stream, sizeof(uint8), size, fp);
753   if (nread < size) {
754     fprintf(stderr, "Error reading stream from file %s\n",filename);
755     perror("fread");
756     usage(argv[0]);
757     exit(-1);
758   }
759   fclose(fp);
760 
761   if (dump) {
762     int x;
763     for (x=0 ; x<size ; x++) {
764       if (x%32 == 0) putchar('\n');
765       if (x%4 == 0) putchar(' ');
766       printf("%02x",*(tnef_stream+x));
767     }
768     putchar('\n');
769   }
770   decode_tnef(tnef_stream,size);
771 
772   return 0;
773 }
774