Add verbatim quickmail.c from libquickmail 0.1.18.
[dcpomatic.git] / src / lib / quickmail.cc
1 /*
2     This file is part of libquickmail.
3
4     libquickmail is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 3 of the License, or
7     (at your option) any later version.
8
9     libquickmail is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with libquickmail.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #if defined(_WIN32) && defined(DLL_EXPORT) && !defined(BUILD_QUICKMAIL_DLL)
19 #define BUILD_QUICKMAIL_DLL
20 #endif
21 #include "quickmail.h"
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <time.h>
26 #ifndef _WIN32
27 #include <unistd.h>
28 #endif
29 #if _MSC_VER
30 #define snprintf _snprintf
31 #define strdup _strdup
32 #endif
33 #ifndef NOCURL
34 #if (defined(STATIC) || defined(BUILD_QUICKMAIL_STATIC)) && !defined(CURL_STATICLIB)
35 #define CURL_STATICLIB
36 #endif
37 #include <curl/curl.h>
38 #else
39 #include "smtpsocket.h"
40 #endif
41
42 #define LIBQUICKMAIL_VERSION_MAJOR 0
43 #define LIBQUICKMAIL_VERSION_MINOR 1
44 #define LIBQUICKMAIL_VERSION_MICRO 18
45
46 #define VERSION_STRINGIZE_(major, minor, micro) #major"."#minor"."#micro
47 #define VERSION_STRINGIZE(major, minor, micro) VERSION_STRINGIZE_(major, minor, micro)
48
49 #define LIBQUICKMAIL_VERSION VERSION_STRINGIZE(LIBQUICKMAIL_VERSION_MAJOR,LIBQUICKMAIL_VERSION_MINOR,LIBQUICKMAIL_VERSION_MICRO)
50
51 #define NEWLINE "\r\n"
52 #define NEWLINELENGTH 2
53 //#define NEWLINE "\n"
54 //#define NEWLINELENGTH 1
55
56 #define MIME_LINE_WIDTH 72
57 #define BODY_BUFFER_SIZE 256
58
59 //definitions of the differen stages of generating the message data
60 #define MAILPART_INITIALIZE 0
61 #define MAILPART_HEADER     1
62 #define MAILPART_BODY       2
63 #define MAILPART_BODY_DONE  3
64 #define MAILPART_ATTACHMENT 4
65 #define MAILPART_END        5
66 #define MAILPART_DONE       6
67
68 static const char* default_mime_type = "text/plain";
69
70 ////////////////////////////////////////////////////////////////////////
71
72 #define DEBUG_ERROR(errmsg)
73 static const char* ERRMSG_MEMORY_ALLOCATION_ERROR = "Memory allocation error";
74
75 ////////////////////////////////////////////////////////////////////////
76
77 char* randomize_zeros (char* data)
78 {
79   //replace all 0s with random digits
80   char* p = data;
81   while (*p) {
82     if (*p == '0')
83       *p = '0' + rand() % 10;
84     p++;
85   }
86   return data;
87 }
88
89 char* str_append (char** data, const char* newdata)
90 {
91   //append a string to the end of an existing string
92   char* p;
93   int len = (*data ? strlen(*data) : 0);
94   if ((p = (char*)realloc(*data, len + strlen(newdata) + 1)) == NULL) {
95     free(p);
96     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
97     return NULL;
98   }
99   *data = p;
100   strcpy(*data + len, newdata);
101   return *data;
102 }
103
104 ////////////////////////////////////////////////////////////////////////
105
106 struct email_info_struct {
107   int current;  //must be zet to 0
108   time_t timestamp;
109   char* from;
110   struct email_info_email_list_struct* to;
111   struct email_info_email_list_struct* cc;
112   struct email_info_email_list_struct* bcc;
113   char* subject;
114   char* header;
115   struct email_info_attachment_list_struct* bodylist;
116   struct email_info_attachment_list_struct* attachmentlist;
117   char* buf;
118   int buflen;
119   char* mime_boundary_body;
120   char* mime_boundary_part;
121   struct email_info_attachment_list_struct* current_attachment;
122   FILE* debuglog;
123   char dtable[64];
124 };
125
126 ////////////////////////////////////////////////////////////////////////
127
128 struct email_info_email_list_struct {
129   char* data;
130   struct email_info_email_list_struct* next;
131 };
132
133 void email_info_string_list_add (struct email_info_email_list_struct** list, const char* data)
134 {
135   struct email_info_email_list_struct** p = list;
136   while (*p)
137     p = &(*p)->next;
138   if ((*p = (struct email_info_email_list_struct*)malloc(sizeof(struct email_info_email_list_struct))) == NULL) {
139     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
140     return;
141   }
142   (*p)->data = (data ? strdup(data) : NULL);
143   (*p)->next = NULL;
144 }
145
146 void email_info_string_list_free (struct email_info_email_list_struct** list)
147 {
148   struct email_info_email_list_struct* p = *list;
149   struct email_info_email_list_struct* current;
150   while (p) {
151     current = p;
152     p = current->next;
153     free(current->data);
154     free(current);
155   }
156   *list = NULL;
157 }
158
159 char* email_info_string_list_concatenate (struct email_info_email_list_struct* list)
160 {
161   char* result = NULL;
162   struct email_info_email_list_struct* listentry = list;
163   while (listentry) {
164     if (listentry->data && *listentry->data) {
165       if (result)
166         str_append(&result, "," NEWLINE "\t");
167       str_append(&result, "<");
168       str_append(&result, listentry->data);
169       str_append(&result, ">");
170     }
171     listentry = listentry->next;
172   }
173   return result;
174 }
175
176 ////////////////////////////////////////////////////////////////////////
177
178 struct email_info_attachment_list_struct {
179   char* filename;
180   char* mimetype;
181   void* filedata;
182   void* handle;
183   quickmail_attachment_open_fn email_info_attachment_open;
184   quickmail_attachment_read_fn email_info_attachment_read;
185   quickmail_attachment_close_fn email_info_attachment_close;
186   quickmail_attachment_free_filedata_fn email_info_attachment_filedata_free;
187   struct email_info_attachment_list_struct* next;
188 };
189
190 struct email_info_attachment_list_struct* email_info_attachment_list_add (struct email_info_attachment_list_struct** list, const char* filename, const char* mimetype, void* filedata, quickmail_attachment_open_fn email_info_attachment_open, quickmail_attachment_read_fn email_info_attachment_read, quickmail_attachment_close_fn email_info_attachment_close, quickmail_attachment_free_filedata_fn email_info_attachment_filedata_free)
191 {
192   struct email_info_attachment_list_struct** p = list;
193   while (*p)
194     p = &(*p)->next;
195   if ((*p = (struct email_info_attachment_list_struct*)malloc(sizeof(struct email_info_attachment_list_struct))) == NULL) {
196     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
197     return NULL;
198   }
199   (*p)->filename = strdup(filename ? filename : "UNNAMED");
200   (*p)->mimetype = (mimetype ? strdup(mimetype) : NULL);
201   (*p)->filedata = filedata;
202   (*p)->handle = NULL;
203   (*p)->email_info_attachment_open = email_info_attachment_open;
204   (*p)->email_info_attachment_read = email_info_attachment_read;
205   (*p)->email_info_attachment_close = email_info_attachment_close;
206   (*p)->email_info_attachment_filedata_free = email_info_attachment_filedata_free;
207   (*p)->next = NULL;
208   return *p;
209 }
210
211 void email_info_attachment_list_free_entry (struct email_info_attachment_list_struct* current)
212 {
213   if (current->handle) {
214     if (current->email_info_attachment_close)
215       current->email_info_attachment_close(current->handle);
216     //else
217     //  free(current->handle);
218     current->handle = NULL;
219   }
220   if (current->filedata) {
221     if (current->email_info_attachment_filedata_free)
222       current->email_info_attachment_filedata_free(current->filedata);
223     else
224       free(current->filedata);
225   }
226   if (current->mimetype)
227     free(current->mimetype);
228   free(current->filename);
229   free(current);
230 }
231
232 void email_info_attachment_list_free (struct email_info_attachment_list_struct** list)
233 {
234   struct email_info_attachment_list_struct* p = *list;
235   struct email_info_attachment_list_struct* current;
236   while (p) {
237     current = p;
238     p = current->next;
239     email_info_attachment_list_free_entry(current);
240   }
241   *list = NULL;
242 }
243
244 int email_info_attachment_list_delete (struct email_info_attachment_list_struct** list, const char* filename)
245 {
246   struct email_info_attachment_list_struct** p = list;
247   while (*p) {
248     if (strcmp((*p)->filename, filename) == 0) {
249       struct email_info_attachment_list_struct* current = *p;
250       *p = current->next;
251       email_info_attachment_list_free_entry(current);
252       return 0;
253     }
254     p = &(*p)->next;
255   }
256   return -1;
257 }
258
259 void email_info_attachment_list_close_handles (struct email_info_attachment_list_struct* list)
260 {
261   struct email_info_attachment_list_struct* p = list;
262   while (p) {
263     if (p->handle) {
264       if (p->email_info_attachment_close)
265         p->email_info_attachment_close(p->handle);
266       //else
267       //  free(p->handle);
268       p->handle = NULL;
269     }
270     p = p->next;
271   }
272 }
273
274 //dummy attachment functions
275
276 void* email_info_attachment_open_dummy (void* filedata)
277 {
278   return &email_info_attachment_open_dummy;
279 }
280
281 size_t email_info_attachment_read_dummy (void* handle, void* buf, size_t len)
282 {
283   return 0;
284 }
285
286 struct email_info_attachment_list_struct* email_info_attachment_list_add_dummy (struct email_info_attachment_list_struct** list, const char* filename, const char* mimetype)
287 {
288   return email_info_attachment_list_add(list, filename, mimetype, NULL, email_info_attachment_open_dummy, email_info_attachment_read_dummy, NULL, NULL);
289 }
290
291 //file attachment functions
292
293 void* email_info_attachment_open_file (void* filedata)
294 {
295   return (void*)fopen((char*)filedata, "rb");
296 }
297
298 size_t email_info_attachment_read_file (void* handle, void* buf, size_t len)
299 {
300   return fread(buf, 1, len, (FILE*)handle);
301 }
302
303 void email_info_attachment_close_file (void* handle)
304 {
305   if (handle)
306     fclose((FILE*)handle);
307 }
308
309 struct email_info_attachment_list_struct* email_info_attachment_list_add_file (struct email_info_attachment_list_struct** list, const char* path, const char* mimetype)
310 {
311   //determine base filename
312   const char* basename = path + strlen(path);
313   while (basename != path) {
314     basename--;
315     if (*basename == '/'
316 #ifdef _WIN32
317         || *basename == '\\' || *basename == ':'
318 #endif
319     ) {
320       basename++;
321       break;
322     }
323   }
324   return email_info_attachment_list_add(list, basename, mimetype, (void*)strdup(path), email_info_attachment_open_file, email_info_attachment_read_file, email_info_attachment_close_file, NULL);
325 }
326
327 //memory attachment functions
328
329 struct email_info_attachment_memory_filedata_struct {
330   char* data;
331   size_t datalen;
332   int mustfree;
333 };
334
335 struct email_info_attachment_memory_handle_struct {
336   const char* data;
337   size_t datalen;
338   size_t pos;
339 };
340
341 void* email_info_attachment_open_memory (void* filedata)
342 {
343   struct email_info_attachment_memory_filedata_struct* data;
344   struct email_info_attachment_memory_handle_struct* result;
345   data = ((struct email_info_attachment_memory_filedata_struct*)filedata);
346   if (!data->data)
347     return NULL;
348   if ((result = (struct email_info_attachment_memory_handle_struct*)malloc(sizeof(struct email_info_attachment_memory_handle_struct))) == NULL) {
349     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
350     return NULL;
351   }
352   result->data = data->data;
353   result->datalen = data->datalen;
354   result->pos = 0;
355   return result;
356 }
357
358 size_t email_info_attachment_read_memory (void* handle, void* buf, size_t len)
359 {
360   struct email_info_attachment_memory_handle_struct* h = (struct email_info_attachment_memory_handle_struct*)handle;
361   size_t n = (h->pos + len <= h->datalen ? len : h->datalen - h->pos);
362   memcpy(buf, h->data + h->pos, n);
363   h->pos += n;
364   return n;
365 }
366
367 void email_info_attachment_close_memory (void* handle)
368 {
369   if (handle)
370     free(handle);
371 }
372
373 void email_info_attachment_filedata_free_memory (void* filedata)
374 {
375   struct email_info_attachment_memory_filedata_struct* data = ((struct email_info_attachment_memory_filedata_struct*)filedata);
376   if (data) {
377     if (data->mustfree)
378       free(data->data);
379     free(data);
380   }
381 }
382
383 struct email_info_attachment_list_struct* email_info_attachment_list_add_memory (struct email_info_attachment_list_struct** list, const char* filename, const char* mimetype, char* data, size_t datalen, int mustfree)
384 {
385   struct email_info_attachment_memory_filedata_struct* filedata;
386   if ((filedata = (struct email_info_attachment_memory_filedata_struct*)malloc(sizeof(struct email_info_attachment_memory_filedata_struct))) == NULL) {
387     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
388     return NULL;
389   }
390   filedata->data = data;
391   filedata->datalen = datalen;
392   filedata->mustfree = mustfree;
393   return email_info_attachment_list_add(list, filename, mimetype, filedata, email_info_attachment_open_memory, email_info_attachment_read_memory, email_info_attachment_close_memory, email_info_attachment_filedata_free_memory);
394 }
395
396 ////////////////////////////////////////////////////////////////////////
397
398 DLL_EXPORT_LIBQUICKMAIL const char* quickmail_get_version ()
399 {
400   return VERSION_STRINGIZE(LIBQUICKMAIL_VERSION_MAJOR, LIBQUICKMAIL_VERSION_MINOR, LIBQUICKMAIL_VERSION_MICRO)
401 #if defined(NOCURL)
402     "-light"
403 #endif
404   ;
405 }
406
407 DLL_EXPORT_LIBQUICKMAIL int quickmail_initialize ()
408 {
409 #if defined(NOCURL) && defined(_WIN32)
410   static WSADATA wsaData;
411   int wsaerr = WSAStartup(MAKEWORD(1, 0), &wsaData);
412   if (wsaerr)
413     return -1;
414   atexit((void(*)())WSACleanup);
415 #endif
416   return 0;
417 }
418
419 DLL_EXPORT_LIBQUICKMAIL quickmail quickmail_create (const char* from, const char* subject)
420 {
421   int i;
422   struct email_info_struct* mailobj;
423   if ((mailobj = (struct email_info_struct*)malloc(sizeof(struct email_info_struct))) == NULL) {
424     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
425     return NULL;
426   }
427   mailobj->current = 0;
428   mailobj->timestamp = time(NULL);
429   mailobj->from = (from ? strdup(from) : NULL);
430   mailobj->to = NULL;
431   mailobj->cc = NULL;
432   mailobj->bcc = NULL;
433   mailobj->subject = (subject ? strdup(subject) : NULL);
434   mailobj->header = NULL;
435   mailobj->bodylist = NULL;
436   mailobj->attachmentlist = NULL;
437   mailobj->buf = NULL;
438   mailobj->buflen = 0;
439   mailobj->mime_boundary_body = NULL;
440   mailobj->mime_boundary_part = NULL;
441   mailobj->current_attachment = NULL;
442   mailobj->debuglog = NULL;
443   for (i = 0; i < 26; i++) {
444     mailobj->dtable[i] = (char)('A' + i);
445     mailobj->dtable[26 + i] = (char)('a' + i);
446   }
447   for (i = 0; i < 10; i++) {
448     mailobj->dtable[52 + i] = (char)('0' + i);
449   }
450   mailobj->dtable[62] = '+';
451   mailobj->dtable[63] = '/';
452   srand(time(NULL));
453   return mailobj;
454 }
455
456 DLL_EXPORT_LIBQUICKMAIL void quickmail_destroy (quickmail mailobj)
457 {
458   free(mailobj->from);
459   email_info_string_list_free(&mailobj->to);
460   email_info_string_list_free(&mailobj->cc);
461   email_info_string_list_free(&mailobj->bcc);
462   free(mailobj->subject);
463   free(mailobj->header);
464   email_info_attachment_list_free(&mailobj->bodylist);
465   email_info_attachment_list_free(&mailobj->attachmentlist);
466   free(mailobj->buf);
467   free(mailobj->mime_boundary_body);
468   free(mailobj->mime_boundary_part);
469   free(mailobj);
470 }
471
472 DLL_EXPORT_LIBQUICKMAIL void quickmail_set_from (quickmail mailobj, const char* from)
473 {
474   free(mailobj->from);
475   mailobj->from = strdup(from);
476 }
477
478 DLL_EXPORT_LIBQUICKMAIL const char* quickmail_get_from (quickmail mailobj)
479 {
480   return mailobj->from;
481 }
482
483 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_to (quickmail mailobj, const char* email)
484 {
485   email_info_string_list_add(&mailobj->to, email);
486 }
487
488 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_cc (quickmail mailobj, const char* email)
489 {
490   email_info_string_list_add(&mailobj->cc, email);
491 }
492
493 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_bcc (quickmail mailobj, const char* email)
494 {
495   email_info_string_list_add(&mailobj->bcc, email);
496 }
497
498 DLL_EXPORT_LIBQUICKMAIL void quickmail_set_subject (quickmail mailobj, const char* subject)
499 {
500   free(mailobj->subject);
501   mailobj->subject = (subject ? strdup(subject) : NULL);
502 }
503
504 DLL_EXPORT_LIBQUICKMAIL const char* quickmail_get_subject (quickmail mailobj)
505 {
506   return mailobj->subject;
507 }
508
509 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_header (quickmail mailobj, const char* headerline)
510 {
511   str_append(&mailobj->header, headerline);
512   str_append(&mailobj->header, NEWLINE);
513 }
514
515 DLL_EXPORT_LIBQUICKMAIL void quickmail_set_body (quickmail mailobj, const char* body)
516 {
517   email_info_attachment_list_free(&mailobj->bodylist);
518   if (body)
519     email_info_attachment_list_add_memory(&mailobj->bodylist, default_mime_type, default_mime_type, strdup(body), strlen(body), 1);
520 }
521
522 DLL_EXPORT_LIBQUICKMAIL char* quickmail_get_body (quickmail mailobj)
523 {
524   size_t n;
525   char* p;
526   char* result = NULL;
527   size_t resultlen = 0;
528   if (mailobj->bodylist && (mailobj->bodylist->handle = mailobj->bodylist->email_info_attachment_open(mailobj->bodylist->filedata)) != NULL) {
529     do {
530       if ((p = (char*)realloc(result, resultlen + BODY_BUFFER_SIZE)) == NULL) {
531         free(result);
532         DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
533         break;
534       }
535       result = p;
536       if ((n = mailobj->bodylist->email_info_attachment_read(mailobj->bodylist->handle, result + resultlen, BODY_BUFFER_SIZE)) > 0)
537         resultlen += n;
538     } while (n > 0);
539     if (mailobj->bodylist->email_info_attachment_close)
540       mailobj->bodylist->email_info_attachment_close(mailobj->bodylist->handle);
541     //else
542     //  free(mailobj->bodylist->handle);
543     mailobj->bodylist->handle = NULL;
544   }
545   return result;
546 }
547
548 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_body_file (quickmail mailobj, const char* mimetype, const char* path)
549 {
550   email_info_attachment_list_add(&mailobj->bodylist, (mimetype ? mimetype : default_mime_type), (mimetype ? mimetype : default_mime_type), (void*)strdup(path), email_info_attachment_open_file, email_info_attachment_read_file, email_info_attachment_close_file, NULL);
551 }
552
553 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_body_memory (quickmail mailobj, const char* mimetype, char* data, size_t datalen, int mustfree)
554 {
555   email_info_attachment_list_add_memory(&mailobj->bodylist, (mimetype ? mimetype : default_mime_type), (mimetype ? mimetype : default_mime_type), data, datalen, mustfree);
556 }
557
558 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_body_custom (quickmail mailobj, const char* mimetype, char* data, quickmail_attachment_open_fn attachment_data_open, quickmail_attachment_read_fn attachment_data_read, quickmail_attachment_close_fn attachment_data_close, quickmail_attachment_free_filedata_fn attachment_data_filedata_free)
559 {
560   email_info_attachment_list_add(&mailobj->bodylist, (mimetype ? mimetype : default_mime_type), (mimetype ? mimetype : default_mime_type), data, (attachment_data_open ? attachment_data_open : email_info_attachment_open_dummy), (attachment_data_read ? attachment_data_read : email_info_attachment_read_dummy), attachment_data_close, attachment_data_filedata_free);
561 }
562
563 DLL_EXPORT_LIBQUICKMAIL int quickmail_remove_body (quickmail mailobj, const char* mimetype)
564 {
565   return email_info_attachment_list_delete(&mailobj->bodylist, mimetype);
566 }
567
568 DLL_EXPORT_LIBQUICKMAIL void quickmail_list_bodies (quickmail mailobj, quickmail_list_attachment_callback_fn callback, void* callbackdata)
569 {
570   struct email_info_attachment_list_struct* p = mailobj->bodylist;
571   while (p) {
572     callback(mailobj, p->filename, p->mimetype, p->email_info_attachment_open, p->email_info_attachment_read, p->email_info_attachment_close, callbackdata);
573     p = p->next;
574   }
575 }
576
577 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_attachment_file (quickmail mailobj, const char* path, const char* mimetype)
578 {
579   email_info_attachment_list_add_file(&mailobj->attachmentlist, path, mimetype);
580 }
581
582 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_attachment_memory (quickmail mailobj, const char* filename, const char* mimetype, char* data, size_t datalen, int mustfree)
583 {
584   email_info_attachment_list_add_memory(&mailobj->attachmentlist, filename, mimetype, data, datalen, mustfree);
585 }
586
587 DLL_EXPORT_LIBQUICKMAIL void quickmail_add_attachment_custom (quickmail mailobj, const char* filename, const char* mimetype, char* data, quickmail_attachment_open_fn attachment_data_open, quickmail_attachment_read_fn attachment_data_read, quickmail_attachment_close_fn attachment_data_close, quickmail_attachment_free_filedata_fn attachment_data_filedata_free)
588 {
589   email_info_attachment_list_add(&mailobj->attachmentlist, filename, mimetype, data, (attachment_data_open ? attachment_data_open : email_info_attachment_open_dummy), (attachment_data_read ? attachment_data_read : email_info_attachment_read_dummy), attachment_data_close, attachment_data_filedata_free);
590 }
591
592 DLL_EXPORT_LIBQUICKMAIL int quickmail_remove_attachment (quickmail mailobj, const char* filename)
593 {
594   return email_info_attachment_list_delete(&mailobj->attachmentlist, filename);
595 }
596
597 DLL_EXPORT_LIBQUICKMAIL void quickmail_list_attachments (quickmail mailobj, quickmail_list_attachment_callback_fn callback, void* callbackdata)
598 {
599   struct email_info_attachment_list_struct* p = mailobj->attachmentlist;
600   while (p) {
601     callback(mailobj, p->filename, p->mimetype, p->email_info_attachment_open, p->email_info_attachment_read, p->email_info_attachment_close, callbackdata);
602     p = p->next;
603   }
604 }
605
606 DLL_EXPORT_LIBQUICKMAIL void quickmail_set_debug_log (quickmail mailobj, FILE* filehandle)
607 {
608   mailobj->debuglog = filehandle;
609 }
610
611 DLL_EXPORT_LIBQUICKMAIL void quickmail_fsave (quickmail mailobj, FILE* filehandle)
612 {
613   int i;
614   size_t n;
615   char buf[80];
616   while ((n = quickmail_get_data(buf, sizeof(buf), 1, mailobj)) > 0) {
617     for (i = 0; i < n; i++)
618       fprintf(filehandle, "%c", buf[i]);
619   }
620 }
621
622 DLL_EXPORT_LIBQUICKMAIL size_t quickmail_get_data (void* ptr, size_t size, size_t nmemb, void* userp)
623 {
624   struct email_info_struct* mailobj = (struct email_info_struct*)userp;
625
626   //abort if no data is requested
627   if (size * nmemb == 0)
628     return 0;
629
630   //initialize on first run
631   if (mailobj->current == MAILPART_INITIALIZE) {
632     free(mailobj->buf);
633     mailobj->buf = NULL;
634     mailobj->buflen = 0;
635     free(mailobj->mime_boundary_body);
636     mailobj->mime_boundary_body = NULL;
637     free(mailobj->mime_boundary_part);
638     mailobj->mime_boundary_part = NULL;
639     mailobj->current_attachment = mailobj->bodylist;
640     mailobj->current++;
641   }
642
643   //process current part of mail if no partial data is pending
644   while (mailobj->buflen == 0) {
645     if (mailobj->buflen == 0 && mailobj->current == MAILPART_HEADER) {
646       char* s;
647       //generate header part
648       char** p = &mailobj->buf;
649       mailobj->buf = NULL;
650       str_append(p, "User-Agent: libquickmail v" LIBQUICKMAIL_VERSION NEWLINE);
651       if (mailobj->timestamp != 0) {
652         char timestamptext[32];
653         if (strftime(timestamptext, sizeof(timestamptext), "%a, %d %b %Y %H:%M:%S %z", localtime(&mailobj->timestamp))) {\r
654           str_append(p, "Date: ");
655           str_append(p, timestamptext);
656           str_append(p, NEWLINE);
657         }
658 #ifdef _WIN32\r
659         //fallback method for Windows when %z (time zone offset) fails
660         else if (strftime(timestamptext, sizeof(timestamptext), "%a, %d %b %Y %H:%M:%S", localtime(&mailobj->timestamp))) {\r
661           TIME_ZONE_INFORMATION tzinfo;\r
662           if (GetTimeZoneInformation(&tzinfo) != TIME_ZONE_ID_INVALID)\r
663             sprintf(timestamptext + strlen(timestamptext), " %c%02i%02i", (tzinfo.Bias > 0 ? '-' : '+'), (int)-tzinfo.Bias / 60, (int)-tzinfo.Bias % 60);\r
664           str_append(p, "Date: ");
665           str_append(p, timestamptext);
666           str_append(p, NEWLINE);
667         }
668 #endif\r
669       }
670       if (mailobj->from && *mailobj->from) {
671         str_append(p, "From: <");
672         str_append(p, mailobj->from);
673         str_append(p, ">" NEWLINE);
674       }
675       if ((s = email_info_string_list_concatenate(mailobj->to)) != NULL) {
676         str_append(p, "To: ");
677         str_append(p, s);
678         str_append(p, NEWLINE);
679         free(s);
680       }
681       if ((s = email_info_string_list_concatenate(mailobj->cc)) != NULL) {
682         str_append(p, "Cc: ");
683         str_append(p, s);
684         str_append(p, NEWLINE);
685         free(s);
686       }
687       if (mailobj->subject) {
688         str_append(p, "Subject: ");
689         str_append(p, mailobj->subject);
690         str_append(p, NEWLINE);
691       }
692       if (mailobj->header) {
693         str_append(p, mailobj->header);
694       }
695       if (mailobj->attachmentlist) {
696         str_append(p, "MIME-Version: 1.0" NEWLINE);
697       }
698       if (mailobj->attachmentlist) {
699         mailobj->mime_boundary_part = randomize_zeros(strdup("=PART=SEPARATOR=_0000_0000_0000_0000_0000_0000_="));
700         str_append(p, "Content-Type: multipart/mixed; boundary=\"");
701         str_append(p, mailobj->mime_boundary_part);
702         str_append(p, "\"" NEWLINE NEWLINE "This is a multipart message in MIME format." NEWLINE NEWLINE "--");
703         str_append(p, mailobj->mime_boundary_part);
704         str_append(p, NEWLINE);
705       }
706       if (mailobj->bodylist && mailobj->bodylist->next) {
707         mailobj->mime_boundary_body = randomize_zeros(strdup("=BODY=SEPARATOR=_0000_0000_0000_0000_0000_0000_="));
708         str_append(p, "Content-Type: multipart/alternative; boundary=\"");
709         str_append(p, mailobj->mime_boundary_body);
710         str_append(p, NEWLINE);
711       }
712       mailobj->buflen = strlen(mailobj->buf);
713       mailobj->current++;
714     }
715     if (mailobj->buflen == 0 && mailobj->current == MAILPART_BODY) {
716       if (mailobj->current_attachment) {
717         if (!mailobj->current_attachment->handle) {
718           //open file with body data
719           while (mailobj->current_attachment) {
720             if ((mailobj->current_attachment->handle = mailobj->current_attachment->email_info_attachment_open(mailobj->current_attachment->filedata)) != NULL) {
721               break;
722             }
723             mailobj->current_attachment = mailobj->current_attachment->next;
724           }
725           if (!mailobj->current_attachment) {
726             mailobj->current_attachment = mailobj->attachmentlist;
727             mailobj->current++;
728           }
729           //generate attachment header
730           if (mailobj->current_attachment && mailobj->current_attachment->handle) {
731             mailobj->buf = NULL;
732             if (mailobj->mime_boundary_body) {
733               mailobj->buf = str_append(&mailobj->buf, NEWLINE "--");
734               mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_body);
735               mailobj->buf = str_append(&mailobj->buf, NEWLINE);
736             }
737             mailobj->buf = str_append(&mailobj->buf, "Content-Type: ");
738             mailobj->buf = str_append(&mailobj->buf, (mailobj->bodylist && mailobj->current_attachment->filename ? mailobj->current_attachment->filename : default_mime_type));
739             mailobj->buf = str_append(&mailobj->buf, NEWLINE "Content-Transfer-Encoding: 8bit" NEWLINE "Content-Disposition: inline" NEWLINE NEWLINE);
740             mailobj->buflen = strlen(mailobj->buf);
741           }
742         }
743         if (mailobj->buflen == 0 && mailobj->current_attachment && mailobj->current_attachment->handle) {
744           //read body data
745           if ((mailobj->buf = malloc(BODY_BUFFER_SIZE)) == NULL) {
746             DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
747           }
748           if (mailobj->buf == NULL || (mailobj->buflen = mailobj->current_attachment->email_info_attachment_read(mailobj->current_attachment->handle, mailobj->buf, BODY_BUFFER_SIZE)) <= 0) {
749             //end of file
750             free(mailobj->buf);
751             mailobj->buflen = 0;
752             if (mailobj->current_attachment->email_info_attachment_close)
753               mailobj->current_attachment->email_info_attachment_close(mailobj->current_attachment->handle);
754             //else
755             //  free(mailobj->current_attachment->handle);
756             mailobj->current_attachment->handle = NULL;
757             mailobj->current_attachment = mailobj->current_attachment->next;
758           }
759         }
760       } else {
761         mailobj->current_attachment = mailobj->attachmentlist;
762         mailobj->current++;
763       }
764     }
765     if (mailobj->buflen == 0 && mailobj->current == MAILPART_BODY_DONE) {
766       mailobj->buf = NULL;
767       if (mailobj->mime_boundary_body) {
768         mailobj->buf = str_append(&mailobj->buf, NEWLINE "--");
769         mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_body);
770         mailobj->buf = str_append(&mailobj->buf, "--" NEWLINE);
771         mailobj->buflen = strlen(mailobj->buf);
772         free(mailobj->mime_boundary_body);
773         mailobj->mime_boundary_body = NULL;
774       }
775       mailobj->current++;
776     }
777     if (mailobj->buflen == 0 && mailobj->current == MAILPART_ATTACHMENT) {
778       if (mailobj->current_attachment) {
779         if (!mailobj->current_attachment->handle) {
780           //open file to attach
781           while (mailobj->current_attachment) {
782             if ((mailobj->current_attachment->handle = mailobj->current_attachment->email_info_attachment_open(mailobj->current_attachment->filedata)) != NULL) {
783               break;
784             }
785             mailobj->current_attachment = mailobj->current_attachment->next;
786           }
787           //generate attachment header
788           if (mailobj->current_attachment && mailobj->current_attachment->handle) {
789             mailobj->buf = NULL;
790             if (mailobj->mime_boundary_part) {
791               mailobj->buf = str_append(&mailobj->buf, NEWLINE "--");
792               mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_part);
793               mailobj->buf = str_append(&mailobj->buf, NEWLINE);
794             }
795             mailobj->buf = str_append(&mailobj->buf, "Content-Type: ");
796             mailobj->buf = str_append(&mailobj->buf, (mailobj->current_attachment->mimetype ? mailobj->current_attachment->mimetype : "application/octet-stream"));
797             mailobj->buf = str_append(&mailobj->buf, "; Name=\"");
798             mailobj->buf = str_append(&mailobj->buf, mailobj->current_attachment->filename);
799             mailobj->buf = str_append(&mailobj->buf, "\"" NEWLINE "Content-Transfer-Encoding: base64" NEWLINE NEWLINE);
800             mailobj->buflen = strlen(mailobj->buf);
801           }
802         } else {
803           //generate next line of attachment data
804           size_t n = 0;
805           int mimelinepos = 0;
806           unsigned char igroup[3] = {0, 0, 0};
807           unsigned char ogroup[4];
808           mailobj->buflen = 0;
809           if ((mailobj->buf = (char*)malloc(MIME_LINE_WIDTH + NEWLINELENGTH + 1)) == NULL) {
810             DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
811             n = 0;
812           } else {
813             while (mimelinepos < MIME_LINE_WIDTH && (n = mailobj->current_attachment->email_info_attachment_read(mailobj->current_attachment->handle, igroup, 3)) > 0) {
814               //code data
815               ogroup[0] = mailobj->dtable[igroup[0] >> 2];
816               ogroup[1] = mailobj->dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
817               ogroup[2] = mailobj->dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
818               ogroup[3] = mailobj->dtable[igroup[2] & 0x3F];
819               //padd with "=" characters if less than 3 characters were read
820               if (n < 3) {
821                 ogroup[3] = '=';
822                 if (n < 2)
823                   ogroup[2] = '=';
824               }
825               memcpy(mailobj->buf + mimelinepos, ogroup, 4);
826               mailobj->buflen += 4;
827               mimelinepos += 4;
828             }
829             if (mimelinepos > 0) {
830               memcpy(mailobj->buf + mimelinepos, NEWLINE, NEWLINELENGTH);
831               mailobj->buflen += NEWLINELENGTH;
832             }
833           }
834           if (n <= 0) {
835             //end of file
836             if (mailobj->current_attachment->email_info_attachment_close)
837               mailobj->current_attachment->email_info_attachment_close(mailobj->current_attachment->handle);
838             else
839               free(mailobj->current_attachment->handle);
840             mailobj->current_attachment->handle = NULL;
841             mailobj->current_attachment = mailobj->current_attachment->next;
842           }
843         }
844       } else {
845         mailobj->current++;
846       }
847     }
848     if (mailobj->buflen == 0 && mailobj->current == MAILPART_END) {
849       mailobj->buf = NULL;
850       mailobj->buflen = 0;
851       if (mailobj->mime_boundary_part) {
852         mailobj->buf = str_append(&mailobj->buf, NEWLINE "--");
853         mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_part);
854         mailobj->buf = str_append(&mailobj->buf, "--" NEWLINE);
855         mailobj->buflen = strlen(mailobj->buf);
856         free(mailobj->mime_boundary_part);
857         mailobj->mime_boundary_part = NULL;
858       }
859       //mailobj->buf = str_append(&mailobj->buf, NEWLINE "." NEWLINE);
860       //mailobj->buflen = strlen(mailobj->buf);
861       mailobj->current++;
862     }
863     if (mailobj->buflen == 0 && mailobj->current == MAILPART_DONE) {
864       break;
865     }
866   }
867
868   //flush pending data if any
869   if (mailobj->buflen > 0) {
870     int len = (mailobj->buflen > size * nmemb ? size * nmemb : mailobj->buflen);
871     memcpy(ptr, mailobj->buf, len);
872     if (len < mailobj->buflen) {
873       mailobj->buf = memmove(mailobj->buf, mailobj->buf + len, mailobj->buflen - len);
874       mailobj->buflen -= len;
875     } else {
876       free(mailobj->buf);
877       mailobj->buf = NULL;
878       mailobj->buflen = 0;
879     }
880     return len;
881   }
882
883   //if (mailobj->current != MAILPART_DONE)
884   //  ;//this should never be reached
885   mailobj->current = 0;
886   return 0;
887 }
888
889 #ifndef NOCURL
890 char* add_angle_brackets (const char* data)
891 {
892   size_t datalen = strlen(data);
893   char* result;
894   if ((result = (char*)malloc(datalen + 3)) == NULL) {
895     DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
896     return NULL;
897   }
898   result[0] = '<';
899   memcpy(result + 1, data, datalen);
900   result[datalen + 1] = '>';
901   result[datalen + 2] = 0;
902   return result;
903 }
904 #endif
905
906 DLL_EXPORT_LIBQUICKMAIL const char* quickmail_send (quickmail mailobj, const char* smtpserver, unsigned int smtpport, const char* username, const char* password)
907 {
908 #ifndef NOCURL
909   //libcurl based sending
910   CURL *curl;
911   CURLcode result = CURLE_FAILED_INIT;
912   //curl_global_init(CURL_GLOBAL_ALL);
913   if ((curl = curl_easy_init()) != NULL) {
914     struct curl_slist *recipients = NULL;
915     struct email_info_email_list_struct* listentry;
916     //set destination URL
917     char* addr;
918     size_t len = strlen(smtpserver) + 14;
919     if ((addr = (char*)malloc(len)) == NULL) {
920       DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
921       return ERRMSG_MEMORY_ALLOCATION_ERROR;
922     }
923     snprintf(addr, len, "smtp://%s:%u", smtpserver, smtpport);
924     curl_easy_setopt(curl, CURLOPT_URL, addr);
925     free(addr);
926     //try Transport Layer Security (TLS), but continue anyway if it fails
927     curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY);
928     //don't fail if the TLS/SSL a certificate could not be verified
929     //alternative: add the issuer certificate (or the host certificate if
930     //the certificate is self-signed) to the set of certificates that are
931     //known to libcurl using CURLOPT_CAINFO and/or CURLOPT_CAPATH
932     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
933     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
934     //set authentication credentials if provided
935     if (username && *username)
936       curl_easy_setopt(curl, CURLOPT_USERNAME, username);
937     if (password)
938       curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
939     //set from value for envelope reverse-path
940     if (mailobj->from && *mailobj->from) {
941       addr = add_angle_brackets(mailobj->from);
942       curl_easy_setopt(curl, CURLOPT_MAIL_FROM, addr);
943       free(addr);
944     }
945     //set recipients
946     listentry = mailobj->to;
947     while (listentry) {
948       if (listentry->data && *listentry->data) {
949         addr = add_angle_brackets(listentry->data);
950         recipients = curl_slist_append(recipients, addr);
951         free(addr);
952       }
953       listentry = listentry->next;
954     }
955     listentry = mailobj->cc;
956     while (listentry) {
957       if (listentry->data && *listentry->data) {
958         addr = add_angle_brackets(listentry->data);
959         recipients = curl_slist_append(recipients, addr);
960         free(addr);
961       }
962       listentry = listentry->next;
963     }
964     listentry = mailobj->bcc;
965     while (listentry) {
966       if (listentry->data && *listentry->data) {
967         addr = add_angle_brackets(listentry->data);
968         recipients = curl_slist_append(recipients, addr);
969         free(addr);
970       }
971       listentry = listentry->next;
972     }
973     curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
974     //set callback function for getting message body
975     curl_easy_setopt(curl, CURLOPT_READFUNCTION, quickmail_get_data);
976     curl_easy_setopt(curl, CURLOPT_READDATA, mailobj);
977     //enable debugging if requested
978     if (mailobj->debuglog) {
979       curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
980       curl_easy_setopt(curl, CURLOPT_STDERR, mailobj->debuglog);
981     }
982     //send the message
983     result = curl_easy_perform(curl);
984     //free the list of recipients and clean up
985     curl_slist_free_all(recipients);
986     curl_easy_cleanup(curl);
987   }
988   return (result == CURLE_OK ? NULL : curl_easy_strerror(result));
989 #else
990   //minimal implementation without libcurl
991   SOCKET sock;
992   char* errmsg = NULL;
993   struct email_info_email_list_struct* listentry;
994   char local_hostname[64];
995   int statuscode;
996   //determine local host name
997   if (gethostname(local_hostname, sizeof(local_hostname)) != 0)
998                 strcpy(local_hostname, "localhost");
999   //connect
1000   if ((sock = socket_open(smtpserver, smtpport, &errmsg)) != INVALID_SOCKET) {
1001     //talk with SMTP server
1002     if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, NULL)) >= 400) {
1003       errmsg = "SMTP server returned an error on connection";
1004     } else {
1005       size_t n;
1006       char buf[WRITE_BUFFER_CHUNK_SIZE];
1007       do {
1008         if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "EHLO %s", local_hostname)) >= 400) {
1009           if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "HELO %s", local_hostname)) >= 400) {
1010             errmsg = "SMTP EHLO/HELO returned error";
1011             break;
1012           }
1013         }
1014         //authenticate if needed
1015         if (username || password) {
1016           int len;
1017           int inpos = 0;
1018           int outpos = 0;
1019           size_t usernamelen = (username ? strlen(username) : 0);
1020           size_t passwordlen = (password ? strlen(password) : 0);
1021           char* auth;
1022           char* base64auth;
1023           if ((auth = (char*)malloc(usernamelen + passwordlen + 4)) == NULL) {
1024             DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
1025             return ERRMSG_MEMORY_ALLOCATION_ERROR;
1026           }
1027           if ((base64auth = (char*)malloc(((usernamelen + passwordlen + 2) + 2) / 3 * 4 + 1)) == NULL) {
1028             DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR)
1029             return ERRMSG_MEMORY_ALLOCATION_ERROR;
1030           }
1031           //leave the authorization identity empty to indicate it's the same the as authentication identity
1032           auth[0] = 0;
1033           len = 1;
1034           //set the authentication identity
1035           memcpy(auth + len, (username ? username : ""), usernamelen + 1);
1036           len += usernamelen + 1;
1037           //set the password
1038           memcpy(auth + len, (password ? password : ""), passwordlen + 1);
1039           len += passwordlen;
1040           //padd with extra zero so groups of 3 bytes can be read
1041           auth[usernamelen + len + 1] = 0;
1042           //encode in base64
1043           while (inpos < len) {
1044             //encode data
1045             base64auth[outpos + 0] = mailobj->dtable[auth[inpos + 0] >> 2];
1046             base64auth[outpos + 1] = mailobj->dtable[((auth[inpos + 0] & 3) << 4) | (auth[inpos + 1] >> 4)];
1047             base64auth[outpos + 2] = mailobj->dtable[((auth[inpos + 1] & 0xF) << 2) | (auth[inpos + 2] >> 6)];
1048             base64auth[outpos + 3] = mailobj->dtable[auth[inpos + 2] & 0x3F];
1049             //padd with "=" characters if less than 3 characters were read
1050             if (inpos + 1 >= len) {
1051               base64auth[outpos + 3] = '=';
1052               if (inpos + 2 >= len)
1053                 base64auth[outpos + 2] = '=';
1054             }
1055             //advance to next position
1056             inpos += 3;
1057             outpos += 4;
1058           }
1059           base64auth[outpos] = 0;
1060           //send originator e-mail address
1061           if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "AUTH PLAIN %s", base64auth)) >= 400) {
1062             errmsg = "SMTP authentication failed";
1063             break;
1064           }
1065         }
1066         //send originator e-mail address
1067         if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "MAIL FROM:<%s>", mailobj->from)) >= 400) {
1068           errmsg = "SMTP server did not accept sender";
1069           break;
1070         }
1071         //send recipient e-mail addresses
1072         listentry = mailobj->to;
1073         while (!errmsg && listentry) {
1074           if (listentry->data && *listentry->data) {
1075             if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "RCPT TO:<%s>", listentry->data)) >= 400)
1076               errmsg = "SMTP server did not accept e-mail address (To)";
1077           }
1078           listentry = listentry->next;
1079         }
1080         listentry = mailobj->cc;
1081         while (!errmsg && listentry) {
1082           if (listentry->data && *listentry->data) {
1083             if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "RCPT TO:<%s>", listentry->data)) >= 400)
1084               errmsg = "SMTP server did not accept e-mail address (CC)";
1085           }
1086           listentry = listentry->next;
1087         }
1088         listentry = mailobj->bcc;
1089         while (!errmsg && listentry) {
1090           if (listentry->data && *listentry->data) {
1091             if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "RCPT TO:<%s>", listentry->data)) >= 400)
1092               errmsg = "SMTP server did not accept e-mail address (BCC)";
1093           }
1094           listentry = listentry->next;
1095         }
1096         if (errmsg)
1097           break;
1098         //prepare to send mail body
1099         if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "DATA")) >= 400) {
1100           errmsg = "SMTP DATA returned error";
1101           break;
1102         }
1103         //send mail body data
1104         while ((n = quickmail_get_data(buf, sizeof(buf), 1, mailobj)) > 0) {
1105           socket_send(sock, buf, n);
1106         }
1107         //send end of data
1108         if ((statuscode = socket_smtp_command(sock, mailobj->debuglog, "\r\n.")) >= 400) {
1109           errmsg = "SMTP error after sending message data";
1110           break;
1111         }
1112       } while (0);
1113       //log out
1114       socket_smtp_command(sock, mailobj->debuglog, "QUIT");
1115     }
1116   }
1117   //close socket
1118   socket_close(sock);
1119   return errmsg;
1120 #endif
1121 }