Fix libmapiadmin and exchange2mbox compilation warnings
[jelmer/openchange-proposed.git/.git] / utils / exchange2mbox.c
1 /*
2    Convert Exchange mails to mbox
3
4    OpenChange Project
5
6    Copyright (C) Julien Kerihuel 2007
7
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.
12    
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.
17    
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/>.
20 */
21
22 #include "libmapi/libmapi.h"
23 #include <samba/popt.h>
24 #include <ldb.h>
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <time.h>
33
34 #include <param.h>
35 #include <magic.h>
36
37 #include <string.h>
38 #include <ctype.h>
39
40 #include "openchange-tools.h"
41
42 /* Ugly and lazy but working ... */
43 #define DEFAULT_BOUNDARY_BASE   "DocE+STaALJfprDB"
44 #define MESSAGEID       "Message-ID: "
45 #define MESSAGEID_LEN   11
46
47 /*
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
50  *                 attachments
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
54  *
55  * found the NTSTATUS error by adding debug to the ReadStream code.
56  */
57 #define MAX_READ_SIZE   12000
58
59 static int message_error = 0;   /* did we get an error processing message */
60
61 static bool opt_test = false;
62
63 static char boundary_base[128] = DEFAULT_BOUNDARY_BASE;
64
65 static time_t start_time;
66
67 static char *boundary(int index)
68 {
69         snprintf(boundary_base, sizeof(boundary_base), "%s_%lu_%d",
70                         DEFAULT_BOUNDARY_BASE, (unsigned long) start_time, index);
71         return boundary_base;
72 }
73
74 static void fix_froms(unsigned char *cp, int len)
75 {
76         unsigned char *ep = cp + len;
77         for (; cp + 6 < ep; cp++) {
78                 if (*cp != '\n')
79                         continue;
80                 if (strncmp((char *)cp, "\nFrom ", 6) == 0) {
81                         cp[1] = 'f';
82                         cp += 5;
83                 }
84         }
85 }
86
87 /*
88  * find a Header at the start of a line (or the start of the text)
89  */
90
91 static char *find_header(char *cp, char *hdr)
92 {
93         char *ep;
94         while ((ep = strcasestr(cp, hdr))) {
95                 if (ep == cp || ep[-1] == '\n')
96                         return ep;
97                 cp = ep + 1;
98         }
99         return NULL;
100 }
101
102
103 /**
104  * delete a message on the exchange server
105  */
106 static bool delete_messages(
107         TALLOC_CTX *mem_ctx,
108         struct mapi_session     *session,
109         char **del_msgid, 
110         int del_count)
111 {
112         enum MAPISTATUS         retval;
113         mapi_object_t           obj_store;
114         mapi_object_t           obj_inbox;
115         mapi_object_t           obj_table;
116         mapi_id_t               id_inbox;
117         struct SPropTagArray    *SPropTagArray;
118         struct SRowSet          SRowSet;
119         uint32_t                i, j;
120         uint64_t                id_message;
121         struct mapi_profile     *profile;
122
123         if (!del_count || !del_msgid) {
124                 return false;
125         }
126
127         profile = session->profile;
128
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;
133
134         /* Open Inbox */
135         retval = GetReceiveFolder(&obj_store, &id_inbox, NULL);
136         if (retval != MAPI_E_SUCCESS) return false;
137
138         mapi_object_init(&obj_inbox);
139         retval = OpenFolder(&obj_store, id_inbox, &obj_inbox);
140         if (retval != MAPI_E_SUCCESS) return false;
141
142         mapi_object_init(&obj_table);
143         retval = GetContentsTable(&obj_inbox, &obj_table, 0, NULL);
144         if (retval != MAPI_E_SUCCESS) return false;
145
146         SPropTagArray = set_SPropTagArray(mem_ctx, 0x3,
147                                           PR_FID,
148                                           PR_MID,
149                                           PR_INTERNET_MESSAGE_ID);
150         retval = SetColumns(&obj_table, SPropTagArray);
151         MAPIFreeBuffer(SPropTagArray);
152
153         if (retval != MAPI_E_SUCCESS)
154                 return false;
155
156         while ((retval = QueryRows(&obj_table, 0xa, TBL_ADVANCE, &SRowSet)) == MAPI_E_SUCCESS) {
157                 if (!SRowSet.cRows)
158                         break;
159
160                 for (i = 0; i < SRowSet.cRows; i++) {
161                         const char      *message_id;
162
163                         message_id = (const char *)find_SPropValue_data(&(SRowSet.aRow[i]), PR_INTERNET_MESSAGE_ID);
164
165                         if (!message_id)
166                                 continue;
167
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",
174                                                                 del_msgid[j]);
175                                                 retval =
176                                                 mapi_profile_delete_string_attr(profile->mapi_ctx,
177                                                                 profile->profname, "Message-ID", del_msgid[j]);
178                                                 if (retval)
179                                                         printf("%s not deleted from profile %s: err=0x%x\n",
180                                                                         del_msgid[j], profile->profname, retval);
181                                         } else {
182                                                 printf("%s NOT deleted from the Exchange server\n",
183                                                                 del_msgid[j]);
184                                         }
185                                 }
186                         }
187                 }
188         }
189         
190         mapi_object_release(&obj_table);
191         mapi_object_release(&obj_inbox);
192         mapi_object_release(&obj_store);
193
194         return true;
195 }
196
197 /**
198  * Fetch message ids from the existing mbox
199  */
200 static uint32_t update(
201         TALLOC_CTX *mem_ctx, FILE *fp, 
202         struct mapi_session     *session)
203 {
204         enum MAPISTATUS         retval;
205         struct mapi_profile     *profile;
206         size_t                  read_size;
207         char                    *line = NULL;
208 #if !defined(__FreeBSD__)
209         ssize_t                 size;
210 #endif
211         const char              *msgid;
212         char                    *id;
213         char                    **mbox_msgids;
214         char                    **prof_msgids;
215         unsigned int            mbox_count = 0;
216         unsigned int            count;
217         unsigned int            i, j;
218         bool                    found = false;
219         char                    **del_msgids;
220         unsigned int            del_count = 0;
221
222         profile = session->profile;
223
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) {
228 #else
229         while ((size = getline(&line, &read_size, fp)) != -1) {
230 #endif
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;
235
236                         mbox_msgids = talloc_realloc(mem_ctx, mbox_msgids, char *, mbox_count + 2);
237                         mbox_msgids[mbox_count] = talloc_strdup(mem_ctx, id);
238                         mbox_count++;
239
240                         retval = FindProfileAttr(profile, "Message-ID", id);
241                         if (GetLastError() == MAPI_E_NOT_FOUND) {
242                                 errno = 0;
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());
247 #if 0
248                                         talloc_free(profname);
249                                         MAPIUninitialize(profile->mapi_ctx);
250 #endif
251                                         return -1;
252                                 }
253                         }
254                         talloc_free(id);
255                 }
256                 
257         }
258         if (line)
259                 free(line);
260
261         /* Remove Message-ID and update Exchange mailbox if a
262          * Message-ID is missing in mbox 
263          */
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;
268         } else if (retval) {
269                 fprintf(stderr, "GetProfileAttr failed - %x\n", retval);
270                 return retval;
271         }
272
273         if (count != mbox_count) {
274                 printf("{+] Synchonizing mbox with Exchange mailbox\n");
275
276                 del_msgids = talloc_zero(mem_ctx, char *);
277                 del_count = 0;
278
279                 for (i = 0; i < count; i++) {
280                         found = false;
281                         for (j = 0; j < mbox_count; j++) {
282                                 if (!strcmp(prof_msgids[i], mbox_msgids[j])) {
283                                         found = true;
284                                 }
285                         }
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]);
289                                 del_count++;
290                         }
291                 }
292
293                 delete_messages(mem_ctx, session, del_msgids, del_count);
294
295                 talloc_free(del_msgids);
296                 del_count = 0;
297         } else {
298                 printf("[+] mbox already synchronized with Exchange Mailbox\n");
299         }
300
301         talloc_free(prof_msgids);
302         talloc_free(mbox_msgids);
303
304         return MAPI_E_SUCCESS;
305 }
306
307 static const char *get_filename(const char *filename)
308 {
309         const char *substr;
310
311         if (!filename) return NULL;
312
313         substr = rindex(filename, '/');
314         if (substr) return substr;
315
316         return filename;
317 }
318
319
320 static char *get_base64_attachment(TALLOC_CTX *mem_ctx, mapi_object_t *obj_attach, const uint32_t size, char **magic)
321 {
322         enum MAPISTATUS retval;
323         const char      *tmp;
324         char            *ret;
325         mapi_object_t   obj_stream;
326         uint32_t        stream_size;
327         uint16_t        read_size;
328         DATA_BLOB       data;
329         magic_t         cookie = NULL;
330
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);
335                 return NULL;
336         }
337
338         data.length = 0;
339         data.data = talloc_zero_size(mem_ctx, size);
340
341         for (stream_size = 0; stream_size < size; ) {
342                 /*
343                  * exchange can only handle about 4K chunks at a time,  and don't
344                  * ask for more or you get none
345                  */
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)
350                         break;
351                 stream_size += read_size;
352         }
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);
359                 return NULL;
360         }
361
362         data.length = stream_size;
363
364         if (magic) {
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);
372                         return NULL;
373                 }
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);
379                         return NULL;
380                 }
381                 tmp = magic_buffer(cookie, (void *)data.data, data.length);
382                 *magic = talloc_strdup(mem_ctx, tmp);
383                 magic_close(cookie);
384         }
385
386         /* convert attachment to base64 */
387         ret = ldb_base64_encode(mem_ctx, (const char *)data.data, data.length);
388
389         talloc_free(data.data);
390         mapi_object_release(&obj_stream);
391
392         return ret;
393 }
394
395
396 #define WRAP_LINES_AT   76
397
398 static void write_base64_data(FILE *fp, const char *buf)
399 {
400         int len = strlen(buf);
401         size_t n;
402
403         while (len > 0) {
404                 int chunk = len > WRAP_LINES_AT ? WRAP_LINES_AT : len;
405
406                 n = fwrite(buf, len > WRAP_LINES_AT ? WRAP_LINES_AT : len, 1, fp);
407                 if (n != 1)
408                         fprintf(stderr, "Error writing %d bytes of base64 attachment: %d\n",
409                                 chunk, ferror(fp));
410                 len -= chunk;
411                 buf += chunk;
412                 fwrite("\n", 1, 1, fp);
413         }
414 }
415
416
417 /*
418  * Read a stream and store it in a DATA_BLOB
419  */
420 static enum MAPISTATUS get_stream(TALLOC_CTX *mem_ctx,
421                                          mapi_object_t *obj_stream, 
422                                          DATA_BLOB *body)
423 {
424         enum MAPISTATUS retval;
425         uint16_t        read_size;
426         uint8_t         buf[MAX_READ_SIZE];
427
428         body->length = 0;
429         body->data = talloc_zero(mem_ctx, uint8_t);
430
431         do {
432                 retval = ReadStream(obj_stream, buf, MAX_READ_SIZE, &read_size);
433                 MAPI_RETVAL_IF(retval, GetLastError(), body->data);
434                 if (read_size) {
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;
439                 }
440         } while (read_size);
441
442         errno = 0;
443         return MAPI_E_SUCCESS;
444 }
445
446 typedef struct {
447    DATA_BLOB body;
448    char *body_header;
449 } body_stuff_t;
450
451 /* Fetch the body, no fancy junk */
452 static enum MAPISTATUS get_body(TALLOC_CTX *mem_ctx,
453                                        mapi_object_t *obj_message,
454                                        struct SRow *aRow,
455                                            body_stuff_t body[3],
456                                            int *body_count)
457 {
458         mapi_object_t           obj_stream;
459         enum MAPISTATUS                 retval;
460         char *data;
461         const struct SBinary_short      *bin;
462
463         *body_count = 0;
464         memset(body, 0, sizeof(body));
465
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";
471                 (*body_count)++;
472         }
473
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";
479                 (*body_count)++;
480         }
481         
482 #if 0
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";
488                 (*body_count)++;
489         }
490 #endif
491
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);
497                 if (retval) {
498                         fprintf(stderr, "Failed to get a message body, making empty one: %x\n", retval);
499                         //message_error = 1;
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";
503                         (*body_count)++;
504                         body[*body_count].body.length = 0;
505                 } else {
506                         retval = get_stream(mem_ctx, &obj_stream, &body[*body_count].body);
507                         if (retval) {
508                                 body[*body_count].body.length = 0;
509                                 fprintf(stderr, "HTML ERROR1 %x\n", retval);
510                         }
511                 }
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";
515                         (*body_count)++;
516                 }
517         }
518
519         return MAPI_E_SUCCESS;
520 }
521
522 /**
523    Sample mbox mail:
524
525    From Administrator Mon Apr 23 14:43:01 2007
526    Date: Mon Apr 23 14:43:01 2007
527    From: Administrator 
528    To: Julien Kerihuel
529    Subject: This is the subject
530
531    This is a sample mail
532
533 **/
534
535 static bool message2mbox(TALLOC_CTX *mem_ctx, FILE *fp, 
536                          struct SRow *aRow, mapi_object_t *obj_message,
537                          int base_level)
538 {
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;
549         const char                      *msgid;
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;
556         char                            *magic;
557         char                            *line = NULL;
558         struct SPropTagArray            *SPropTagArray = NULL;
559         struct SPropValue               *lpProps;
560         struct SRow                     aRow2;
561         struct SRowSet                  rowset_attach;
562         uint32_t                        count;
563         unsigned int                    i;
564         int                             header_done = 0;
565         body_stuff_t                    body[3];
566         int                             body_count = 0;
567
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);
572
573         delivery_date = (const uint64_t *)octool_get_propval(aRow, PR_MESSAGE_DELIVERY_TIME);
574         if (delivery_date) {
575                 date = nt_time_string(mem_ctx, *delivery_date);
576         } else {
577                 date = "None";
578         }
579
580         from = (const char *) octool_get_propval(aRow, PR_SENT_REPRESENTING_NAME);
581         if (from == NULL) {
582                 from = "unknown";
583         }
584
585         subject = (const char*) octool_get_propval(aRow, PR_SUBJECT);
586         msgid = (const char *) octool_get_propval(aRow, PR_INTERNET_MESSAGE_ID);
587
588         msgheaders = (const char *) octool_get_propval(aRow, PR_TRANSPORT_MESSAGE_HEADERS);
589
590         retval = get_body(mem_ctx, obj_message, aRow, body, &body_count);
591
592         /* First line From - but only if base_level == 0 */
593         if (base_level == 0) {
594                 char *f, *p;
595                 f = talloc_strdup(mem_ctx, from);
596                 /* strip out all '"'s, ugly but works */
597                 for (p = f; p && *p; ) {
598                         if (*p == '"') {
599                                 memmove(p, p+1, strlen(p)); /* gets NUL */
600                                 continue;
601                         }
602                         p++;
603                 }
604                 fprintf(fp, "From \"%s\" %s\n", f, date);
605                 talloc_free(f);
606         }
607
608         if (msgheaders) {
609                 char *mhalloc, *mhclean, *mhend, *mhp, *mht;
610                 
611                 mhalloc = talloc_strdup(mem_ctx, msgheaders);
612
613                 mhclean = mhalloc;
614                 do {
615                         /* Skip past the comment Exchange adds to the beginning */
616                         mhclean = strchr(mhclean, '\n');
617                         if (!mhclean)
618                                 goto old_code;
619                         mhclean++;
620                 } while (isspace(*mhclean));
621                 
622                 /* Trim off the empty mime parts that Exchange leaves after 
623                    the end of the headers */
624                 mhend = strstr(mhclean, "\n------=");
625                 if (mhend) {
626                         mhend++;
627                         *mhend = '\0';
628                 }
629
630                 /* strip CR/NL */
631                 while ((mhend = strchr(mhclean, '\r')))
632                         memmove(mhend, mhend+1, strlen(mhend) /* gets NUL */);
633
634                 /*
635                  * strip Content-* headers (we make our own), be sure to get
636                  * and extended (indented parts) of this header
637                  */
638                 while (1) {
639                         /*
640                          * strip some bad-for-us headers (poor mans lookup table below :-)
641                          */
642                         mhend = find_header(mhclean, "Content-");
643                         if (!mhend)
644                                 mhend = find_header(mhclean, "X-MS-");
645                         if (!mhend)
646                                 mhend = find_header(mhclean, "Lines:");
647                         if (!mhend)
648                                 break;
649                         mhp = NULL;
650                         if (mhend) {
651                                 mht = mhend;
652                                 while ((mhp = strchr(mht, '\n'))) {
653                                         mhp++;
654                                         if (!*mhp || !isspace(*mhp))
655                                                 break;
656                                         mht = mhp;
657                                 }
658                         }
659                         if (mhp) {
660                                 /* match was in the middle of other headers */
661                                 memmove(mhend, mhp, strlen(mhp) + 1);
662                         } else if (mhend) {
663                                 /* match was at the end of the headers, truncate it */
664                                 *mhend = '\0';
665                         }
666                 }
667
668                 /* remove any NL's at end of headers */
669                 mhp = mhclean + strlen(mhclean);
670                 while (mhp > mhclean && mhp[-1] == '\n')
671                         *--mhp = '\0';
672                 
673                 line = talloc_asprintf(mem_ctx, "%s\n", mhclean);
674                 if (line) fwrite(line, strlen(line), 1, fp);
675                 talloc_free(line);
676                 talloc_free(mhalloc);
677         }
678
679 old_code:
680         if (!msgheaders) {
681                 /* Second line: Date */
682                 line = talloc_asprintf(mem_ctx, "Date: %s\n", date);
683                 if (line) {
684                         fwrite(line, strlen(line), 1, fp);
685                 }
686                 talloc_free(line);
687
688                 /* Third line From */
689                 line = talloc_asprintf(mem_ctx, "From: %s\n", from);
690                 if (line) {
691                         fwrite(line, strlen(line), 1, fp);
692                 }
693                 talloc_free(line);
694
695                 /* To, Cc, Bcc */
696                 if (to) {
697                         line = talloc_asprintf(mem_ctx, "To: %s\n", to);
698                         if (line) {
699                                 fwrite(line, strlen(line), 1, fp);
700                         }
701                         talloc_free(line);
702                 }
703
704                 if (cc) {
705                         line = talloc_asprintf(mem_ctx, "Cc: %s\n", cc);
706                         if (line) {
707                                 fwrite(line, strlen(line), 1, fp);
708                         }
709                         talloc_free(line);
710                 }
711
712                 if (bcc) {
713                         line = talloc_asprintf(mem_ctx, "Bcc: %s\n", bcc);
714                         if (line) {
715                                 fwrite(line, strlen(line), 1, fp);
716                         }
717                         talloc_free(line);
718                 }
719
720                 /* Subject */
721                 if (subject) {
722                         line = talloc_asprintf(mem_ctx, "Subject: %s\n", subject);
723                         if (line) {
724                                 fwrite(line, strlen(line), 1, fp);
725                         }
726                         talloc_free(line);
727                 }
728
729                 if (msgid) {
730                         line = talloc_asprintf(mem_ctx, "Message-ID: %s\n", msgid);
731                         if (line) {
732                                 fwrite(line, strlen(line), 1, fp);
733                         }
734                         talloc_free(line);
735                 }
736         }
737
738         /* Set multi-type if we have attachment mixed for all things/alternative
739          * for just bodies */
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));
746         }
747
748         /* body */
749         if (body_count > 0) {
750                 if ((has_attach && *has_attach) || body_count > 1) {
751                         /* blank line before content */
752                         if (!header_done) {
753                                 fwrite("\n", 1, 1, fp);
754                                 header_done = 1;
755                         }
756                         fprintf(fp, "--%s\n", boundary(base_level+0));
757                 }
758
759                 /*
760                  * if we have more than 1 body we need to do a multipart alternative
761                  * within the first attachment
762                  */
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));
767                 }
768
769                 /*
770                  * output content type
771                  */
772                 fwrite(body[0].body_header, strlen(body[0].body_header), 1, fp);
773                 fprintf(fp, "Content-Disposition: inline\n");
774
775                 /* blank after header */
776                 fwrite("\n", 1, 1, fp);
777                 header_done = 1;
778
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);
782         }
783
784         /* blank line before content */
785         if (!header_done) {
786                 fwrite("\n", 1, 1, fp);
787                 header_done = 1;
788         }
789
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));
794                 } else {
795                         fprintf(fp, "\n\n--%s\n", boundary(base_level+0));
796                 }
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);
802         }
803
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));
808                 }
809
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);
817                         
818                         retval = QueryRows(&obj_tb_attach, 0xa, TBL_ADVANCE, &rowset_attach);
819                         MAPI_RETVAL_IF(retval, retval, NULL);
820                         
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;
824                                 attach_num = &n;
825                                 retval = OpenAttach(obj_message, *attach_num, &obj_attach);
826                                 if (retval == MAPI_E_SUCCESS) {
827                                         SPropTagArray = set_SPropTagArray(mem_ctx, 0x5,
828                                                                           PR_ATTACH_FILENAME,
829                                                                           PR_ATTACH_LONG_FILENAME,
830                                                                           PR_ATTACH_SIZE,
831                                                                           PR_ATTACH_MIME_TAG,
832                                                                           PR_ATTACH_METHOD);
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;
838
839                                                 aRow2.ulAdrEntryPad = 0;
840                                                 aRow2.cValues = count;
841                                                 aRow2.lpProps = lpProps;
842
843                                                 mp = (uint32_t *) octool_get_propval(&aRow2, PR_ATTACH_METHOD);
844                                                 if (mp)
845                                                         method = *mp;
846
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));
850                                                 }
851                                                 attach_size = (const uint32_t *) octool_get_propval(&aRow2, PR_ATTACH_SIZE);                                            
852
853                                                 attachment_data = NULL;
854                                                 switch (method) {
855                                                 case ATTACH_BY_VALUE:
856                                                         magic = (char *) octool_get_propval(&aRow2, PR_ATTACH_MIME_TAG);                                                
857                                                         if (magic)
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) {
863                                                                 message_error = 1;
864                                                                 fprintf(stderr, "Failed to read attachment for message %s\n", msgid ? msgid : "unknown");
865                                                                 break;
866                                                         }
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);
873                                                         talloc_free(magic);
874                                                         break;
875                                                 case ATTACH_BY_REFERENCE:
876                                                         fprintf(stderr,"ATTACH_BY_REFERENCE unsupported\n");
877                                                         message_error = 1;
878                                                         break;
879                                                 case ATTACH_BY_REF_RESOLVE:
880                                                         fprintf(stderr,"ATTACH_BY_REF_RESOLVE unsupported\n");
881                                                         message_error = 1;
882                                                         break;
883                                                 case ATTACH_BY_REF_ONLY:
884                                                         fprintf(stderr,"ATTACH_BY_REF_ONLY unsupported\n");
885                                                         message_error = 1;
886                                                         break;
887                                                 case ATTACH_EMBEDDED_MSG: {
888                                                         mapi_object_t obj_embeddedmsg;
889                                                         struct SPropTagArray    *embTagArray = NULL;
890                                                         struct SPropValue               *embProps;
891                                                         struct SRow                             eRow;
892                                                         uint32_t                                emb_count = 0;
893
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);
899                                                                 message_error = 1;
900                                                                 break;
901                                                         }
902
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,
910                                                                                         PR_BODY,
911                                                                                         PR_BODY_UNICODE,
912                                                                                         PR_HTML,
913                                                                                         PR_RTF_COMPRESSED,
914                                                                                         PR_RTF_IN_SYNC,
915                                                                                         PR_SENT_REPRESENTING_NAME,
916                                                                                         PR_SENT_REPRESENTING_NAME_UNICODE,
917                                                                                         PR_DISPLAY_TO,
918                                                                                         PR_DISPLAY_TO_UNICODE,
919                                                                                         PR_DISPLAY_CC,
920                                                                                         PR_DISPLAY_CC_UNICODE,
921                                                                                         PR_DISPLAY_BCC,
922                                                                                         PR_DISPLAY_BCC_UNICODE,
923                                                                                         PR_HASATTACH,
924                                                                                         PR_TRANSPORT_MESSAGE_HEADERS);
925                                                         retval = GetProps(&obj_embeddedmsg, MAPI_UNICODE, embTagArray,
926                                                                         &embProps, &emb_count);
927                                                         MAPIFreeBuffer(embTagArray);
928
929                                                         if (retval != MAPI_E_SUCCESS) {
930                                                                 fprintf(stderr, "Failed to get Embedded msg props: %x\n", retval);
931                                                                 message_error = 1;
932                                                                 break;
933                                                         }
934
935                                                         /* Build a SRow structure */
936                                                         eRow.ulAdrEntryPad = 0;
937                                                         eRow.cValues = emb_count;
938                                                         eRow.lpProps = embProps;
939
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");
943                                                         fprintf(fp, "\n");
944
945                                                         message2mbox(mem_ctx, fp, &eRow, &obj_embeddedmsg,
946                                                                         base_level + 2 /* 0 = main, 1 = alt */);
947                                                         talloc_free(embProps);
948                                                         } break;
949                                                 case ATTACH_OLE:
950                                                         fprintf(stderr,"ATTACH_OLE unsupported - "
951                                                                         "allowing message through anyway\n");
952                                                         // message_error = 1;
953                                                         break;
954                                                 default:
955                                                         fprintf(stderr, "Unsupported attach method = %d\n", method);
956                                                         message_error = 1;
957                                                         break;
958                                                 }
959                                         }
960                                         MAPIFreeBuffer(lpProps);
961                                 }
962                         }
963
964                         line = talloc_asprintf(mem_ctx, "\n\n--%s--\n\n\n", boundary(base_level+0));
965                         if (line) {
966                                 fwrite(line, strlen(line), 1, fp);
967                         }
968                         talloc_free(line);
969                 }
970         } else if (body_count > 1) {
971                 /* close body attachments */
972                 fprintf(fp, "\n\n--%s--\n", boundary(base_level+0));
973         }
974         
975         fwrite("\n\n\n", 3, 1, fp);
976
977         return true;
978 }
979
980
981
982 int main(int argc, const char *argv[])
983 {
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;
993         mapi_id_t                       id_inbox;
994         uint32_t                        count;
995         struct SPropTagArray            *SPropTagArray = NULL;
996         struct SPropValue               *lpProps;
997         struct SRow                     aRow;
998         struct SRowSet                  rowset;
999         poptContext                     pc;
1000         int                             opt;
1001         FILE                            *fp;
1002         unsigned int                    i;
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;
1010         const char                      *msgid;
1011
1012         enum {OPT_PROFILE_DB=1000, OPT_PROFILE, OPT_PASSWORD, OPT_MBOX, OPT_UPDATE,
1013               OPT_DEBUG, OPT_DUMPDATA, OPT_TEST};
1014
1015         struct poptOption long_options[] = {
1016                 POPT_AUTOHELP
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 }
1027         };
1028
1029         start_time = time(0);
1030
1031         mem_ctx = talloc_named(NULL, 0, "exchange2mbox");
1032
1033         pc = poptGetContext("exchange2mbox", argc, argv, long_options, 0);
1034
1035         while ((opt = poptGetNextOpt(pc)) != -1) {
1036                 switch (opt) {
1037                 case OPT_PROFILE_DB:
1038                         opt_profdb = poptGetOptArg(pc);
1039                         break;
1040                 case OPT_PROFILE:
1041                         opt_profname = talloc_strdup(mem_ctx, (char *)poptGetOptArg(pc));
1042                         break;
1043                 case OPT_PASSWORD:
1044                         opt_password = poptGetOptArg(pc);
1045                         break;
1046                 case OPT_MBOX:
1047                         opt_mbox = poptGetOptArg(pc);
1048                         break;
1049                 case OPT_UPDATE:
1050                         opt_update = true;
1051                         break;
1052                 case OPT_TEST:
1053                         opt_test = true;
1054                         break;
1055                 case OPT_DEBUG:
1056                         opt_debug = poptGetOptArg(pc);
1057                         break;
1058                 case OPT_DUMPDATA:
1059                         opt_dumpdata = true;
1060                         break;
1061                 }
1062         }
1063
1064         /**
1065          * Sanity checks
1066          */
1067
1068         if (!opt_profdb) {
1069                 opt_profdb = talloc_asprintf(mem_ctx, DEFAULT_PROFDB, getenv("HOME"));
1070         }
1071
1072         if (!opt_mbox) {
1073                 opt_mbox = talloc_asprintf(mem_ctx, DEFAULT_MBOX, getenv("HOME"));
1074         }
1075
1076         /**
1077          * Open the MBOX
1078          */
1079
1080         if ((fp = fopen(opt_mbox, "a+")) == NULL) {
1081                 perror("fopen");
1082                 exit (1);
1083         }
1084
1085         /**
1086          * Initialize MAPI subsystem
1087          */
1088
1089         retval = MAPIInitialize(&mapi_ctx, opt_profdb);
1090         if (retval != MAPI_E_SUCCESS) {
1091                 mapi_errstr("MAPIInitialize", GetLastError());
1092                 exit (1);
1093         }
1094
1095         /* debug options */
1096         SetMAPIDumpData(mapi_ctx, opt_dumpdata);
1097
1098         if (opt_debug) {
1099                 SetMAPIDebugLevel(mapi_ctx, atoi(opt_debug));
1100         }
1101
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");
1107                         exit (1);
1108                 }
1109         }
1110         
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());
1115                 exit (1);
1116         }
1117         profile = session->profile;
1118
1119         /* not sure about this,  but it works and it's nice to have it there */
1120         profile->mapi_ctx = mapi_ctx;
1121
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);
1127                         exit (1);
1128                 }
1129         }
1130         
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());
1136                 exit (1);
1137         }
1138
1139         /* Open Inbox */
1140         retval = GetReceiveFolder(&obj_store, &id_inbox, NULL);
1141         MAPI_RETVAL_IF(retval, retval, mem_ctx);
1142
1143         mapi_object_init(&obj_inbox);
1144         retval = OpenFolder(&obj_store, id_inbox, &obj_inbox);
1145         MAPI_RETVAL_IF(retval, retval, mem_ctx);
1146
1147         mapi_object_init(&obj_table);
1148         retval = GetContentsTable(&obj_inbox, &obj_table, 0, &count);
1149         MAPI_RETVAL_IF(retval, retval, mem_ctx);
1150
1151         SPropTagArray = set_SPropTagArray(mem_ctx, 0x5,
1152                                           PR_FID,
1153                                           PR_MID,
1154                                           PR_INST_ID,
1155                                           PR_INSTANCE_NUM,
1156                                           PR_INTERNET_MESSAGE_ID);
1157         retval = SetColumns(&obj_table, SPropTagArray);
1158         MAPIFreeBuffer(SPropTagArray);
1159         MAPI_RETVAL_IF(retval, retval, mem_ctx);
1160
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, 
1167                                              &obj_message, 0);
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,
1176                                                                   PR_BODY,
1177                                                                   PR_BODY_UNICODE,
1178                                                                   PR_HTML,
1179                                                                   PR_RTF_COMPRESSED,
1180                                                                   PR_RTF_IN_SYNC,
1181                                                                   PR_SENT_REPRESENTING_NAME,
1182                                                                   PR_SENT_REPRESENTING_NAME_UNICODE,
1183                                                                   PR_DISPLAY_TO,
1184                                                                   PR_DISPLAY_TO_UNICODE,
1185                                                                   PR_DISPLAY_CC,
1186                                                                   PR_DISPLAY_CC_UNICODE,
1187                                                                   PR_DISPLAY_BCC,
1188                                                                   PR_DISPLAY_BCC_UNICODE,
1189                                                                   PR_HASATTACH,
1190                                                                   PR_TRANSPORT_MESSAGE_HEADERS,
1191                                                                   PR_SUBJECT_PREFIX,
1192                                                                   PR_SUBJECT_PREFIX_UNICODE,
1193                                                                   PR_NORMALIZED_SUBJECT,
1194                                                                   PR_NORMALIZED_SUBJECT_UNICODE,
1195                                                                   PR_SUBJECT,
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);
1201                                         exit (1);
1202                                 }
1203
1204                                 /* Build a SRow structure */
1205                                 aRow.ulAdrEntryPad = 0;
1206                                 aRow.cValues = count;
1207                                 aRow.lpProps = lpProps;
1208
1209                                 msgid = (const char *) octool_get_propval(&aRow, PR_INTERNET_MESSAGE_ID);
1210                                 if (msgid) {
1211                                         retval = FindProfileAttr(profile, "Message-ID", msgid);
1212                                         if (GetLastError() == MAPI_E_NOT_FOUND) {
1213                                                 bool ok;
1214                                                 
1215                                                 message_error = 0;
1216                                                 ok = message2mbox(mem_ctx, fp, &aRow, &obj_message, 0);
1217                                                 if (!ok) {
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);
1224                                                 } else if
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());
1227                                                 } else {
1228                                                         printf("Message-ID: %s added to profile %s\n", msgid, profile->profname);
1229                                                 }
1230                                         } else {
1231                                                 printf("Message-ID: %s already in profile %s\n", msgid, profile->profname);
1232                                         }
1233                                 } else {
1234                                         fprintf(stderr, "%s: message with no msgid cannot be downloaded\n", profile->profname);
1235                                 }
1236                                 talloc_free(lpProps);
1237                         } else {
1238                                 fprintf(stderr, "could not open row %d: retval=%d GetLastError=%d\n", i, retval, GetLastError());
1239                         }
1240                         mapi_object_release(&obj_message);
1241                         errno = 0;
1242                 }
1243         }
1244
1245         fclose(fp);
1246         mapi_object_release(&obj_table);
1247         mapi_object_release(&obj_inbox);
1248         mapi_object_release(&obj_store);
1249         MAPIUninitialize(mapi_ctx);
1250
1251         talloc_free(mem_ctx);
1252
1253         return 0;
1254 }