1 /*
2  * libEtPan! -- a mail stuff library
3  *
4  * Copyright (C) 2001, 2005 - DINH Viet Hoa
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the libEtPan! project nor the names of its
16  *    contributors may be used to endorse or promote products derived
17  *    from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 /*
33  * $Id: feeddriver.c,v 1.3 2008/04/11 07:33:08 hoa Exp $
34  */
35 
36 #ifdef HAVE_CONFIG_H
37 #	include <config.h>
38 #endif
39 
40 #include "feeddriver.h"
41 
42 #include <string.h>
43 #include <stdlib.h>
44 #include <time.h>
45 
46 #include "mailimf_types_helper.h"
47 #include "newsfeed.h"
48 #include "mail.h"
49 #include "mailmessage.h"
50 #include "maildriver_tools.h"
51 #include "feeddriver_message.h"
52 #include "feeddriver_types.h"
53 
54 #define MIN_DELAY 5
55 
56 static int feeddriver_initialize(mailsession * session);
57 
58 static void feeddriver_uninitialize(mailsession * session);
59 
60 static int feeddriver_connect_path(mailsession * session, const char * path);
61 
62 static int feeddriver_status_folder(mailsession * session, const char * mb,
63     uint32_t * result_messages,
64     uint32_t * result_recent,
65     uint32_t * result_unseen);
66 
67 static int feeddriver_messages_number(mailsession * session, const char * mb,
68 				      uint32_t * result);
69 
70 static int
71 feeddriver_get_envelopes_list(mailsession * session,
72 			      struct mailmessage_list * env_list);
73 
74 
75 static int feeddriver_get_messages_list(mailsession * session,
76 					struct mailmessage_list ** result);
77 
78 static int feeddriver_get_message(mailsession * session,
79 				  uint32_t num, mailmessage ** result);
80 
81 static int feeddriver_get_message_by_uid(mailsession * session,
82     const char * uid,
83     mailmessage ** result);
84 
85 static mailsession_driver local_feed_session_driver = {
86   /* sess_name */ "feed",
87 
88   /* sess_initialize */ feeddriver_initialize,
89   /* sess_uninitialize */ feeddriver_uninitialize,
90 
91   /* sess_parameters */ NULL,
92 
93   /* sess_connect_stream */ NULL,
94   /* sess_connect_path */ feeddriver_connect_path,
95   /* sess_starttls */ NULL,
96   /* sess_login */ NULL,
97   /* sess_logout */ NULL,
98   /* sess_noop */ NULL,
99 
100   /* sess_build_folder_name */ NULL,
101   /* sess_create_folder */ NULL,
102   /* sess_delete_folder */ NULL,
103   /* sess_rename_folder */ NULL,
104   /* sess_check_folder */ NULL,
105   /* sess_examine_folder */ NULL,
106   /* sess_select_folder */ NULL,
107   /* sess_expunge_folder */ NULL,
108   /* sess_status_folder */ feeddriver_status_folder,
109   /* sess_messages_number */ feeddriver_messages_number,
110   /* sess_recent_number */ feeddriver_messages_number,
111   /* sess_unseen_number */ feeddriver_messages_number,
112   /* sess_list_folders */ NULL,
113   /* sess_lsub_folders */ NULL,
114   /* sess_subscribe_folder */ NULL,
115   /* sess_unsubscribe_folder */ NULL,
116 
117   /* sess_append_message */ NULL,
118   /* sess_append_message_flags */ NULL,
119   /* sess_copy_message */ NULL,
120   /* sess_move_message */ NULL,
121 
122   /* sess_get_message */ feeddriver_get_message,
123   /* sess_get_message_by_uid */ feeddriver_get_message_by_uid,
124 
125   /* sess_get_messages_list */ feeddriver_get_messages_list,
126   /* sess_get_envelopes_list */ feeddriver_get_envelopes_list,
127   /* sess_remove_message */ NULL,
128 
129   /* sess_login_sasl */ NULL,
130 };
131 
132 
133 mailsession_driver * feed_session_driver = &local_feed_session_driver;
134 
135 static void update(mailsession * session);
136 
feeddriver_feed_error_to_mail_error(int error)137 static int feeddriver_feed_error_to_mail_error(int error)
138 {
139   switch (error) {
140   case NEWSFEED_NO_ERROR:
141     return MAIL_NO_ERROR;
142 
143   case NEWSFEED_ERROR_CANCELLED:
144     return MAIL_ERROR_STREAM;
145 
146   case NEWSFEED_ERROR_INTERNAL:
147     return MAIL_ERROR_UNKNOWN;
148 
149   case NEWSFEED_ERROR_BADURL:
150     return MAIL_ERROR_INVAL;
151 
152   case NEWSFEED_ERROR_RESOLVE_PROXY:
153   case NEWSFEED_ERROR_RESOLVE_HOST:
154     return MAIL_ERROR_CONNECT;
155 
156   case NEWSFEED_ERROR_CONNECT:
157     return MAIL_ERROR_CONNECT;
158 
159   case NEWSFEED_ERROR_STREAM:
160     return MAIL_ERROR_STREAM;
161 
162   case NEWSFEED_ERROR_PROTOCOL:
163   case NEWSFEED_ERROR_PARSE:
164     return MAIL_ERROR_PARSE;
165 
166   case NEWSFEED_ERROR_ACCESS:
167     return MAIL_ERROR_NO_PERMISSION;
168 
169   case NEWSFEED_ERROR_AUTHENTICATION:
170     return MAIL_ERROR_LOGIN;
171 
172   case NEWSFEED_ERROR_FTP:
173     return MAIL_ERROR_UNKNOWN;
174 
175   case NEWSFEED_ERROR_PARTIAL_FILE:
176   case NEWSFEED_ERROR_FETCH:
177     return MAIL_ERROR_FETCH;
178 
179   case NEWSFEED_ERROR_HTTP:
180     return MAIL_ERROR_UNKNOWN;
181 
182   case NEWSFEED_ERROR_FILE:
183     return MAIL_ERROR_FILE;
184 
185   case NEWSFEED_ERROR_PUT:
186     return MAIL_ERROR_APPEND;
187 
188   case NEWSFEED_ERROR_MEMORY:
189     return MAIL_ERROR_MEMORY;
190 
191   case NEWSFEED_ERROR_SSL:
192     return MAIL_ERROR_SSL;
193 
194   case NEWSFEED_ERROR_LDAP:
195     return MAIL_ERROR_UNKNOWN;
196 
197   case NEWSFEED_ERROR_UNSUPPORTED_PROTOCOL:
198     return MAIL_ERROR_INVAL;
199   }
200 
201   return MAIL_ERROR_UNKNOWN;
202 }
203 
204 static inline struct feed_session_state_data *
get_data(mailsession * session)205 get_data(mailsession * session)
206 {
207   return session->sess_data;
208 }
209 
get_feed_session(mailsession * session)210 static inline struct newsfeed * get_feed_session(mailsession * session)
211 {
212   return get_data(session)->feed_session;
213 }
214 
feeddriver_initialize(mailsession * session)215 static int feeddriver_initialize(mailsession * session)
216 {
217   struct feed_session_state_data * data;
218   struct newsfeed * feed;
219 
220   feed = newsfeed_new();
221   if (feed == NULL)
222     goto err;
223 
224   data = malloc(sizeof(* data));
225   if (data == NULL)
226     goto free;
227 
228   data->feed_session = feed;
229   data->feed_error = MAIL_NO_ERROR;
230   session->sess_data = data;
231 
232   return MAIL_NO_ERROR;
233 
234  free:
235   newsfeed_free(feed);
236  err:
237   return MAIL_ERROR_MEMORY;
238 }
239 
feeddriver_uninitialize(mailsession * session)240 static void feeddriver_uninitialize(mailsession * session)
241 {
242   struct feed_session_state_data * data;
243 
244   data = get_data(session);
245 
246   newsfeed_free(data->feed_session);
247   free(data);
248 
249   session->sess_data = NULL;
250 }
251 
feeddriver_connect_path(mailsession * session,const char * path)252 static int feeddriver_connect_path(mailsession * session, const char * path)
253 {
254   struct feed_session_state_data * data;
255   int r;
256 
257   data = get_data(session);
258   r = newsfeed_set_url(data->feed_session, path);
259   return feeddriver_feed_error_to_mail_error(r);
260 }
261 
feeddriver_status_folder(mailsession * session,const char * mb,uint32_t * result_messages,uint32_t * result_recent,uint32_t * result_unseen)262 static int feeddriver_status_folder(mailsession * session, const char * mb,
263     uint32_t * result_messages,
264     uint32_t * result_recent,
265     uint32_t * result_unseen)
266 {
267   uint32_t count;
268   int r;
269 
270   r = feeddriver_messages_number(session, mb, &count);
271   if (r != MAIL_NO_ERROR)
272     return r;
273 
274   * result_messages = count;
275   * result_recent = count;
276   * result_unseen = count;
277 
278   return MAIL_NO_ERROR;
279 }
280 
feeddriver_messages_number(mailsession * session,const char * mb,uint32_t * result)281 static int feeddriver_messages_number(mailsession * session, const char * mb,
282     uint32_t * result)
283 {
284   struct feed_session_state_data * data;
285   unsigned int count;
286   int res;
287 
288   update(session);
289   data = get_data(session);
290   if (data->feed_error != MAIL_NO_ERROR) {
291     res = data->feed_error;
292     goto err;
293   }
294 
295   count = newsfeed_item_list_get_count(data->feed_session);
296 
297   * result = count;
298 
299   return MAIL_NO_ERROR;
300 
301  err:
302   return res;
303 }
304 
update(mailsession * session)305 static void update(mailsession * session)
306 {
307   int r;
308   struct feed_session_state_data * data;
309   time_t value;
310 
311   data = get_data(session);
312 
313   value = time(NULL);
314   if (data->feed_last_update != (time_t) -1) {
315     if (value - data->feed_last_update < MIN_DELAY)
316       return;
317   }
318 
319   r = newsfeed_update(data->feed_session, -1);
320   data->feed_error = feeddriver_feed_error_to_mail_error(r);
321   if (data->feed_error == MAIL_NO_ERROR) {
322     value = time(NULL);
323     data->feed_last_update = value;
324   }
325 }
326 
327 static int
feeddriver_get_envelopes_list(mailsession * session,struct mailmessage_list * env_list)328 feeddriver_get_envelopes_list(mailsession * session,
329 			      struct mailmessage_list * env_list)
330 {
331   return MAIL_NO_ERROR;
332 }
333 
to_be_quoted(const char * word,size_t size)334 static inline int to_be_quoted(const char * word, size_t size)
335 {
336   int do_quote;
337   const char * cur;
338   size_t i;
339 
340   do_quote = 0;
341   cur = word;
342   for(i = 0 ; i < size ; i ++) {
343     switch (* cur) {
344     case ',':
345     case ':':
346     case '!':
347     case '"':
348     case '#':
349     case '$':
350     case '@':
351     case '[':
352     case '\\':
353     case ']':
354     case '^':
355     case '`':
356     case '{':
357     case '|':
358     case '}':
359     case '~':
360     case '=':
361     case '?':
362     case '_':
363       do_quote = 1;
364       break;
365     default:
366       if (((unsigned char) * cur) >= 128)
367         do_quote = 1;
368       break;
369     }
370     cur ++;
371   }
372 
373   return do_quote;
374 }
375 
376 #define MAX_IMF_LINE 72
377 
quote_word(const char * display_charset,MMAPString * mmapstr,const char * word,size_t size)378 static inline int quote_word(const char * display_charset,
379     MMAPString * mmapstr, const char * word, size_t size)
380 {
381   const char * cur;
382   size_t i;
383   char hex[4];
384   int col;
385 
386   if (mmap_string_append(mmapstr, "=?") == NULL)
387     return -1;
388   if (mmap_string_append(mmapstr, display_charset) == NULL)
389     return -1;
390   if (mmap_string_append(mmapstr, "?Q?") == NULL)
391     return -1;
392 
393   col = (int) mmapstr->len;
394 
395   cur = word;
396   for(i = 0 ; i < size ; i ++) {
397     int do_quote_char;
398 
399     if (col + 2 /* size of "?=" */
400         + 3 /* max size of newly added character */
401         + 1 /* minimum column of string in a
402                folded header */ >= MAX_IMF_LINE) {
403       int old_pos;
404       /* adds a concatened encoded word */
405 
406       if (mmap_string_append(mmapstr, "?=") == NULL)
407         return -1;
408 
409       if (mmap_string_append(mmapstr, " ") == NULL)
410         return -1;
411 
412       old_pos = (int) mmapstr->len;
413 
414       if (mmap_string_append(mmapstr, "=?") == NULL)
415         return -1;
416       if (mmap_string_append(mmapstr, display_charset) == NULL)
417         return -1;
418       if (mmap_string_append(mmapstr, "?Q?") == NULL)
419         return -1;
420 
421       col = (int) mmapstr->len - old_pos;
422     }
423 
424     do_quote_char = 0;
425     switch (* cur) {
426     case ',':
427     case ':':
428     case '!':
429     case '"':
430     case '#':
431     case '$':
432     case '@':
433     case '[':
434     case '\\':
435     case ']':
436     case '^':
437     case '`':
438     case '{':
439     case '|':
440     case '}':
441     case '~':
442     case '=':
443     case '?':
444     case '_':
445       do_quote_char = 1;
446       break;
447 
448     default:
449       if (((unsigned char) * cur) >= 128)
450         do_quote_char = 1;
451       break;
452     }
453 
454     if (do_quote_char) {
455       snprintf(hex, 4, "=%2.2X", (unsigned char) * cur);
456       if (mmap_string_append(mmapstr, hex) == NULL)
457         return -1;
458       col += 3;
459     }
460     else {
461       if (* cur == ' ') {
462         if (mmap_string_append_c(mmapstr, '_') == NULL)
463           return -1;
464       }
465       else {
466         if (mmap_string_append_c(mmapstr, * cur) == NULL)
467           return -1;
468       }
469       col += 3;
470     }
471     cur ++;
472   }
473 
474   if (mmap_string_append(mmapstr, "?=") == NULL)
475     return -1;
476 
477   return 0;
478 }
479 
get_word(const char * begin,const char ** pend,int * pto_be_quoted)480 static inline void get_word(const char * begin,
481     const char ** pend, int * pto_be_quoted)
482 {
483   const char * cur;
484 
485   cur = begin;
486 
487   while ((* cur != ' ') && (* cur != '\t') && (* cur != '\0')) {
488     cur ++;
489   }
490 
491   if (cur - begin +
492       1  /* minimum column of string in a
493             folded header */ > MAX_IMF_LINE)
494     * pto_be_quoted = 1;
495   else
496     * pto_be_quoted = to_be_quoted(begin, cur - begin);
497 
498   * pend = cur;
499 }
500 
make_quoted_printable(const char * display_charset,const char * phrase)501 static char * make_quoted_printable(const char * display_charset,
502     const char * phrase)
503 {
504   char * str;
505   const char * cur;
506   MMAPString * mmapstr;
507   int r;
508 
509   mmapstr = mmap_string_new("");
510   if (mmapstr == NULL)
511     return NULL;
512 
513   cur = phrase;
514   while (* cur != '\0') {
515     const char * begin;
516     const char * end;
517     int do_quote;
518     int quote_words;
519 
520     begin = cur;
521     end = begin;
522     quote_words = 0;
523     do_quote = 1;
524 
525     while (* cur != '\0') {
526       get_word(cur, &cur, &do_quote);
527       if (do_quote) {
528         quote_words = 1;
529         end = cur;
530       }
531       else
532         break;
533       if (* cur != '\0')
534         cur ++;
535     }
536 
537     if (quote_words) {
538       r = quote_word(display_charset, mmapstr, begin, end - begin);
539       if (r < 0) {
540         mmap_string_free(mmapstr);
541         return NULL;
542       }
543 
544       if ((* end == ' ') || (* end == '\t')) {
545         if (mmap_string_append_c(mmapstr, * end) == NULL) {
546           mmap_string_free(mmapstr);
547           return NULL;
548         }
549         end ++;
550       }
551 
552       if (* end != '\0') {
553         if (mmap_string_append_len(mmapstr, end, cur - end) == NULL) {
554           mmap_string_free(mmapstr);
555           return NULL;
556         }
557       }
558     }
559     else {
560       if (mmap_string_append_len(mmapstr, begin, cur - begin) == NULL) {
561         mmap_string_free(mmapstr);
562         return NULL;
563       }
564     }
565 
566     if ((* cur == ' ') || (* cur == '\t')) {
567       if (mmap_string_append_c(mmapstr, * cur) == 0) {
568         mmap_string_free(mmapstr);
569         return NULL;
570       }
571       cur ++;
572     }
573   }
574 
575   str = strdup(mmapstr->str);
576   if (str == NULL) {
577     mmap_string_free(mmapstr);
578     return NULL;
579   }
580 
581   mmap_string_free(mmapstr);
582 
583   return str;
584 }
585 
feed_item_to_message(mailsession * session,unsigned int num,struct newsfeed_item * item)586 static mailmessage * feed_item_to_message(mailsession * session,
587     unsigned int num,
588     struct newsfeed_item * item)
589 {
590   struct mailimf_fields * fields;
591   struct mailimf_date_time * date_time;
592   time_t time_modified;
593   struct mailimf_mailbox_list * from;
594   mailmessage * msg;
595   char * subject;
596   const char * subject_const;
597   char * msg_id;
598   int r;
599   const char * author_const;
600 
601   from = NULL;
602   author_const = newsfeed_item_get_author(item);
603   if (author_const != NULL) {
604     char * author;
605     char * addr_spec;
606     struct mailimf_mailbox * mb;
607 
608     author = strdup(author_const);
609     if (author == NULL) {
610       goto err;
611     }
612 
613     from = mailimf_mailbox_list_new_empty();
614     if (from == NULL) {
615       free(author);
616       goto err;
617     }
618     addr_spec = strdup("invalid@localhost.local");
619     if (addr_spec == NULL) {
620       free(author);
621       goto free_from;
622     }
623 
624     /* XXX - encode author with MIME */
625     mb = mailimf_mailbox_new(author, addr_spec);
626     if (mb == NULL) {
627       free(addr_spec);
628       free(author);
629       goto free_from;
630     }
631 
632     r = mailimf_mailbox_list_add(from, mb);
633     if (r != MAILIMF_NO_ERROR) {
634       mailimf_mailbox_free(mb);
635       goto free_from;
636     }
637   }
638 
639   date_time = NULL;
640   time_modified = newsfeed_item_get_date_modified(item);
641   if (time_modified != (time_t) -1) {
642     date_time = mailimf_get_date(time_modified);
643     if (date_time == NULL) {
644       goto free_from;
645     }
646   }
647 
648   subject = NULL;
649   subject_const = newsfeed_item_get_title(item);
650   if (subject_const != NULL) {
651     subject = make_quoted_printable("utf-8", subject_const);
652     if (subject == NULL) {
653       goto free_date;
654     }
655   }
656 
657   msg_id = mailimf_get_message_id();
658   if (msg_id == NULL) {
659     goto free_subject;
660   }
661 
662   fields = mailimf_fields_new_with_data_all(date_time,
663       from,
664       NULL,
665       NULL,
666       NULL,
667       NULL,
668       NULL,
669       msg_id,
670       NULL,
671       NULL,
672       subject);
673 
674   msg = mailmessage_new();
675   r = mailmessage_init(msg, session, feed_message_driver, num, 0);
676   if (r != MAIL_NO_ERROR) {
677     goto free_fields;
678   }
679   msg->msg_fields = fields;
680 
681   return msg;
682 
683  free_fields:
684   mailimf_fields_free(fields);
685   goto err;
686  free_subject:
687   free(subject);
688  free_date:
689   mailimf_date_time_free(date_time);
690  free_from:
691   mailimf_mailbox_list_free(from);
692  err:
693   return NULL;
694 }
695 
feeddriver_get_messages_list(mailsession * session,struct mailmessage_list ** result)696 static int feeddriver_get_messages_list(mailsession * session,
697     struct mailmessage_list ** result)
698 {
699   unsigned int i;
700   struct feed_session_state_data * data;
701   unsigned int count;
702   struct mailmessage_list * msg_list;
703   carray * tab;
704   int res;
705   int r;
706 
707   update(session);
708   data = get_data(session);
709   if (data->feed_error != MAIL_NO_ERROR) {
710     res = data->feed_error;
711     goto err;
712   }
713 
714   count = newsfeed_item_list_get_count(data->feed_session);
715 
716   tab = carray_new(count);
717   if (tab == NULL) {
718     res = MAIL_ERROR_MEMORY;
719     goto err;
720   }
721   fprintf(stderr, "count: %i\n", count);
722 
723   for(i = 0 ; i < count ; i ++) {
724     struct newsfeed_item * item;
725     mailmessage * msg;
726 
727     item = newsfeed_get_item(data->feed_session, i);
728     msg = feed_item_to_message(session, i, item);
729     r = carray_add(tab, msg, NULL);
730     if (r < 0) {
731       res = MAIL_ERROR_MEMORY;
732       goto free_tab;
733     }
734   }
735 
736   msg_list = mailmessage_list_new(tab);
737   if (msg_list == NULL) {
738     res = MAIL_ERROR_MEMORY;
739     goto free_tab;
740   }
741 
742   * result = msg_list;
743 
744   return MAIL_NO_ERROR;
745 
746  free_tab:
747   for(i = 0 ; i < carray_count(tab) ; i ++) {
748     mailmessage * msg;
749 
750     msg = carray_get(tab, i);
751     mailmessage_free(msg);
752   }
753  err:
754   return res;
755 }
756 
feeddriver_get_message(mailsession * session,uint32_t num,mailmessage ** result)757 static int feeddriver_get_message(mailsession * session,
758     uint32_t num, mailmessage ** result)
759 {
760   mailmessage * msg_info;
761   int r;
762 
763   msg_info = mailmessage_new();
764   if (msg_info == NULL)
765     return MAIL_ERROR_MEMORY;
766 
767   r = mailmessage_init(msg_info, session, feed_message_driver, num, 0);
768   if (r != MAIL_NO_ERROR) {
769     mailmessage_free(msg_info);
770     return r;
771   }
772 
773   * result = msg_info;
774 
775   return MAIL_NO_ERROR;
776 }
777 
feeddriver_get_message_by_uid(mailsession * session,const char * uid,mailmessage ** result)778 static int feeddriver_get_message_by_uid(mailsession * session,
779     const char * uid,
780     mailmessage ** result)
781 {
782 #if 0
783   uint32_t num;
784   char * p;
785 
786   if (uid == NULL)
787     return MAIL_ERROR_INVAL;
788 
789   num = strtoul(uid, &p, 10);
790   if ((p == uid) || (* p != '\0'))
791     return MAIL_ERROR_INVAL;
792 
793   return feeddriver_get_message(session, num, result);
794 #endif
795   return MAIL_ERROR_INVAL;
796  }
797