2 Convert Exchange mails to mbox
6 Copyright (C) Julien Kerihuel 2007
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "libmapi/libmapi.h"
23 #include <samba/popt.h>
26 #include <sys/types.h>
40 #include "openchange-tools.h"
42 /* Ugly and lazy but working ... */
43 #define DEFAULT_BOUNDARY_BASE "DocE+STaALJfprDB"
44 #define MESSAGEID "Message-ID: "
45 #define MESSAGEID_LEN 11
48 * how much to request at a time, and it's complex :-(
49 * This was 4096 - was getting NT_STATUS_BUFFER_TOO_SMALL loading large
51 * It must be less than 16K (Windows API is a signed short)
52 * If you ask for more than windows allows you get nothing.
53 * Asking for too little just dogs the performance
55 * found the NTSTATUS error by adding debug to the ReadStream code.
57 #define MAX_READ_SIZE 12000
59 static int message_error = 0; /* did we get an error processing message */
61 static bool opt_test = false;
63 static char boundary_base[128] = DEFAULT_BOUNDARY_BASE;
65 static time_t start_time;
67 static char *boundary(int index)
69 snprintf(boundary_base, sizeof(boundary_base), "%s_%lu_%d",
70 DEFAULT_BOUNDARY_BASE, (unsigned long) start_time, index);
74 static void fix_froms(unsigned char *cp, int len)
76 unsigned char *ep = cp + len;
77 for (; cp + 6 < ep; cp++) {
80 if (strncmp((char *)cp, "\nFrom ", 6) == 0) {
88 * find a Header at the start of a line (or the start of the text)
91 static char *find_header(char *cp, char *hdr)
94 while ((ep = strcasestr(cp, hdr))) {
95 if (ep == cp || ep[-1] == '\n')
104 * delete a message on the exchange server
106 static bool delete_messages(
108 struct mapi_session *session,
112 enum MAPISTATUS retval;
113 mapi_object_t obj_store;
114 mapi_object_t obj_inbox;
115 mapi_object_t obj_table;
117 struct SPropTagArray *SPropTagArray;
118 struct SRowSet SRowSet;
121 struct mapi_profile *profile;
123 if (!del_count || !del_msgid) {
127 profile = session->profile;
129 /* Open the default message store */
130 mapi_object_init(&obj_store);
131 retval = OpenMsgStore(session, &obj_store);
132 if (retval != MAPI_E_SUCCESS) return false;
135 retval = GetReceiveFolder(&obj_store, &id_inbox, NULL);
136 if (retval != MAPI_E_SUCCESS) return false;
138 mapi_object_init(&obj_inbox);
139 retval = OpenFolder(&obj_store, id_inbox, &obj_inbox);
140 if (retval != MAPI_E_SUCCESS) return false;
142 mapi_object_init(&obj_table);
143 retval = GetContentsTable(&obj_inbox, &obj_table, 0, NULL);
144 if (retval != MAPI_E_SUCCESS) return false;
146 SPropTagArray = set_SPropTagArray(mem_ctx, 0x3,
149 PR_INTERNET_MESSAGE_ID);
150 retval = SetColumns(&obj_table, SPropTagArray);
151 MAPIFreeBuffer(SPropTagArray);
153 if (retval != MAPI_E_SUCCESS)
156 while ((retval = QueryRows(&obj_table, 0xa, TBL_ADVANCE, &SRowSet)) == MAPI_E_SUCCESS) {
160 for (i = 0; i < SRowSet.cRows; i++) {
161 const char *message_id;
163 message_id = (const char *)find_SPropValue_data(&(SRowSet.aRow[i]), PR_INTERNET_MESSAGE_ID);
168 for (j = 0; j < del_count; j++) {
169 if (strcmp(message_id, del_msgid[j]) == 0) {
170 id_message = SRowSet.aRow[i].lpProps[1].value.d;
171 retval = DeleteMessage(&obj_inbox, &id_message, 1);
172 if (retval == MAPI_E_SUCCESS) {
173 printf("%s deleted from the Exchange server\n",
176 mapi_profile_delete_string_attr(profile->mapi_ctx,
177 profile->profname, "Message-ID", del_msgid[j]);
179 printf("%s not deleted from profile %s: err=0x%x\n",
180 del_msgid[j], profile->profname, retval);
182 printf("%s NOT deleted from the Exchange server\n",
190 mapi_object_release(&obj_table);
191 mapi_object_release(&obj_inbox);
192 mapi_object_release(&obj_store);
198 * Fetch message ids from the existing mbox
200 static uint32_t update(
201 TALLOC_CTX *mem_ctx, FILE *fp,
202 struct mapi_session *session)
204 enum MAPISTATUS retval;
205 struct mapi_profile *profile;
208 #if !defined(__FreeBSD__)
215 unsigned int mbox_count = 0;
220 unsigned int del_count = 0;
222 profile = session->profile;
224 mbox_msgids = talloc_zero(mem_ctx, char *);
225 /* Add Message-ID attribute to the profile if it is missing */
226 #if defined(__FreeBSD__)
227 while ((line = fgetln(fp, &read_size)) != NULL) {
229 while ((size = getline(&line, &read_size, fp)) != -1) {
231 if (line && !strncmp(line, MESSAGEID, strlen(MESSAGEID))) {
232 msgid = strstr(line, MESSAGEID);
233 id = talloc_strdup(mem_ctx, msgid + strlen(MESSAGEID));
234 id[strlen(id) - 1] = 0;
236 mbox_msgids = talloc_realloc(mem_ctx, mbox_msgids, char *, mbox_count + 2);
237 mbox_msgids[mbox_count] = talloc_strdup(mem_ctx, id);
240 retval = FindProfileAttr(profile, "Message-ID", id);
241 if (GetLastError() == MAPI_E_NOT_FOUND) {
243 printf("[+] Adding %s to %s\n", id, profile->profname);
244 retval = mapi_profile_add_string_attr(profile->mapi_ctx, profile->profname, "Message-ID", id);
245 if (retval != MAPI_E_SUCCESS) {
246 mapi_errstr("mapi_profile_add_string_attr", GetLastError());
248 talloc_free(profname);
249 MAPIUninitialize(profile->mapi_ctx);
261 /* Remove Message-ID and update Exchange mailbox if a
262 * Message-ID is missing in mbox
264 retval = GetProfileAttr(profile, "Message-ID", &count, &prof_msgids);
265 if (retval == MAPI_E_NOT_FOUND) {
266 printf("No Synchonizing is needed at all\n");
267 return MAPI_E_SUCCESS;
269 fprintf(stderr, "GetProfileAttr failed - %x\n", retval);
273 if (count != mbox_count) {
274 printf("{+] Synchonizing mbox with Exchange mailbox\n");
276 del_msgids = talloc_zero(mem_ctx, char *);
279 for (i = 0; i < count; i++) {
281 for (j = 0; j < mbox_count; j++) {
282 if (!strcmp(prof_msgids[i], mbox_msgids[j])) {
286 if (found == false) {
287 del_msgids = talloc_realloc(mem_ctx, del_msgids, char *, del_count + 2);
288 del_msgids[del_count] = talloc_strdup(mem_ctx, prof_msgids[i]);
293 delete_messages(mem_ctx, session, del_msgids, del_count);
295 talloc_free(del_msgids);
298 printf("[+] mbox already synchronized with Exchange Mailbox\n");
301 talloc_free(prof_msgids);
302 talloc_free(mbox_msgids);
304 return MAPI_E_SUCCESS;
307 static const char *get_filename(const char *filename)
311 if (!filename) return NULL;
313 substr = rindex(filename, '/');
314 if (substr) return substr;
320 static char *get_base64_attachment(TALLOC_CTX *mem_ctx, mapi_object_t *obj_attach, const uint32_t size, char **magic)
322 enum MAPISTATUS retval;
325 mapi_object_t obj_stream;
326 uint32_t stream_size;
329 magic_t cookie = NULL;
331 mapi_object_init(&obj_stream);
332 retval = OpenStream(obj_attach, PR_ATTACH_DATA_BIN, 0, &obj_stream);
333 if (retval != MAPI_E_SUCCESS) {
334 fprintf(stderr, "OpenStream failed %x\n", retval);
339 data.data = talloc_zero_size(mem_ctx, size);
341 for (stream_size = 0; stream_size < size; ) {
343 * exchange can only handle about 4K chunks at a time, and don't
344 * ask for more or you get none
346 retval = ReadStream(&obj_stream, data.data + stream_size,
347 (stream_size + MAX_READ_SIZE < size) ? MAX_READ_SIZE :
348 (size - stream_size), &read_size);
349 if ((retval != MAPI_E_SUCCESS) || read_size == 0)
351 stream_size += read_size;
353 if (retval != MAPI_E_SUCCESS) {
354 fprintf(stderr, "ReadStream failed retval=%x read_size=%d "
355 "stream_size=%d size=%d\n",
356 retval, read_size, stream_size, size);
357 talloc_free(data.data);
358 mapi_object_release(&obj_stream);
362 data.length = stream_size;
365 /* if they want a mime magic string try and autodetect one */
366 cookie = magic_open(MAGIC_MIME);
367 if (cookie == NULL) {
368 fprintf(stderr, "%s,%d - NULL\n", __FILE__, __LINE__);
369 printf("%s\n", magic_error(cookie));
370 talloc_free(data.data);
371 mapi_object_release(&obj_stream);
374 if (magic_load(cookie, NULL) == -1) {
375 fprintf(stderr, "%s,%d - NULL\n", __FILE__, __LINE__);
376 printf("%s\n", magic_error(cookie));
377 talloc_free(data.data);
378 mapi_object_release(&obj_stream);
381 tmp = magic_buffer(cookie, (void *)data.data, data.length);
382 *magic = talloc_strdup(mem_ctx, tmp);
386 /* convert attachment to base64 */
387 ret = ldb_base64_encode(mem_ctx, (const char *)data.data, data.length);
389 talloc_free(data.data);
390 mapi_object_release(&obj_stream);
396 #define WRAP_LINES_AT 76
398 static void write_base64_data(FILE *fp, const char *buf)
400 int len = strlen(buf);
404 int chunk = len > WRAP_LINES_AT ? WRAP_LINES_AT : len;
406 n = fwrite(buf, len > WRAP_LINES_AT ? WRAP_LINES_AT : len, 1, fp);
408 fprintf(stderr, "Error writing %d bytes of base64 attachment: %d\n",
412 fwrite("\n", 1, 1, fp);
418 * Read a stream and store it in a DATA_BLOB
420 static enum MAPISTATUS get_stream(TALLOC_CTX *mem_ctx,
421 mapi_object_t *obj_stream,
424 enum MAPISTATUS retval;
426 uint8_t buf[MAX_READ_SIZE];
429 body->data = talloc_zero(mem_ctx, uint8_t);
432 retval = ReadStream(obj_stream, buf, MAX_READ_SIZE, &read_size);
433 MAPI_RETVAL_IF(retval, GetLastError(), body->data);
435 body->data = talloc_realloc(mem_ctx, body->data, uint8_t,
436 body->length + read_size);
437 memcpy(&(body->data[body->length]), buf, read_size);
438 body->length += read_size;
443 return MAPI_E_SUCCESS;
451 /* Fetch the body, no fancy junk */
452 static enum MAPISTATUS get_body(TALLOC_CTX *mem_ctx,
453 mapi_object_t *obj_message,
455 body_stuff_t body[3],
458 mapi_object_t obj_stream;
459 enum MAPISTATUS retval;
461 const struct SBinary_short *bin;
464 memset(body, 0, sizeof(body));
466 data = octool_get_propval(aRow, PR_BODY);
467 if (data && strlen(data)) {
468 body[*body_count].body.data = talloc_memdup(mem_ctx, data, strlen(data));
469 body[*body_count].body.length = strlen(data);
470 body[*body_count].body_header = "Content-Type: text/plain; charset=us-ascii\n";
474 bin = (const struct SBinary_short *) octool_get_propval(aRow, PR_HTML);
475 if (bin && bin->cb) {
476 body[*body_count].body.data = talloc_memdup(mem_ctx, bin->lpb, bin->cb);
477 body[*body_count].body.length = bin->cb;
478 body[*body_count].body_header = "Content-Type: text/html\n";
483 bin = (const struct SBinary_short *) octool_get_propval(aRow, PR_RTF_COMPRESSED);
484 if (bin && bin->cb) {
485 body[*body_count].body.data = talloc_memdup(mem_ctx, bin->lpb, bin->cb);
486 body[*body_count].body.length = bin->cb;
487 body[*body_count].body_header = "Content-Type: text/rtf\n";
492 if (*body_count <= 0) {
493 printf("No HTML or TEXT, generating TEXT body\n");
494 /* generate a body for us ? */
495 mapi_object_init(&obj_stream);
496 retval = OpenStream(obj_message, PR_BODY, 0, &obj_stream);
498 fprintf(stderr, "Failed to get a message body, making empty one: %x\n", retval);
500 body[*body_count].body.data = talloc_zero(mem_ctx, uint8_t);
501 body[*body_count].body.length = 0;
502 body[*body_count].body_header = "Content-Type: text/plain; charset=us-ascii\n";
504 body[*body_count].body.length = 0;
506 retval = get_stream(mem_ctx, &obj_stream, &body[*body_count].body);
508 body[*body_count].body.length = 0;
509 fprintf(stderr, "HTML ERROR1 %x\n", retval);
512 mapi_object_release(&obj_stream);
513 if (body[*body_count].body.length) {
514 body[*body_count].body_header = "Content-Type: text/plain; charset=us-ascii\n";
519 return MAPI_E_SUCCESS;
525 From Administrator Mon Apr 23 14:43:01 2007
526 Date: Mon Apr 23 14:43:01 2007
529 Subject: This is the subject
531 This is a sample mail
535 static bool message2mbox(TALLOC_CTX *mem_ctx, FILE *fp,
536 struct SRow *aRow, mapi_object_t *obj_message,
539 enum MAPISTATUS retval;
540 mapi_object_t obj_tb_attach;
541 mapi_object_t obj_attach;
542 const uint64_t *delivery_date;
543 const char *date = NULL;
544 const char *to = NULL;
545 const char *cc = NULL;
546 const char *bcc = NULL;
547 const char *from = NULL;
548 const char *subject = NULL;
550 const char *msgheaders = NULL;
551 const char *attach_filename;
552 const uint32_t *attach_size;
553 char *attachment_data;
554 const uint8_t *has_attach = NULL;
555 const uint32_t *attach_num = NULL;
558 struct SPropTagArray *SPropTagArray = NULL;
559 struct SPropValue *lpProps;
561 struct SRowSet rowset_attach;
565 body_stuff_t body[3];
568 has_attach = (const uint8_t *) octool_get_propval(aRow, PR_HASATTACH);
569 to = (const char *) octool_get_propval(aRow, PR_DISPLAY_TO);
570 cc = (const char *) octool_get_propval(aRow, PR_DISPLAY_CC);
571 bcc = (const char *) octool_get_propval(aRow, PR_DISPLAY_BCC);
573 delivery_date = (const uint64_t *)octool_get_propval(aRow, PR_MESSAGE_DELIVERY_TIME);
575 date = nt_time_string(mem_ctx, *delivery_date);
580 from = (const char *) octool_get_propval(aRow, PR_SENT_REPRESENTING_NAME);
585 subject = (const char*) octool_get_propval(aRow, PR_SUBJECT);
586 msgid = (const char *) octool_get_propval(aRow, PR_INTERNET_MESSAGE_ID);
588 msgheaders = (const char *) octool_get_propval(aRow, PR_TRANSPORT_MESSAGE_HEADERS);
590 retval = get_body(mem_ctx, obj_message, aRow, body, &body_count);
592 /* First line From - but only if base_level == 0 */
593 if (base_level == 0) {
595 f = talloc_strdup(mem_ctx, from);
596 /* strip out all '"'s, ugly but works */
597 for (p = f; p && *p; ) {
599 memmove(p, p+1, strlen(p)); /* gets NUL */
604 fprintf(fp, "From \"%s\" %s\n", f, date);
609 char *mhalloc, *mhclean, *mhend, *mhp, *mht;
611 mhalloc = talloc_strdup(mem_ctx, msgheaders);
615 /* Skip past the comment Exchange adds to the beginning */
616 mhclean = strchr(mhclean, '\n');
620 } while (isspace(*mhclean));
622 /* Trim off the empty mime parts that Exchange leaves after
623 the end of the headers */
624 mhend = strstr(mhclean, "\n------=");
631 while ((mhend = strchr(mhclean, '\r')))
632 memmove(mhend, mhend+1, strlen(mhend) /* gets NUL */);
635 * strip Content-* headers (we make our own), be sure to get
636 * and extended (indented parts) of this header
640 * strip some bad-for-us headers (poor mans lookup table below :-)
642 mhend = find_header(mhclean, "Content-");
644 mhend = find_header(mhclean, "X-MS-");
646 mhend = find_header(mhclean, "Lines:");
652 while ((mhp = strchr(mht, '\n'))) {
654 if (!*mhp || !isspace(*mhp))
660 /* match was in the middle of other headers */
661 memmove(mhend, mhp, strlen(mhp) + 1);
663 /* match was at the end of the headers, truncate it */
668 /* remove any NL's at end of headers */
669 mhp = mhclean + strlen(mhclean);
670 while (mhp > mhclean && mhp[-1] == '\n')
673 line = talloc_asprintf(mem_ctx, "%s\n", mhclean);
674 if (line) fwrite(line, strlen(line), 1, fp);
676 talloc_free(mhalloc);
681 /* Second line: Date */
682 line = talloc_asprintf(mem_ctx, "Date: %s\n", date);
684 fwrite(line, strlen(line), 1, fp);
688 /* Third line From */
689 line = talloc_asprintf(mem_ctx, "From: %s\n", from);
691 fwrite(line, strlen(line), 1, fp);
697 line = talloc_asprintf(mem_ctx, "To: %s\n", to);
699 fwrite(line, strlen(line), 1, fp);
705 line = talloc_asprintf(mem_ctx, "Cc: %s\n", cc);
707 fwrite(line, strlen(line), 1, fp);
713 line = talloc_asprintf(mem_ctx, "Bcc: %s\n", bcc);
715 fwrite(line, strlen(line), 1, fp);
722 line = talloc_asprintf(mem_ctx, "Subject: %s\n", subject);
724 fwrite(line, strlen(line), 1, fp);
730 line = talloc_asprintf(mem_ctx, "Message-ID: %s\n", msgid);
732 fwrite(line, strlen(line), 1, fp);
738 /* Set multi-type if we have attachment mixed for all things/alternative
740 if (has_attach && *has_attach) {
741 fprintf(fp, "Content-Type: multipart/mixed; boundary=\"%s\"\n",
742 boundary(base_level+0));
743 } else if (body_count > 1) {
744 fprintf(fp, "Content-Type: multipart/alternative; boundary=\"%s\"\n",
745 boundary(base_level+0));
749 if (body_count > 0) {
750 if ((has_attach && *has_attach) || body_count > 1) {
751 /* blank line before content */
753 fwrite("\n", 1, 1, fp);
756 fprintf(fp, "--%s\n", boundary(base_level+0));
760 * if we have more than 1 body we need to do a multipart alternative
761 * within the first attachment
763 if ((has_attach && *has_attach) && body_count > 1) {
764 fprintf(fp, "Content-Type: multipart/alternative; boundary=\"%s\"\n", boundary(base_level+1));
765 fwrite("\n", 1, 1, fp);
766 fprintf(fp, "\n\n--%s\n", boundary(base_level+1));
770 * output content type
772 fwrite(body[0].body_header, strlen(body[0].body_header), 1, fp);
773 fprintf(fp, "Content-Disposition: inline\n");
775 /* blank after header */
776 fwrite("\n", 1, 1, fp);
779 fix_froms(body[0].body.data, body[0].body.length);
780 fwrite(body[0].body.data, body[0].body.length, 1, fp);
781 talloc_free(body[0].body.data);
784 /* blank line before content */
786 fwrite("\n", 1, 1, fp);
790 /* do the other bodies before attachments */
791 for (i = 1; i < body_count && i < 3; i++) {
792 if (has_attach && *has_attach) {
793 fprintf(fp, "\n\n--%s\n", boundary(base_level+1));
795 fprintf(fp, "\n\n--%s\n", boundary(base_level+0));
797 fwrite(body[i].body_header, strlen(body[i].body_header), 1, fp);
798 fprintf(fp, "Content-Disposition: inline\n");
799 fwrite("\n", 1, 1, fp);
800 fix_froms(body[i].body.data, body[i].body.length);
801 fwrite(body[i].body.data, body[i].body.length, 1, fp);
804 if (has_attach && *has_attach) {
805 /* close body attachments */
806 if (body_count > 1) {
807 fprintf(fp, "\n\n--%s--\n", boundary(base_level+1));
810 mapi_object_init(&obj_tb_attach);
811 retval = GetAttachmentTable(obj_message, &obj_tb_attach);
812 if (retval == MAPI_E_SUCCESS) {
813 SPropTagArray = set_SPropTagArray(mem_ctx, 0x1, PR_ATTACH_NUM);
814 retval = SetColumns(&obj_tb_attach, SPropTagArray);
815 MAPIFreeBuffer(SPropTagArray);
816 MAPI_RETVAL_IF(retval, retval, NULL);
818 retval = QueryRows(&obj_tb_attach, 0xa, TBL_ADVANCE, &rowset_attach);
819 MAPI_RETVAL_IF(retval, retval, NULL);
821 for (i = 0; i < rowset_attach.cRows; i++) {
822 //attach_num = (const uint32_t *)find_SPropValue_data(&(rowset_attach.aRow[i]), PR_ATTACH_NUM);
823 uint32_t n = rowset_attach.aRow[i].lpProps[0].value.l;
825 retval = OpenAttach(obj_message, *attach_num, &obj_attach);
826 if (retval == MAPI_E_SUCCESS) {
827 SPropTagArray = set_SPropTagArray(mem_ctx, 0x5,
829 PR_ATTACH_LONG_FILENAME,
833 lpProps = talloc_zero(mem_ctx, struct SPropValue);
834 retval = GetProps(&obj_attach, MAPI_UNICODE, SPropTagArray, &lpProps, &count);
835 MAPIFreeBuffer(SPropTagArray);
836 if (retval == MAPI_E_SUCCESS) {
837 uint32_t *mp, method = -1;
839 aRow2.ulAdrEntryPad = 0;
840 aRow2.cValues = count;
841 aRow2.lpProps = lpProps;
843 mp = (uint32_t *) octool_get_propval(&aRow2, PR_ATTACH_METHOD);
847 attach_filename = get_filename(octool_get_propval(&aRow2, PR_ATTACH_LONG_FILENAME));
848 if (!attach_filename || (attach_filename && !strcmp(attach_filename, ""))) {
849 attach_filename = get_filename(octool_get_propval(&aRow2, PR_ATTACH_FILENAME));
851 attach_size = (const uint32_t *) octool_get_propval(&aRow2, PR_ATTACH_SIZE);
853 attachment_data = NULL;
855 case ATTACH_BY_VALUE:
856 magic = (char *) octool_get_propval(&aRow2, PR_ATTACH_MIME_TAG);
858 magic = talloc_strdup(mem_ctx, magic);
859 attachment_data = get_base64_attachment(mem_ctx,
860 &obj_attach, *attach_size,
861 magic ? NULL : &magic);
862 if (attachment_data == NULL) {
864 fprintf(stderr, "Failed to read attachment for message %s\n", msgid ? msgid : "unknown");
867 fprintf(fp, "\n\n--%s\n", boundary(base_level+0));
868 fprintf(fp, "Content-Disposition: attachment; filename=\"%s\"\n", attach_filename);
869 fprintf(fp, "Content-Type: %s\n", magic);
870 fprintf(fp, "Content-Transfer-Encoding: base64\n\n");
871 write_base64_data(fp, attachment_data);
872 talloc_free(attachment_data);
875 case ATTACH_BY_REFERENCE:
876 fprintf(stderr,"ATTACH_BY_REFERENCE unsupported\n");
879 case ATTACH_BY_REF_RESOLVE:
880 fprintf(stderr,"ATTACH_BY_REF_RESOLVE unsupported\n");
883 case ATTACH_BY_REF_ONLY:
884 fprintf(stderr,"ATTACH_BY_REF_ONLY unsupported\n");
887 case ATTACH_EMBEDDED_MSG: {
888 mapi_object_t obj_embeddedmsg;
889 struct SPropTagArray *embTagArray = NULL;
890 struct SPropValue *embProps;
892 uint32_t emb_count = 0;
894 mapi_object_init(&obj_embeddedmsg);
895 retval = OpenEmbeddedMessage(&obj_attach,
896 &obj_embeddedmsg, MAPI_READONLY);
897 if (retval != MAPI_E_SUCCESS) {
898 fprintf(stderr, "Failed to open Embedded msg: %x\n", retval);
903 embTagArray = set_SPropTagArray(mem_ctx, 0x15,
904 PR_INTERNET_MESSAGE_ID,
905 PR_INTERNET_MESSAGE_ID_UNICODE,
906 PR_CONVERSATION_TOPIC,
907 PR_CONVERSATION_TOPIC_UNICODE,
908 PR_MESSAGE_DELIVERY_TIME,
909 PR_MSG_EDITOR_FORMAT,
915 PR_SENT_REPRESENTING_NAME,
916 PR_SENT_REPRESENTING_NAME_UNICODE,
918 PR_DISPLAY_TO_UNICODE,
920 PR_DISPLAY_CC_UNICODE,
922 PR_DISPLAY_BCC_UNICODE,
924 PR_TRANSPORT_MESSAGE_HEADERS);
925 retval = GetProps(&obj_embeddedmsg, MAPI_UNICODE, embTagArray,
926 &embProps, &emb_count);
927 MAPIFreeBuffer(embTagArray);
929 if (retval != MAPI_E_SUCCESS) {
930 fprintf(stderr, "Failed to get Embedded msg props: %x\n", retval);
935 /* Build a SRow structure */
936 eRow.ulAdrEntryPad = 0;
937 eRow.cValues = emb_count;
938 eRow.lpProps = embProps;
940 fprintf(fp, "\n\n--%s\n", boundary(base_level+0));
941 fprintf(fp, "Content-Type: message/rfc822\n");
942 fprintf(fp, "Content-Disposition: inline\n");
945 message2mbox(mem_ctx, fp, &eRow, &obj_embeddedmsg,
946 base_level + 2 /* 0 = main, 1 = alt */);
947 talloc_free(embProps);
950 fprintf(stderr,"ATTACH_OLE unsupported - "
951 "allowing message through anyway\n");
952 // message_error = 1;
955 fprintf(stderr, "Unsupported attach method = %d\n", method);
960 MAPIFreeBuffer(lpProps);
964 line = talloc_asprintf(mem_ctx, "\n\n--%s--\n\n\n", boundary(base_level+0));
966 fwrite(line, strlen(line), 1, fp);
970 } else if (body_count > 1) {
971 /* close body attachments */
972 fprintf(fp, "\n\n--%s--\n", boundary(base_level+0));
975 fwrite("\n\n\n", 3, 1, fp);
982 int main(int argc, const char *argv[])
984 TALLOC_CTX *mem_ctx = NULL;
985 enum MAPISTATUS retval;
986 struct mapi_context *mapi_ctx = NULL;
987 struct mapi_session *session = NULL;
988 struct mapi_profile *profile = NULL;
989 mapi_object_t obj_store;
990 mapi_object_t obj_inbox;
991 mapi_object_t obj_table;
992 mapi_object_t obj_message;
995 struct SPropTagArray *SPropTagArray = NULL;
996 struct SPropValue *lpProps;
998 struct SRowSet rowset;
1003 const char *opt_profdb = NULL;
1004 char *opt_profname = NULL;
1005 const char *opt_password = NULL;
1006 const char *opt_mbox = NULL;
1007 bool opt_update = false;
1008 bool opt_dumpdata = false;
1009 const char *opt_debug = NULL;
1012 enum {OPT_PROFILE_DB=1000, OPT_PROFILE, OPT_PASSWORD, OPT_MBOX, OPT_UPDATE,
1013 OPT_DEBUG, OPT_DUMPDATA, OPT_TEST};
1015 struct poptOption long_options[] = {
1017 {"test", 't', POPT_ARG_NONE, 0, OPT_TEST, "Do not update server, just download messages to mbox", NULL},
1018 {"database", 'f', POPT_ARG_STRING, NULL, OPT_PROFILE_DB, "set the profile database path", "PATH"},
1019 {"profile", 'p', POPT_ARG_STRING, NULL, OPT_PROFILE, "set the profile name", "PROFILE"},
1020 {"password", 'P', POPT_ARG_STRING, NULL, OPT_PASSWORD, "set the profile password", "PASSWORD"},
1021 {"mbox", 'm', POPT_ARG_STRING, NULL, OPT_MBOX, "set the mbox file", "FILENAME"},
1022 {"update", 'u', POPT_ARG_NONE, 0, OPT_UPDATE, "mirror mbox changes back to the Exchange server", NULL},
1023 {"debuglevel", 'd', POPT_ARG_STRING, NULL, OPT_DEBUG, "set the debug level", "LEVEL"},
1024 {"dump-data", 0, POPT_ARG_NONE, NULL, OPT_DUMPDATA, "dump the hex data", NULL},
1025 POPT_OPENCHANGE_VERSION
1026 { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL }
1029 start_time = time(0);
1031 mem_ctx = talloc_named(NULL, 0, "exchange2mbox");
1033 pc = poptGetContext("exchange2mbox", argc, argv, long_options, 0);
1035 while ((opt = poptGetNextOpt(pc)) != -1) {
1037 case OPT_PROFILE_DB:
1038 opt_profdb = poptGetOptArg(pc);
1041 opt_profname = talloc_strdup(mem_ctx, (char *)poptGetOptArg(pc));
1044 opt_password = poptGetOptArg(pc);
1047 opt_mbox = poptGetOptArg(pc);
1056 opt_debug = poptGetOptArg(pc);
1059 opt_dumpdata = true;
1069 opt_profdb = talloc_asprintf(mem_ctx, DEFAULT_PROFDB, getenv("HOME"));
1073 opt_mbox = talloc_asprintf(mem_ctx, DEFAULT_MBOX, getenv("HOME"));
1080 if ((fp = fopen(opt_mbox, "a+")) == NULL) {
1086 * Initialize MAPI subsystem
1089 retval = MAPIInitialize(&mapi_ctx, opt_profdb);
1090 if (retval != MAPI_E_SUCCESS) {
1091 mapi_errstr("MAPIInitialize", GetLastError());
1096 SetMAPIDumpData(mapi_ctx, opt_dumpdata);
1099 SetMAPIDebugLevel(mapi_ctx, atoi(opt_debug));
1102 /* if no profile is supplied use the default one */
1103 if (!opt_profname) {
1104 retval = GetDefaultProfile(mapi_ctx, &opt_profname);
1105 if (retval != MAPI_E_SUCCESS) {
1106 printf("No profile specified and no default profile found\n");
1111 retval = MapiLogonEx(mapi_ctx, &session, opt_profname, opt_password);
1112 talloc_free(opt_profname);
1113 if (retval != MAPI_E_SUCCESS) {
1114 mapi_errstr("MapiLogonEx", GetLastError());
1117 profile = session->profile;
1119 /* not sure about this, but it works and it's nice to have it there */
1120 profile->mapi_ctx = mapi_ctx;
1122 /* do the updates now */
1123 if (opt_update == true) {
1124 retval = update(mem_ctx, fp, session);
1125 if (retval != MAPI_E_SUCCESS) {
1126 printf("Problem encountered during update: %d\n", retval);
1131 /* Open the default message store */
1132 mapi_object_init(&obj_store);
1133 retval = OpenMsgStore(session, &obj_store);
1134 if (retval != MAPI_E_SUCCESS) {
1135 mapi_errstr("OpenMsgStore", GetLastError());
1140 retval = GetReceiveFolder(&obj_store, &id_inbox, NULL);
1141 MAPI_RETVAL_IF(retval, retval, mem_ctx);
1143 mapi_object_init(&obj_inbox);
1144 retval = OpenFolder(&obj_store, id_inbox, &obj_inbox);
1145 MAPI_RETVAL_IF(retval, retval, mem_ctx);
1147 mapi_object_init(&obj_table);
1148 retval = GetContentsTable(&obj_inbox, &obj_table, 0, &count);
1149 MAPI_RETVAL_IF(retval, retval, mem_ctx);
1151 SPropTagArray = set_SPropTagArray(mem_ctx, 0x5,
1156 PR_INTERNET_MESSAGE_ID);
1157 retval = SetColumns(&obj_table, SPropTagArray);
1158 MAPIFreeBuffer(SPropTagArray);
1159 MAPI_RETVAL_IF(retval, retval, mem_ctx);
1161 while ((retval = QueryRows(&obj_table, 0xa, TBL_ADVANCE, &rowset)) != MAPI_E_NOT_FOUND && rowset.cRows) {
1162 for (i = 0; i < rowset.cRows; i++) {
1163 mapi_object_init(&obj_message);
1164 retval = OpenMessage(&obj_store,
1165 rowset.aRow[i].lpProps[0].value.d,
1166 rowset.aRow[i].lpProps[1].value.d,
1168 if (retval == MAPI_E_SUCCESS) {
1169 SPropTagArray = set_SPropTagArray(mem_ctx, 0x1b,
1170 PR_INTERNET_MESSAGE_ID,
1171 PR_INTERNET_MESSAGE_ID_UNICODE,
1172 PR_CONVERSATION_TOPIC,
1173 PR_CONVERSATION_TOPIC_UNICODE,
1174 PR_MESSAGE_DELIVERY_TIME,
1175 PR_MSG_EDITOR_FORMAT,
1181 PR_SENT_REPRESENTING_NAME,
1182 PR_SENT_REPRESENTING_NAME_UNICODE,
1184 PR_DISPLAY_TO_UNICODE,
1186 PR_DISPLAY_CC_UNICODE,
1188 PR_DISPLAY_BCC_UNICODE,
1190 PR_TRANSPORT_MESSAGE_HEADERS,
1192 PR_SUBJECT_PREFIX_UNICODE,
1193 PR_NORMALIZED_SUBJECT,
1194 PR_NORMALIZED_SUBJECT_UNICODE,
1196 PR_SUBJECT_UNICODE);
1197 retval = GetProps(&obj_message, MAPI_UNICODE, SPropTagArray, &lpProps, &count);
1198 MAPIFreeBuffer(SPropTagArray);
1199 if (retval != MAPI_E_SUCCESS) {
1200 fprintf(stderr, "Badness getting row %d attrs\n", i);
1204 /* Build a SRow structure */
1205 aRow.ulAdrEntryPad = 0;
1206 aRow.cValues = count;
1207 aRow.lpProps = lpProps;
1209 msgid = (const char *) octool_get_propval(&aRow, PR_INTERNET_MESSAGE_ID);
1211 retval = FindProfileAttr(profile, "Message-ID", msgid);
1212 if (GetLastError() == MAPI_E_NOT_FOUND) {
1216 ok = message2mbox(mem_ctx, fp, &aRow, &obj_message, 0);
1218 printf("Message-ID: %s error, not added to %s\n", msgid, profile->profname);
1219 } else if (message_error) {
1220 printf("Message-ID: %s error, ignoring\n", msgid);
1221 fprintf(stderr, "Message-ID: %s error, ignoring message (check with OWA if you can, will retry next time)\n", msgid);
1222 } else if (opt_test) {
1223 printf("Message-ID: %s saved but not updated in %s\n", msgid, profile->profname);
1225 (mapi_profile_add_string_attr(profile->mapi_ctx, profile->profname, "Message-ID", msgid) != MAPI_E_SUCCESS) {
1226 mapi_errstr("mapi_profile_add_string_attr", GetLastError());
1228 printf("Message-ID: %s added to profile %s\n", msgid, profile->profname);
1231 printf("Message-ID: %s already in profile %s\n", msgid, profile->profname);
1234 fprintf(stderr, "%s: message with no msgid cannot be downloaded\n", profile->profname);
1236 talloc_free(lpProps);
1238 fprintf(stderr, "could not open row %d: retval=%d GetLastError=%d\n", i, retval, GetLastError());
1240 mapi_object_release(&obj_message);
1246 mapi_object_release(&obj_table);
1247 mapi_object_release(&obj_inbox);
1248 mapi_object_release(&obj_store);
1249 MAPIUninitialize(mapi_ctx);
1251 talloc_free(mem_ctx);