1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "array.h"
6 #include "unichar.h"
7 #include "imap-match.h"
8 #include "subscription-file.h"
9 #include "mailbox-tree.h"
10 #include "mailbox-list-private.h"
11 #include "mailbox-list-subscriptions.h"
12 
13 #include <sys/stat.h>
14 
15 struct subscriptions_mailbox_list_iterate_context {
16 	struct mailbox_list_iterate_context ctx;
17 	struct mailbox_tree_context *tree;
18 	struct mailbox_tree_iterate_context *iter;
19 	struct mailbox_info info;
20 };
21 
22 static int
mailbox_list_subscription_fill_one(struct mailbox_list * list,struct mailbox_list * src_list,const char * name)23 mailbox_list_subscription_fill_one(struct mailbox_list *list,
24 				   struct mailbox_list *src_list,
25 				   const char *name)
26 {
27 	struct mail_namespace *ns, *default_ns = list->ns;
28 	struct mail_namespace *namespaces = default_ns->user->namespaces;
29 	struct mailbox_node *node;
30 	const char *vname, *ns_name, *error;
31 	size_t len;
32 	bool created;
33 
34 	/* default_ns is whatever namespace we're currently listing.
35 	   if we have e.g. prefix="" and prefix=pub/ namespaces with
36 	   pub/ namespace having subscriptions=no, we want to:
37 
38 	   1) when listing "" namespace we want to skip over any names
39 	   that begin with pub/. */
40 	if (src_list->ns->prefix_len == 0)
41 		ns_name = name;
42 	else {
43 		/* we could have two-level namespace: ns/ns2/ */
44 		ns_name = t_strconcat(src_list->ns->prefix, name, NULL);
45 	}
46 	ns = mail_namespace_find_unsubscribable(namespaces, ns_name);
47 	if (ns != NULL && ns != default_ns) {
48 		if (ns->prefix_len > 0)
49 			return 0;
50 		/* prefix="" namespace=no : catching this is basically the
51 		   same as not finding any namespace. */
52 		ns = NULL;
53 	}
54 
55 	/* 2) when listing pub/ namespace, skip over entries that don't
56 	   begin with pub/. */
57 	if (ns == NULL &&
58 	    (default_ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0)
59 		return 0;
60 
61 	/* When listing shared namespace's subscriptions, we need to
62 	   autocreate all the visible child namespaces. their subscriptions
63 	   are listed later. */
64 	if (ns != NULL && mail_namespace_is_shared_user_root(ns)) {
65 		/* we'll need to get the namespace autocreated.
66 		   one easy way is to just ask to join a reference and
67 		   pattern */
68 		(void)mailbox_list_join_refpattern(ns->list, ns_name, "");
69 	}
70 
71 	/* When listing pub/ namespace, skip over the namespace
72 	   prefix in the name. the rest of the name is storage_name. */
73 	if (ns == NULL)
74 		ns = default_ns;
75 	else if (strncmp(ns_name, ns->prefix, ns->prefix_len) == 0) {
76 		ns_name += ns->prefix_len;
77 		name = ns_name;
78 	} else {
79 		/* "pub" entry - this shouldn't be possible normally, because
80 		   it should be saved as "pub/", but handle it anyway */
81 		i_assert(strncmp(ns_name, ns->prefix, ns->prefix_len-1) == 0 &&
82 			 ns_name[ns->prefix_len-1] == '\0');
83 		name = "";
84 		/* ns_name = ""; */
85 	}
86 
87 	len = strlen(name);
88 	if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) {
89 		/* entry ends with hierarchy separator, remove it.
90 		   this exists mainly for backwards compatibility with old
91 		   Dovecot versions and non-Dovecot software that added them */
92 		name = t_strndup(name, len-1);
93 	}
94 
95 	if (!mailbox_list_is_valid_name(list, name, &error)) {
96 		/* we'll only get into trouble if we show this */
97 		return -1;
98 	} else {
99 		vname = mailbox_list_get_vname(list, name);
100 		if (!uni_utf8_str_is_valid(vname))
101 			return -1;
102 		node = mailbox_tree_get(list->subscriptions, vname, &created);
103 		node->flags = MAILBOX_SUBSCRIBED;
104 	}
105 	return 0;
106 }
107 
mailbox_list_subscriptions_refresh(struct mailbox_list * src_list,struct mailbox_list * dest_list)108 int mailbox_list_subscriptions_refresh(struct mailbox_list *src_list,
109 				       struct mailbox_list *dest_list)
110 {
111 	struct subsfile_list_context *subsfile_ctx;
112 	struct stat st;
113 	enum mailbox_list_path_type type;
114 	const char *path, *name;
115 	char sep;
116 	int ret;
117 
118 	/* src_list is subscriptions=yes, dest_list is subscriptions=no
119 	   (or the same as src_list) */
120 	i_assert((src_list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0);
121 
122 	if (dest_list->subscriptions == NULL) {
123 		sep = mail_namespace_get_sep(src_list->ns);
124 		dest_list->subscriptions = mailbox_tree_init(sep);
125 	}
126 
127 	type = src_list->set.control_dir != NULL ?
128 		MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR;
129 	if (!mailbox_list_get_root_path(src_list, type, &path) ||
130 	    src_list->set.subscription_fname == NULL) {
131 		/* no subscriptions (e.g. pop3c) */
132 		return 0;
133 	}
134 	path = t_strconcat(path, "/", src_list->set.subscription_fname, NULL);
135 	if (stat(path, &st) < 0) {
136 		if (errno == ENOENT) {
137 			/* no subscriptions */
138 			mailbox_tree_clear(dest_list->subscriptions);
139 			dest_list->subscriptions_mtime = 0;
140 			return 0;
141 		}
142 		mailbox_list_set_critical(dest_list, "stat(%s) failed: %m",
143 					  path);
144 		return -1;
145 	}
146 	if (st.st_mtime == dest_list->subscriptions_mtime &&
147 	    st.st_mtime < dest_list->subscriptions_read_time-1) {
148 		/* we're up to date */
149 		return 0;
150 	}
151 
152 	mailbox_tree_clear(dest_list->subscriptions);
153 	dest_list->subscriptions_read_time = ioloop_time;
154 
155 	subsfile_ctx = subsfile_list_init(dest_list, path);
156 	if (subsfile_list_fstat(subsfile_ctx, &st) == 0)
157 		dest_list->subscriptions_mtime = st.st_mtime;
158 	while ((name = subsfile_list_next(subsfile_ctx)) != NULL) T_BEGIN {
159 		T_BEGIN {
160 			ret = mailbox_list_subscription_fill_one(dest_list,
161 								 src_list, name);
162 		} T_END;
163 		if (ret < 0) {
164 			e_warning(dest_list->ns->user->event,
165 				  "Subscriptions file %s: "
166 				  "Removing invalid entry: %s",
167 				  path, name);
168 			(void)subsfile_set_subscribed(src_list, path,
169 				mailbox_list_get_temp_prefix(src_list),
170 				name, FALSE);
171 
172 		}
173 	} T_END;
174 
175 	if (subsfile_list_deinit(&subsfile_ctx) < 0) {
176 		dest_list->subscriptions_mtime = (time_t)-1;
177 		return -1;
178 	}
179 	return 0;
180 }
181 
mailbox_list_set_subscription_flags(struct mailbox_list * list,const char * vname,enum mailbox_info_flags * flags)182 void mailbox_list_set_subscription_flags(struct mailbox_list *list,
183 					 const char *vname,
184 					 enum mailbox_info_flags *flags)
185 {
186 	struct mailbox_node *node;
187 
188 	*flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
189 
190 	node = mailbox_tree_lookup(list->subscriptions, vname);
191 	if (node != NULL) {
192 		*flags |= node->flags & MAILBOX_SUBSCRIBED;
193 
194 		/* the only reason why node might have a child is if one of
195 		   them is subscribed */
196 		if (node->children != NULL)
197 			*flags |= MAILBOX_CHILD_SUBSCRIBED;
198 	}
199 }
200 
mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context * ctx,struct mailbox_tree_context * tree,bool default_nonexistent)201 void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
202 				     struct mailbox_tree_context *tree,
203 				     bool default_nonexistent)
204 {
205 	struct mailbox_list_iter_update_context update_ctx;
206 	struct mailbox_tree_iterate_context *iter;
207 	const char *name;
208 
209 	i_zero(&update_ctx);
210 	update_ctx.iter_ctx = ctx;
211 	update_ctx.tree_ctx = tree;
212 	update_ctx.glob = ctx->glob;
213 	update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
214 	if (default_nonexistent)
215 		update_ctx.leaf_flags |= MAILBOX_NONEXISTENT;
216 	update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
217 	update_ctx.match_parents =
218 		(ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
219 
220 	iter = mailbox_tree_iterate_init(ctx->list->subscriptions, NULL,
221 					 MAILBOX_SUBSCRIBED);
222 	while (mailbox_tree_iterate_next(iter, &name) != NULL)
223 		mailbox_list_iter_update(&update_ctx, name);
224 	mailbox_tree_iterate_deinit(&iter);
225 }
226 
227 struct mailbox_list_iterate_context *
mailbox_list_subscriptions_iter_init(struct mailbox_list * list,const char * const * patterns,enum mailbox_list_iter_flags flags)228 mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
229 				     const char *const *patterns,
230 				     enum mailbox_list_iter_flags flags)
231 {
232 	struct subscriptions_mailbox_list_iterate_context *ctx;
233 	pool_t pool;
234 	char sep = mail_namespace_get_sep(list->ns);
235 
236 	pool = pool_alloconly_create("mailbox list subscriptions iter", 1024);
237 	ctx = p_new(pool, struct subscriptions_mailbox_list_iterate_context, 1);
238 	ctx->ctx.pool = pool;
239 	ctx->ctx.list = list;
240 	ctx->ctx.flags = flags;
241 	ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, sep);
242 	array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
243 
244 	ctx->tree = mailbox_tree_init(sep);
245 	mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree, FALSE);
246 
247 	ctx->info.ns = list->ns;
248 	/* the tree usually has only those entries we want to iterate through,
249 	   but there are also non-matching root entries (e.g. "LSUB foo/%" will
250 	   include the "foo"), which we'll drop with MAILBOX_MATCHED. */
251 	ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, MAILBOX_MATCHED);
252 	return &ctx->ctx;
253 }
254 
255 const struct mailbox_info *
mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context * _ctx)256 mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *_ctx)
257 {
258 	struct subscriptions_mailbox_list_iterate_context *ctx =
259 		(struct subscriptions_mailbox_list_iterate_context *)_ctx;
260 	struct mailbox_list *list = _ctx->list;
261 	struct mailbox_node *node;
262 	enum mailbox_info_flags subs_flags;
263 	const char *vname, *storage_name, *error;
264 	int ret;
265 
266 	node = mailbox_tree_iterate_next(ctx->iter, &vname);
267 	if (node == NULL)
268 		return mailbox_list_iter_default_next(_ctx);
269 
270 	ctx->info.vname = vname;
271 	subs_flags = node->flags & (MAILBOX_SUBSCRIBED |
272 				    MAILBOX_CHILD_SUBSCRIBED);
273 
274 	if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
275 	    (_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) {
276 		/* don't care about flags, just return it */
277 		ctx->info.flags = subs_flags;
278 		return &ctx->info;
279 	}
280 
281 	storage_name = mailbox_list_get_storage_name(list, vname);
282 	if (!mailbox_list_is_valid_name(list, storage_name, &error)) {
283 		/* broken entry in subscriptions file */
284 		ctx->info.flags = MAILBOX_NONEXISTENT;
285 	} else if (mailbox_list_mailbox(list, storage_name,
286 					&ctx->info.flags) < 0) {
287 		ctx->info.flags = 0;
288 		_ctx->failed = TRUE;
289 	} else if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 &&
290 		   (ctx->info.flags & (MAILBOX_CHILDREN |
291 				       MAILBOX_NOCHILDREN)) == 0) {
292 		ret = mailbox_has_children(list, storage_name);
293 		if (ret < 0)
294 			_ctx->failed = TRUE;
295 		else if (ret == 0)
296 			ctx->info.flags |= MAILBOX_NOCHILDREN;
297 		else
298 			ctx->info.flags |= MAILBOX_CHILDREN;
299 
300 	}
301 
302 	ctx->info.flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
303 	ctx->info.flags |=
304 		node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
305 	return &ctx->info;
306 }
307 
mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context * _ctx)308 int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *_ctx)
309 {
310 	struct subscriptions_mailbox_list_iterate_context *ctx =
311 		(struct subscriptions_mailbox_list_iterate_context *)_ctx;
312 	int ret = _ctx->failed ? -1 : 0;
313 
314 	mailbox_tree_iterate_deinit(&ctx->iter);
315 	mailbox_tree_deinit(&ctx->tree);
316 	pool_unref(&_ctx->pool);
317 	return ret;
318 }
319