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