xref: /plan9/sys/src/cmd/ip/imap4d/list.c (revision 8cf6001e50e647a07ccf484b8e2f9940411befb9)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "imap4d.h"
6 
7 #define SUBSCRIBED	"imap.subscribed"
8 
9 static int	matches(char *ref, char *pat, char *name);
10 static int	mayMatch(char *pat, char *name, int star);
11 static int	checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir);
12 static int	listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime);
13 static int	listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm);
14 static int	mkSubscribed(void);
15 
16 static long
listMtime(char * file)17 listMtime(char *file)
18 {
19 	Dir *d;
20 	long mtime;
21 
22 	d = cdDirstat(mboxDir, file);
23 	if(d == nil)
24 		return 0;
25 	mtime = d->mtime;
26 	free(d);
27 	return mtime;
28 }
29 
30 /*
31  * check for subscribed mailboxes
32  * each line is either a comment starting with #
33  * or is a subscribed mailbox name
34  */
35 int
lsubBoxes(char * cmd,char * ref,char * pat)36 lsubBoxes(char *cmd, char *ref, char *pat)
37 {
38 	MbLock *mb;
39 	Dir *d;
40 	Biobuf bin;
41 	char *s;
42 	long mtime;
43 	int fd, ok, isdir;
44 
45 	mb = mbLock();
46 	if(mb == nil)
47 		return 0;
48 	fd = cdOpen(mboxDir, SUBSCRIBED, OREAD);
49 	if(fd < 0)
50 		fd = mkSubscribed();
51 	if(fd < 0){
52 		mbUnlock(mb);
53 		return 0;
54 	}
55 	ok = 0;
56 	Binit(&bin, fd, OREAD);
57 	while(s = Brdline(&bin, '\n')){
58 		s[Blinelen(&bin) - 1] = '\0';
59 		if(s[0] == '#')
60 			continue;
61 		isdir = 1;
62 		if(cistrcmp(s, "INBOX") == 0){
63 			if(access("msgs", AEXIST) == 0)
64 				mtime = listMtime("msgs");
65 			else
66 				mtime = listMtime("mbox");
67 			isdir = 0;
68 		}else{
69 			d = cdDirstat(mboxDir, s);
70 			if(d != nil){
71 				mtime = d->mtime;
72 				if(!(d->mode & DMDIR))
73 					isdir = 0;
74 				free(d);
75 			}else
76 				mtime = 0;
77 		}
78 		ok |= checkMatch(cmd, ref, pat, s, mtime, isdir);
79 	}
80 	Bterm(&bin);
81 	close(fd);
82 	mbUnlock(mb);
83 	return ok;
84 }
85 
86 static int
mkSubscribed(void)87 mkSubscribed(void)
88 {
89 	int fd;
90 
91 	fd = cdCreate(mboxDir, SUBSCRIBED, ORDWR, 0664);
92 	if(fd < 0)
93 		return -1;
94 	fprint(fd, "#imap4 subscription list\nINBOX\n");
95 	seek(fd, 0, 0);
96 	return fd;
97 }
98 
99 /*
100  * either subscribe or unsubscribe to a mailbox
101  */
102 int
subscribe(char * mbox,int how)103 subscribe(char *mbox, int how)
104 {
105 	MbLock *mb;
106 	char *s, *in, *ein;
107 	int fd, tfd, ok, nmbox;
108 
109 	if(cistrcmp(mbox, "inbox") == 0)
110 		mbox = "INBOX";
111 	mb = mbLock();
112 	if(mb == nil)
113 		return 0;
114 	fd = cdOpen(mboxDir, SUBSCRIBED, ORDWR);
115 	if(fd < 0)
116 		fd = mkSubscribed();
117 	if(fd < 0){
118 		mbUnlock(mb);
119 		return 0;
120 	}
121 	in = readFile(fd);
122 	if(in == nil){
123 		mbUnlock(mb);
124 		return 0;
125 	}
126 	nmbox = strlen(mbox);
127 	s = strstr(in, mbox);
128 	while(s != nil && (s != in && s[-1] != '\n' || s[nmbox] != '\n'))
129 		s = strstr(s+1, mbox);
130 	ok = 0;
131 	if(how == 's' && s == nil){
132 		if(fprint(fd, "%s\n", mbox) > 0)
133 			ok = 1;
134 	}else if(how == 'u' && s != nil){
135 		ein = strchr(s, '\0');
136 		memmove(s, &s[nmbox+1], ein - &s[nmbox+1]);
137 		ein -= nmbox+1;
138 		tfd = cdOpen(mboxDir, SUBSCRIBED, OWRITE|OTRUNC);
139 		if(tfd >= 0 && seek(fd, 0, 0) >= 0 && write(fd, in, ein-in) == ein-in)
140 			ok = 1;
141 		if(tfd > 0)
142 			close(tfd);
143 	}else
144 		ok = 1;
145 	close(fd);
146 	mbUnlock(mb);
147 	return ok;
148 }
149 
150 /*
151  * stupidly complicated so that % doesn't read entire directory structure
152  * yet * works
153  * note: in most places, inbox is case-insensitive,
154  * but here INBOX is checked for a case-sensitve match.
155  */
156 int
listBoxes(char * cmd,char * ref,char * pat)157 listBoxes(char *cmd, char *ref, char *pat)
158 {
159 	int ok;
160 
161 	ok = checkMatch(cmd, ref, pat, "INBOX", listMtime("mbox"), 0);
162 	return ok | listMatch(cmd, ref, pat, ref, pat);
163 }
164 
165 /*
166  * look for all messages which may match the pattern
167  * punt when a * is reached
168  */
169 static int
listMatch(char * cmd,char * ref,char * pat,char * mbox,char * mm)170 listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm)
171 {
172 	Dir *dir, *dirs;
173 	char *mdir, *m, *mb, *wc;
174 	long mode;
175 	int c, i, nmb, nmdir, nd, ok, fd;
176 
177 	mdir = nil;
178 	for(m = mm; c = *m; m++){
179 		if(c == '%' || c == '*'){
180 			if(mdir == nil){
181 				fd = cdOpen(mboxDir, ".", OREAD);
182 				if(fd < 0)
183 					return 0;
184 				mbox = "";
185 				nmdir = 0;
186 			}else{
187 				*mdir = '\0';
188 				fd = cdOpen(mboxDir, mbox, OREAD);
189 				*mdir = '/';
190 				nmdir = mdir - mbox + 1;
191 				if(fd < 0)
192 					return 0;
193 				dir = dirfstat(fd);
194 				if(dir == nil){
195 					close(fd);
196 					return 0;
197 				}
198 				mode = dir->mode;
199 				free(dir);
200 				if(!(mode & DMDIR))
201 					break;
202 			}
203 			wc = m;
204 			for(; c = *m; m++)
205 				if(c == '/')
206 					break;
207 			nmb = nmdir + strlen(m) + MboxNameLen + 3;
208 			mb = emalloc(nmb);
209 			strncpy(mb, mbox, nmdir);
210 			ok = 0;
211 			while((nd = dirread(fd, &dirs)) > 0){
212 				for(i = 0; i < nd; i++){
213 					if(strcmp(mbox, "") == 0 &&
214 					    !okMbox(dirs[i].name))
215 						continue;
216 					/* Safety: ignore message dirs */
217 					if(strstr(dirs[i].name, "mails") != 0 ||
218 					   strcmp(dirs[i].name, "out") == 0 ||
219 					   strcmp(dirs[i].name, "obox") == 0 ||
220 					   strcmp(dirs[i].name, "ombox") == 0)
221 						continue;
222 					if(strcmp(dirs[i].name, "msgs") == 0)
223 						dirs[i].mode &= ~DMDIR;
224 					if(*wc == '*' && dirs[i].mode & DMDIR &&
225 					    mayMatch(mm, dirs[i].name, 1)){
226 						snprint(mb+nmdir, nmb-nmdir,
227 							"%s", dirs[i].name);
228 						ok |= listAll(cmd, ref, pat, mb,
229 							dirs[i].mtime);
230 					}else if(mayMatch(mm, dirs[i].name, 0)){
231 						snprint(mb+nmdir, nmb-nmdir,
232 							"%s%s", dirs[i].name, m);
233 						if(*m == '\0')
234 							ok |= checkMatch(cmd,
235 								ref, pat, mb,
236 								dirs[i].mtime,
237 								dirs[i].mode &
238 								DMDIR);
239 						else if(dirs[i].mode & DMDIR)
240 							ok |= listMatch(cmd,
241 								ref, pat, mb, mb
242 								+ nmdir + strlen(
243 								dirs[i].name));
244 					}
245 				}
246 				free(dirs);
247 			}
248 			close(fd);
249 			free(mb);
250 			return ok;
251 		}
252 		if(c == '/'){
253 			mdir = m;
254 			mm = m + 1;
255 		}
256 	}
257 	m = mbox;
258 	if(*mbox == '\0')
259 		m = ".";
260 	dir = cdDirstat(mboxDir, m);
261 	if(dir == nil)
262 		return 0;
263 	ok = checkMatch(cmd, ref, pat, mbox, dir->mtime, (dir->mode & DMDIR) == DMDIR);
264 	free(dir);
265 	return ok;
266 }
267 
268 /*
269  * too hard: recursively list all files rooted at mbox,
270  * and list checkMatch figure it out
271  */
272 static int
listAll(char * cmd,char * ref,char * pat,char * mbox,long mtime)273 listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime)
274 {
275 	Dir *dirs;
276 	char *mb;
277 	int i, nmb, nd, ok, fd;
278 
279 	ok = checkMatch(cmd, ref, pat, mbox, mtime, 1);
280 	fd = cdOpen(mboxDir, mbox, OREAD);
281 	if(fd < 0)
282 		return ok;
283 
284 	nmb = strlen(mbox) + MboxNameLen + 2;
285 	mb = emalloc(nmb);
286 	while((nd = dirread(fd, &dirs)) > 0){
287 		for(i = 0; i < nd; i++){
288 			snprint(mb, nmb, "%s/%s", mbox, dirs[i].name);
289 			/* safety: do not recurr */
290 			if(0 && dirs[i].mode & DMDIR)
291 				ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime);
292 			else
293 				ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, 0);
294 		}
295 		free(dirs);
296 	}
297 	close(fd);
298 	free(mb);
299 	return ok;
300 }
301 
302 static int
mayMatch(char * pat,char * name,int star)303 mayMatch(char *pat, char *name, int star)
304 {
305 	Rune r;
306 	int i, n;
307 
308 	for(; *pat && *pat != '/'; pat += n){
309 		r = *(uchar*)pat;
310 		if(r < Runeself)
311 			n = 1;
312 		else
313 			n = chartorune(&r, pat);
314 
315 		if(r == '*' || r == '%'){
316 			pat += n;
317 			if(r == '*' && star || *pat == '\0' || *pat == '/')
318 				return 1;
319 			while(*name){
320 				if(mayMatch(pat, name, star))
321 					return 1;
322 				name += chartorune(&r, name);
323 			}
324 			return 0;
325 		}
326 		for(i = 0; i < n; i++)
327 			if(name[i] != pat[i])
328 				return 0;
329 		name += n;
330 	}
331 	if(*name == '\0')
332 		return 1;
333 	return 0;
334 }
335 
336 /*
337  * mbox is a mailbox name which might match pat.
338  * verify the match
339  * generates response
340  */
341 static int
checkMatch(char * cmd,char * ref,char * pat,char * mbox,long mtime,int isdir)342 checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir)
343 {
344 	char *s, *flags;
345 
346 	if(!matches(ref, pat, mbox) || !okMbox(mbox))
347 		return 0;
348 	if(strcmp(mbox, ".") == 0)
349 		mbox = "";
350 
351 	if(isdir)
352 		flags = "(\\Noselect)";
353 	else{
354 		s = impName(mbox);
355 		if(s != nil && listMtime(s) < mtime)
356 			flags = "(\\Noinferiors \\Marked)";
357 		else
358 			flags = "(\\Noinferiors)";
359 	}
360 
361 	s = strmutf7(mbox);
362 	if(s != nil)
363 		Bprint(&bout, "* %s %s \"/\" \"%s\"\r\n", cmd, flags, s);
364 	return 1;
365 }
366 
367 static int
matches(char * ref,char * pat,char * name)368 matches(char *ref, char *pat, char *name)
369 {
370 	Rune r;
371 	int i, n;
372 
373 	while(ref != pat)
374 		if(*name++ != *ref++)
375 			return 0;
376 	for(; *pat; pat += n){
377 		r = *(uchar*)pat;
378 		if(r < Runeself)
379 			n = 1;
380 		else
381 			n = chartorune(&r, pat);
382 
383 		if(r == '*'){
384 			pat += n;
385 			if(*pat == '\0')
386 				return 1;
387 			while(*name){
388 				if(matches(pat, pat, name))
389 					return 1;
390 				name += chartorune(&r, name);
391 			}
392 			return 0;
393 		}
394 		if(r == '%'){
395 			pat += n;
396 			while(*name && *name != '/'){
397 				if(matches(pat, pat, name))
398 					return 1;
399 				name += chartorune(&r, name);
400 			}
401 			pat -= n;
402 			continue;
403 		}
404 		for(i = 0; i < n; i++)
405 			if(name[i] != pat[i])
406 				return 0;
407 		name += n;
408 	}
409 	if(*name == '\0')
410 		return 1;
411 	return 0;
412 }
413