1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-2021 Free Software Foundation, Inc.
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 3 of the License, or (at your option) any later version.
8 
9    This library 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 GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General
15    Public License along with this library.  If not, see
16    <http://www.gnu.org/licenses/>. */
17 
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <fnmatch.h>
26 
27 #include <mailutils/auth.h>
28 #include <mailutils/debug.h>
29 #include <mailutils/iterator.h>
30 #include <mailutils/list.h>
31 #include <mailutils/monitor.h>
32 #include <mailutils/observer.h>
33 #include <mailutils/registrar.h>
34 #include <mailutils/url.h>
35 #include <mailutils/errno.h>
36 #include <mailutils/property.h>
37 #include <mailutils/mailbox.h>
38 #include <mailutils/imaputil.h>
39 #include <mailutils/util.h>
40 #include <mailutils/sys/folder.h>
41 
42 int
mu_folder_glob_match(const char * name,void * pattern,int flags)43 mu_folder_glob_match (const char *name, void *pattern, int flags)
44 {
45   return fnmatch (pattern, name[0] == '/' ? name + 1 : name, 0);
46 }
47 
48 int
mu_folder_imap_match(const char * name,void * pattern,int flags)49 mu_folder_imap_match (const char *name, void *pattern, int flags)
50 {
51   return mu_imap_wildmatch (pattern, name, '/');
52 }
53 
54 int
mu_folder_create_from_record(mu_folder_t * pfolder,mu_url_t url,mu_record_t record)55 mu_folder_create_from_record (mu_folder_t *pfolder, mu_url_t url,
56 			      mu_record_t record)
57 {
58   if (!pfolder)
59     return MU_ERR_OUT_PTR_NULL;
60 
61   if (record ||
62       /* Look in the registrar list(iterator), for a possible concrete mailbox
63 	 implementation that could match the URL.  */
64       mu_registrar_lookup_url (url, MU_FOLDER_ATTRIBUTE_DIRECTORY, &record,
65 			       NULL) == 0)
66     {
67       int (*f_init) (mu_folder_t) = NULL;
68 
69       mu_record_get_folder (record, &f_init);
70       if (f_init)
71         {
72 	  int status, mask;
73 	  mu_folder_t folder;
74 	  int (*u_init) (mu_url_t) = NULL;
75 
76 	  status = mu_record_check_url (record, url, &mask);
77 	  if (status)
78 	    /* FIXME: mask would provide more info */
79 	    return status;
80 
81 	  mu_record_get_url (record, &u_init);
82 	  if (u_init)
83 	    {
84 	      status = u_init (url);
85 	      if (status)
86 		return status;
87 	    }
88 
89 	  /* Create a new folder.  */
90 
91 	  /* Allocate memory for the folder.  */
92 	  folder = calloc (1, sizeof (*folder));
93 	  if (folder != NULL)
94 	    {
95 	      folder->url = url;
96 	      folder->is_local = record->flags & MU_RECORD_LOCAL;
97 	      /* Initialize the internal foilder lock, now so the
98 		 concrete folder could use it.  */
99 	      status = mu_monitor_create (&folder->monitor, 0, folder);
100 	      if (status == 0)
101 		{
102 		  /* Create the concrete folder type.  */
103 		  status = f_init (folder);
104 		  if (status == 0)
105 		    {
106 		      if (!folder->_match)
107 			folder->_match = mu_folder_imap_match;
108 		      *pfolder = folder;
109 		      folder->ref++;
110 		    }
111 		}
112 	      /* Something went wrong, destroy the object. */
113 	      if (status)
114 		{
115 		  if (folder->monitor)
116 		    mu_monitor_destroy (&folder->monitor, folder);
117 		  free (folder);
118 		}
119 	    }
120 	  return status;
121 	}
122     }
123 
124   return MU_ERR_NOENT;
125 }
126 
127 int
mu_folder_create(mu_folder_t * pfolder,const char * name)128 mu_folder_create (mu_folder_t *pfolder, const char *name)
129 {
130   int rc;
131   mu_url_t url;
132 
133   rc = mu_url_create (&url, name);
134   if (rc)
135     return rc;
136   rc = mu_folder_create_from_record (pfolder, url, NULL);
137   if (rc)
138     mu_url_destroy (&url);
139   return rc;
140 }
141 
142 int
mu_folder_attach_ticket(mu_folder_t folder)143 mu_folder_attach_ticket (mu_folder_t folder)
144 {
145   mu_authority_t auth = NULL;
146   int rc = MU_ERR_NOENT;
147 
148   if (mu_folder_get_authority (folder, &auth) == 0 && auth)
149     {
150       char *filename = mu_tilde_expansion (mu_ticket_file,
151 					   MU_HIERARCHY_DELIMITER, NULL);
152       mu_wicket_t wicket;
153 
154       mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
155 		("Reading user ticket file %s", filename));
156       if ((rc = mu_file_wicket_create (&wicket, filename)) == 0)
157 	{
158 	  mu_ticket_t ticket;
159 
160 	  if ((rc = mu_wicket_get_ticket (wicket, NULL, &ticket)) == 0)
161 	    {
162 	      rc = mu_authority_set_ticket (auth, ticket);
163 	      mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1,
164 			("Retrieved and set ticket: %d", rc));
165 	    }
166 	  else
167 	    mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
168 		      ("Error retrieving ticket: %s\n",
169 		       mu_strerror (rc)));
170 	  mu_wicket_destroy (&wicket);
171 	}
172       else
173 	mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
174 		  ("Error creating wicket: %s\n", mu_strerror (rc)));
175       free (filename);
176     }
177   return rc;
178 }
179 
180 int
mu_folder_is_local(mu_folder_t folder)181 mu_folder_is_local (mu_folder_t folder)
182 {
183   if (!folder)
184     return -1;
185   return folder->is_local;
186 }
187 
188 /* The folder is destroy if it is the last reference.  */
189 void
mu_folder_destroy(mu_folder_t * pfolder)190 mu_folder_destroy (mu_folder_t *pfolder)
191 {
192   if (pfolder && *pfolder)
193     {
194       mu_folder_t folder = *pfolder;
195       int destroy_lock = 0;
196       mu_monitor_t monitor = folder->monitor;
197 
198       mu_monitor_wrlock (monitor);
199 
200       folder->ref--;
201 
202       if (folder->ref <= 0)
203 	{
204 	  mu_monitor_unlock (monitor);
205 	  destroy_lock = 1;
206 	  /* Notify the observers.  */
207 	  if (folder->observable)
208 	    {
209 	      mu_observable_notify (folder->observable, MU_EVT_FOLDER_DESTROY,
210 				    folder);
211 	      mu_observable_destroy (&folder->observable, folder);
212 	    }
213 	  if (folder->_destroy)
214 	    folder->_destroy (folder);
215 	  mu_monitor_wrlock (monitor);
216 	  if (folder->authority)
217 	    mu_authority_destroy (&folder->authority, folder);
218 	  if (folder->url)
219 	    mu_url_destroy (&folder->url);
220 	  if (folder->property)
221 	    mu_property_destroy (&folder->property);
222 	  free (folder);
223 	}
224       mu_monitor_unlock (monitor);
225       if (destroy_lock)
226 	mu_monitor_destroy (&monitor, folder);
227       *pfolder = NULL;
228     }
229 }
230 
231 int
mu_folder_get_property(mu_folder_t folder,mu_property_t * prop)232 mu_folder_get_property (mu_folder_t folder, mu_property_t *prop)
233 {
234   if (folder == NULL)
235     return EINVAL;
236   if (prop == NULL)
237     return MU_ERR_OUT_PTR_NULL;
238 
239   if (folder->property == NULL)
240     {
241       int status;
242 
243       if (folder->_get_property)
244 	status = folder->_get_property (folder, &folder->property);
245       else
246 	status = mu_property_create_init (&folder->property,
247 					  mu_assoc_property_init, NULL);
248       if (status != 0)
249 	return status;
250     }
251   *prop = folder->property;
252   return 0;
253 }
254 
255 
256 /* Cover functions.  */
257 int
mu_folder_open(mu_folder_t folder,int flags)258 mu_folder_open (mu_folder_t folder, int flags)
259 {
260   if (folder == NULL)
261     return EINVAL;
262   if (folder->_open == NULL)
263     return ENOSYS;
264   return folder->_open (folder, flags);
265 }
266 
267 int
mu_folder_close(mu_folder_t folder)268 mu_folder_close (mu_folder_t folder)
269 {
270   if (folder == NULL)
271     return EINVAL;
272   if (folder->_close == NULL)
273     return ENOSYS;
274   return folder->_close (folder);
275 }
276 
277 int
mu_folder_set_authority(mu_folder_t folder,mu_authority_t authority)278 mu_folder_set_authority (mu_folder_t folder, mu_authority_t authority)
279 {
280   if (folder == NULL)
281     return EINVAL;
282   if (folder->authority)
283     mu_authority_destroy (&folder->authority, folder);
284   folder->authority = authority;
285   return 0;
286 }
287 
288 int
mu_folder_get_authority(mu_folder_t folder,mu_authority_t * pauthority)289 mu_folder_get_authority (mu_folder_t folder, mu_authority_t *pauthority)
290 {
291   if (folder == NULL)
292     return EINVAL;
293   if (pauthority == NULL)
294     return MU_ERR_OUT_PTR_NULL;
295   *pauthority = folder->authority;
296   return 0;
297 }
298 
299 int
mu_folder_get_observable(mu_folder_t folder,mu_observable_t * pobservable)300 mu_folder_get_observable (mu_folder_t folder, mu_observable_t *pobservable)
301 {
302   if (folder == NULL)
303     return EINVAL;
304   if (pobservable == NULL)
305     return MU_ERR_OUT_PTR_NULL;
306 
307   if (folder->observable == NULL)
308     {
309       int status = mu_observable_create (&folder->observable, folder);
310       if (status != 0)
311         return status;
312     }
313   *pobservable = folder->observable;
314   return 0;
315 }
316 
317 int
mu_folder_set_match(mu_folder_t folder,mu_folder_match_fp pmatch)318 mu_folder_set_match (mu_folder_t folder, mu_folder_match_fp pmatch)
319 {
320   if (folder == NULL)
321     return EINVAL;
322   folder->_match = pmatch;
323   return 0;
324 }
325 
326 int
mu_folder_get_match(mu_folder_t folder,mu_folder_match_fp * pmatch)327 mu_folder_get_match (mu_folder_t folder, mu_folder_match_fp *pmatch)
328 {
329   if (folder == NULL)
330     return EINVAL;
331   if (pmatch == NULL)
332     return MU_ERR_OUT_PTR_NULL;
333   *pmatch = folder->_match;
334   return 0;
335 }
336 
337 void
mu_list_response_free(void * data)338 mu_list_response_free (void *data)
339 {
340   struct mu_list_response *f = data;
341   free (f->name);
342   free (f);
343 }
344 
345 int
mu_folder_scan(mu_folder_t folder,struct mu_folder_scanner * scn)346 mu_folder_scan (mu_folder_t folder, struct mu_folder_scanner *scn)
347 {
348   if (!folder || !scn)
349     return EINVAL;
350   if (folder->_list == NULL)
351     return ENOSYS;
352   if (scn->result)
353     mu_list_set_destroy_item (scn->result, mu_list_response_free);
354   return folder->_list (folder, scn);
355 }
356 
357 int
mu_folder_list(mu_folder_t folder,const char * dirname,void * pattern,size_t max_depth,mu_list_t * pflist)358 mu_folder_list (mu_folder_t folder, const char *dirname, void *pattern,
359 		size_t max_depth,
360 		mu_list_t *pflist)
361 {
362   return mu_folder_enumerate (folder, dirname, pattern,
363 			      MU_FOLDER_ATTRIBUTE_ALL, max_depth,
364 			      pflist, NULL, NULL);
365 }
366 
367 int
mu_folder_enumerate(mu_folder_t folder,const char * name,void * pattern,int flags,size_t max_depth,mu_list_t * pflist,mu_folder_enumerate_fp enumfun,void * enumdata)368 mu_folder_enumerate (mu_folder_t folder, const char *name,
369 		     void *pattern, int flags,
370 		     size_t max_depth,
371 		     mu_list_t *pflist,
372 		     mu_folder_enumerate_fp enumfun, void *enumdata)
373 {
374   int status;
375   if (folder == NULL || (!pflist && !enumfun))
376     return EINVAL;
377   else if (folder->_list == NULL)
378     return ENOSYS;
379   else
380     {
381       struct mu_folder_scanner scn;
382 
383       scn.refname = name;
384       scn.pattern = pattern;
385       scn.match_flags = flags;
386       scn.max_depth = max_depth;
387       scn.enumfun = enumfun;
388       scn.enumdata = enumdata;
389       scn.records = NULL;
390       if (pflist)
391 	{
392 	  status = mu_list_create (&scn.result);
393 	  if (status)
394 	    return status;
395 	  mu_list_set_destroy_item (scn.result, mu_list_response_free);
396 	}
397       status = mu_folder_scan (folder, &scn);
398       if (status == 0)
399 	{
400 	  if (pflist)
401 	    *pflist = scn.result;
402 	}
403       else
404 	mu_list_destroy (&scn.result);
405     }
406   return status;
407 }
408 
409 int
mu_folder_lsub(mu_folder_t folder,const char * dirname,const char * basename,mu_list_t * pflist)410 mu_folder_lsub (mu_folder_t folder, const char *dirname, const char *basename,
411 		mu_list_t *pflist)
412 {
413   int status;
414 
415   if (folder == NULL)
416     return EINVAL;
417   else if (folder->_lsub == NULL)
418     return ENOSYS;
419   else
420     {
421       status = mu_list_create (pflist);
422       if (status)
423 	return status;
424       mu_list_set_destroy_item (*pflist, mu_list_response_free);
425       status = folder->_lsub (folder, dirname, basename, *pflist);
426     }
427   return status;
428 }
429 
430 int
mu_folder_subscribe(mu_folder_t folder,const char * name)431 mu_folder_subscribe (mu_folder_t folder, const char *name)
432 {
433   if (folder == NULL)
434     return EINVAL;
435   if (folder->_subscribe == NULL)
436     return ENOSYS;
437   return folder->_subscribe (folder, name);
438 }
439 
440 int
mu_folder_unsubscribe(mu_folder_t folder,const char * name)441 mu_folder_unsubscribe (mu_folder_t folder, const char *name)
442 {
443   if (folder == NULL)
444     return EINVAL;
445   if (folder->_unsubscribe == NULL)
446     return ENOSYS;
447   return folder->_unsubscribe (folder, name);
448 }
449 
450 int
mu_folder_delete(mu_folder_t folder,const char * name)451 mu_folder_delete (mu_folder_t folder, const char *name)
452 {
453   int rc;
454 
455   if (folder == NULL)
456     return EINVAL;
457   if (folder->_delete)
458     rc = folder->_delete (folder, name);
459   else
460     {
461       /* If there is no folder-specific _delete method, then try to create the
462 	 mailbox and call mailbox delete (remove) method.  This is necessary
463 	 because certain types of mailboxes share a common folder (e.g. mbox,
464 	 maildir and mh all use filesystem folder), but have a different
465 	 internal structure.  Supplying mu_folder_t with knowledge about
466 	 mailbox internals will harm separation of concerns.  On the other
467 	 hand, removing something without looking into it may well yield
468 	 undesired results.  For example, a MH mailbox can hold another
469 	 mailboxes, i.e. be a folder itself.  Removing it blindly would
470 	 result in removing these mailboxes as well, which is clearly not
471 	 intended.
472 
473 	 To solve this, both folder and mailbox delete methods are tightly
474 	 paired, but without looking into each-others internal mechanisms. */
475       mu_mailbox_t mbox;
476       rc = mu_mailbox_create_at (&mbox, folder, name);
477       if (rc == 0)
478 	{
479 	  rc = mu_mailbox_remove (mbox);
480 	  mu_mailbox_destroy (&mbox);
481 	}
482     }
483   return rc;
484 }
485 
486 int
mu_folder_rename(mu_folder_t folder,const char * oldname,const char * newname)487 mu_folder_rename (mu_folder_t folder, const char *oldname, const char *newname)
488 {
489   if (folder == NULL)
490     return EINVAL;
491   if (folder->_rename == NULL)
492     return ENOSYS;
493   return folder->_rename (folder, oldname, newname);
494 }
495 
496 int
mu_folder_get_url(mu_folder_t folder,mu_url_t * purl)497 mu_folder_get_url (mu_folder_t folder, mu_url_t *purl)
498 {
499   if (folder == NULL)
500     return EINVAL;
501   if (purl == NULL)
502     return MU_ERR_OUT_PTR_NULL;
503   *purl = folder->url;
504   return 0;
505 }
506 
507