1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils 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, or (at your option)
7    any later version.
8 
9    GNU Mailutils 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 GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "imap4d.h"
18 #include <mailutils/assoc.h>
19 
20 struct namespace namespace[NS_MAX] = {
21   [NS_PERSONAL] = { "personal" },
22   [NS_OTHER]    = { "other" },
23   [NS_SHARED]   = { "shared" }
24 };
25 
26 static mu_assoc_t prefixes;
27 
28 struct namespace *
namespace_lookup(char const * name)29 namespace_lookup (char const *name)
30 {
31   size_t i;
32 
33   for (i = 0; i < NS_MAX; i++)
34     if (strcmp (namespace[i].name, name) == 0)
35       {
36 	if (!namespace[i].prefixes)
37 	  {
38 	    int rc = mu_list_create (&namespace[i].prefixes);
39 	    if (rc)
40 	      {
41 		mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc);
42 		abort ();
43 	      }
44 	  }
45 	return &namespace[i];
46       }
47   return NULL;
48 }
49 
50 static int
cmplen(const char * aname,const void * adata,const char * bname,const void * bdata,void * closure)51 cmplen (const char *aname, const void *adata,
52 	const char *bname, const void *bdata,
53 	void *closure)
54 {
55   return strlen (bname) - strlen (aname);
56 }
57 
58 static void
trim_delim(char * str,int delim)59 trim_delim (char *str, int delim)
60 {
61   size_t len = strlen (str);
62   while (len && str[len-1] == '/')
63     len--;
64   str[len] = 0;
65 }
66 
67 void
namespace_init(void)68 namespace_init (void)
69 {
70   int i;
71   int rc;
72   struct namespace_prefix *pfx;
73 
74   if (mu_assoc_create (&prefixes, 0))
75     imap4d_bye (ERR_NO_MEM);
76   for (i = 0; i < NS_MAX; i++)
77     {
78       mu_iterator_t itr;
79 
80       if (mu_list_is_empty (namespace[i].prefixes))
81 	continue;
82 
83       if (mu_list_get_iterator (namespace[i].prefixes, &itr))
84 	imap4d_bye (ERR_NO_MEM);
85 
86       for (mu_iterator_first (itr);
87 	   !mu_iterator_is_done (itr); mu_iterator_next (itr))
88 	{
89 	  rc = mu_iterator_current (itr, (void **)&pfx);
90 	  if (rc)
91 	    {
92 	      mu_diag_funcall (MU_DIAG_ERROR, "mu_iterator_current", NULL, rc);
93 	      continue;
94 	    }
95 	  pfx->ns = i;
96 
97 	  trim_delim (pfx->dir, '/');
98 
99 	  if (!pfx->scheme)
100 	    mu_registrar_get_default_record (&pfx->record);
101 
102 	  rc = mu_assoc_install (prefixes, pfx->prefix, pfx);
103 	  if (rc == MU_ERR_EXISTS)
104 	    {
105 	      mu_error (_("%s: namespace prefix appears twice"), pfx->prefix);
106 	      exit (EX_CONFIG);
107 	    }
108 	  else if (rc)
109 	    {
110 	      mu_error (_("%s: can't install prefix: %s"),
111 			pfx->prefix, mu_strerror (rc));
112 	      exit (EX_CONFIG);
113 	    }
114 	}
115     }
116 
117   pfx = mu_assoc_get (prefixes, "");
118   if (pfx)
119     {
120       if (pfx->ns != NS_PERSONAL)
121 	{
122 	  mu_error (_("empty prefix not allowed in the namespace %s"),
123 		    namespace[pfx->ns].name);
124 	  exit (EX_CONFIG);
125 	}
126     }
127   else
128     {
129       struct namespace *priv;
130 
131       pfx = mu_zalloc (sizeof (*pfx));
132       pfx->prefix = mu_strdup ("");
133       pfx->dir = mu_strdup ("$home");
134       pfx->delim = '/';
135       mu_registrar_get_default_record (&pfx->record);
136       priv = namespace_lookup ("personal");
137       mu_list_prepend (priv->prefixes, pfx);
138       rc = mu_assoc_install (prefixes, pfx->prefix, pfx);
139       if (rc)
140 	{
141 	  mu_error (_("%s: can't install prefix: %s"),
142 		    pfx->prefix, mu_strerror (rc));
143 	  exit (EX_CONFIG);
144 	}
145     }
146 
147   mu_assoc_sort_r (prefixes, cmplen, NULL);
148 }
149 
150 void
translate_delim(char * dst,char const * src,int dst_delim,int src_delim)151 translate_delim (char *dst, char const *src, int dst_delim, int src_delim)
152 {
153   do
154     *dst++ = *src == src_delim ? dst_delim : *src;
155   while (*src++);
156 }
157 
158 static char *
prefix_translate_name(struct namespace_prefix const * pfx,char const * name,size_t namelen)159 prefix_translate_name (struct namespace_prefix const *pfx, char const *name,
160 		       size_t namelen)
161 {
162   size_t pfxlen = strlen (pfx->prefix);
163 
164   if (pfxlen <= namelen && memcmp (pfx->prefix, name, pfxlen) == 0)
165     {
166       char *tmpl, *p;
167 
168       name += pfxlen;
169 
170       if (pfx->ns == NS_PERSONAL && pfxlen == 0 &&
171 	  mu_c_strcasecmp (name, "INBOX") == 0)
172 	{
173 	  tmpl = mu_strdup (auth_data->mailbox);
174 	  return tmpl;//FIXME
175 	}
176 
177       tmpl = mu_alloc (namelen - pfxlen + strlen (pfx->dir) + 2);
178       p = tmpl;
179 
180       p = mu_stpcpy (p, pfx->dir);
181       if (*name)
182 	{
183 	  if (pfx->ns == NS_OTHER)
184 	    {
185 	      if (pfx->prefix[strlen (pfx->prefix) - 1] == pfx->delim)
186 		++name;
187 
188 	      while (*name && *name != pfx->delim)
189 		name++;
190 	    }
191 	  else if (*name != pfx->delim)
192 	    *p++ = '/';
193 	  translate_delim (p, name, '/', pfx->delim);
194 	}
195 
196       return tmpl;
197     }
198   return NULL;
199 }
200 
201 static char *
translate_name(char const * name,struct namespace_prefix const ** return_pfx)202 translate_name (char const *name, struct namespace_prefix const **return_pfx)
203 {
204   mu_iterator_t itr;
205   int rc;
206   char *res = NULL;
207   size_t namelen = strlen (name);
208 
209   rc = mu_assoc_get_iterator (prefixes, &itr);
210   if (rc)
211     {
212       mu_diag_funcall (MU_DIAG_ERROR, "mu_assoc_get_iterator", NULL, rc);
213       return NULL;
214     }
215   for (mu_iterator_first (itr);
216        !mu_iterator_is_done (itr); mu_iterator_next (itr))
217     {
218       struct namespace_prefix *pfx;
219 
220       rc = mu_iterator_current (itr, (void **)&pfx);
221       if (rc)
222 	{
223 	  mu_diag_funcall (MU_DIAG_ERROR, "mu_iterator_current", NULL, rc);
224 	  continue;
225 	}
226 
227       res = prefix_translate_name (pfx, name, namelen);
228       if (res)
229 	{
230 	  if (return_pfx)
231 	    *return_pfx = pfx;
232 	  break;
233 	}
234     }
235   mu_iterator_destroy (&itr);
236 
237   return res;
238 }
239 
240 static char *
extract_username(char const * name,struct namespace_prefix const * pfx)241 extract_username (char const *name, struct namespace_prefix const *pfx)
242 {
243   char const *p;
244   char *end;
245   char *user;
246   size_t len;
247 
248   if (strlen (name) < strlen (pfx->prefix))
249     return NULL;
250   p = name + strlen (pfx->prefix);
251   end = strchr (p, pfx->delim);
252 
253   if (end)
254     len = end - p;
255   else
256     len = strlen (p);
257 
258   if (len == 0)
259     return mu_strdup (auth_data->name);
260 
261   user = mu_alloc (len + 1);
262   memcpy (user, p, len);
263   user[len] = 0;
264   return user;
265 }
266 
267 char *
namespace_translate_name(char const * name,struct namespace_prefix const ** return_pfx)268 namespace_translate_name (char const *name,
269 			  struct namespace_prefix const **return_pfx)
270 {
271   char *res = NULL;
272   struct namespace_prefix const *pfx;
273 
274   res = translate_name (name, &pfx);
275 
276   if (res)
277     {
278       mu_assoc_t assoc;
279       int rc;
280       char *dir;
281 
282       rc = mu_assoc_create (&assoc, 0);
283       if (rc)
284 	{
285 	  mu_diag_funcall (MU_DIAG_ERROR, "mu_assoc_create", NULL, rc);
286 	  free (res);
287 	  imap4d_bye (ERR_NO_MEM);
288 	}
289 
290       switch (pfx->ns)
291 	{
292 	case NS_PERSONAL:
293 	  mu_assoc_install (assoc, "user", auth_data->name);
294 	  mu_assoc_install (assoc, "home", real_homedir);
295 	  break;
296 
297 	case NS_OTHER:
298 	  {
299 	    struct mu_auth_data *adata;
300 	    char *user = extract_username (name, pfx);
301 	    mu_assoc_install (assoc, "user", user);
302 	    adata = mu_get_auth_by_name (user);
303 	    if (adata)
304 	      {
305 		mu_assoc_install (assoc, "home", mu_strdup (adata->dir));
306 		mu_auth_data_free (adata);
307 	      }
308 	    mu_assoc_set_destroy_item (assoc, mu_list_free_item);
309 	  }
310 	  break;
311 
312 	case NS_SHARED:
313 	  break;
314 	}
315 
316       rc = mu_str_expand (&dir, res, assoc);
317       free (res);
318       mu_assoc_destroy (&assoc);
319       if (rc)
320 	{
321 	  if (rc == MU_ERR_FAILURE)
322 	    {
323 	      mu_error (_("cannot expand line `%s': %s"), res, dir);
324 	      free (dir);
325 	    }
326 	  else
327 	    {
328 	      mu_error (_("cannot expand line `%s': %s"), res,
329 			mu_strerror (rc));
330 	    }
331 	  imap4d_bye (ERR_NO_MEM);
332 	}
333 
334       res = dir;
335       trim_delim (res, '/');
336     }
337   else if (mu_c_strcasecmp (name, "INBOX") == 0 && auth_data->change_uid)
338     {
339       res = mu_strdup (auth_data->mailbox);
340       pfx = mu_assoc_get (prefixes, "");
341     }
342 
343   if (res && return_pfx)
344     *return_pfx = pfx;
345 
346   return res;
347 }
348 
349 char *
namespace_get_name(char const * name,mu_record_t * rec,int * mode)350 namespace_get_name (char const *name, mu_record_t *rec, int *mode)
351 {
352   struct namespace_prefix const *pfx;
353   char *path = namespace_translate_name (name, &pfx);
354   if (strcmp (name, pfx->prefix) == 0)
355     {
356       free (path);
357       return NULL;
358     }
359   if (rec)
360     *rec = pfx->record;
361   if (mode)
362     *mode = namespace[pfx->ns].mode;
363   return path;
364 }
365 
366 char *
namespace_decode_delim(struct namespace_prefix const * pfx,char const * src)367 namespace_decode_delim (struct namespace_prefix const *pfx, char const *src)
368 {
369   char *dst = mu_alloc (strlen (src) + 1);
370   translate_delim (dst, src, '/', pfx->delim);
371   return dst;
372 }
373 
374 char *
namespace_encode_delim(struct namespace_prefix const * pfx,char const * src)375 namespace_encode_delim (struct namespace_prefix const *pfx, char const *src)
376 {
377   char *dst = mu_alloc (strlen (src) + 1);
378   translate_delim (dst, src, pfx->delim, '/');
379   return dst;
380 }
381 
382 
383 static int
prefix_printer(void * item,void * data)384 prefix_printer(void *item, void *data)
385 {
386   struct namespace_prefix *pfx = item;
387   int *first = data;
388 
389   if (*first)
390     *first = 0;
391   else
392     io_sendf (" ");
393   io_sendf ("(\"%s\" \"%c\")", pfx->prefix, pfx->delim);
394   return 0;
395 }
396 
397 static void
print_namespace(int ns)398 print_namespace (int ns)
399 {
400   if (mu_list_is_empty (namespace[ns].prefixes))
401     io_sendf ("NIL");
402   else
403     {
404       int first = 1;
405       io_sendf ("(");
406       mu_list_foreach (namespace[ns].prefixes, prefix_printer, &first);
407       io_sendf (")");
408     }
409 }
410 
411 /*
412 5. NAMESPACE Command
413 
414    Arguments: none
415 
416    Response:  an untagged NAMESPACE response that contains the prefix
417                  and hierarchy delimiter to the server's Personal
418                  Namespace(s), Other Users' Namespace(s), and Shared
419                  Namespace(s) that the server wishes to expose. The
420                  response will contain a NIL for any namespace class
421                  that is not available. Namespace_Response_Extensions
422                  MAY be included in the response.
423                  Namespace_Response_Extensions which are not on the IETF
424                  standards track, MUST be prefixed with an "X-".
425 */
426 
427 int
imap4d_namespace(struct imap4d_session * session,struct imap4d_command * command,imap4d_tokbuf_t tok)428 imap4d_namespace (struct imap4d_session *session,
429                   struct imap4d_command *command, imap4d_tokbuf_t tok)
430 {
431   if (imap4d_tokbuf_argc (tok) != 2)
432     return io_completion_response (command, RESP_BAD, "Invalid arguments");
433 
434   io_sendf ("* NAMESPACE ");
435 
436   print_namespace (NS_PERSONAL);
437   io_sendf (" ");
438   print_namespace (NS_OTHER);
439   io_sendf (" ");
440   print_namespace (NS_SHARED);
441   io_sendf ("\n");
442 
443   return io_completion_response (command, RESP_OK, "Completed");
444 }
445