/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ #include <u.h>#include <libc.h>#include <bio.h>#include <auth.h>#include "imap4d.h" #define SUBSCRIBED "imap.subscribed" static int matches(char *ref, char *pat, char *name);static int mayMatch(char *pat, char *name, int star);static int checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir);static int listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime);static int listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm);static int mkSubscribed(void); static longlistMtime(char *file){ Dir *d; long mtime; d = cdDirstat(mboxDir, file); if(d == nil) return 0; mtime = d->mtime; free(d); return mtime;} /* * check for subscribed mailboxes * each line is either a comment starting with # * or is a subscribed mailbox name */intlsubBoxes(char *cmd, char *ref, char *pat){ MbLock *mb; Dir *d; Biobuf bin; char *s; long mtime; int fd, ok, isdir; mb = mbLock(); if(mb == nil) return 0; fd = cdOpen(mboxDir, SUBSCRIBED, OREAD); if(fd < 0) fd = mkSubscribed(); if(fd < 0){ mbUnlock(mb); return 0; } ok = 0; Binit(&bin, fd, OREAD); while(s = Brdline(&bin, '\n')){ s[Blinelen(&bin) - 1] = '\0'; if(s[0] == '#') continue; isdir = 1; if(cistrcmp(s, "INBOX") == 0){ if(access("msgs", AEXIST) == 0) mtime = listMtime("msgs"); else mtime = listMtime("mbox"); isdir = 0; }else{ d = cdDirstat(mboxDir, s); if(d != nil){ mtime = d->mtime; if(!(d->mode & DMDIR)) isdir = 0; free(d); }else mtime = 0; } ok |= checkMatch(cmd, ref, pat, s, mtime, isdir); } Bterm(&bin); close(fd); mbUnlock(mb); return ok;} static intmkSubscribed(void){ int fd; fd = cdCreate(mboxDir, SUBSCRIBED, ORDWR, 0664); if(fd < 0) return -1; fprint(fd, "#imap4 subscription list\nINBOX\n"); seek(fd, 0, 0); return fd;} /* * either subscribe or unsubscribe to a mailbox */intsubscribe(char *mbox, int how){ MbLock *mb; char *s, *in, *ein; int fd, tfd, ok, nmbox; if(cistrcmp(mbox, "inbox") == 0) mbox = "INBOX"; mb = mbLock(); if(mb == nil) return 0; fd = cdOpen(mboxDir, SUBSCRIBED, ORDWR); if(fd < 0) fd = mkSubscribed(); if(fd < 0){ mbUnlock(mb); return 0; } in = readFile(fd); if(in == nil){ mbUnlock(mb); return 0; } nmbox = strlen(mbox); s = strstr(in, mbox); while(s != nil && (s != in && s[-1] != '\n' || s[nmbox] != '\n')) s = strstr(s+1, mbox); ok = 0; if(how == 's' && s == nil){ if(fprint(fd, "%s\n", mbox) > 0) ok = 1; }else if(how == 'u' && s != nil){ ein = strchr(s, '\0'); memmove(s, &s[nmbox+1], ein - &s[nmbox+1]); ein -= nmbox+1; tfd = cdOpen(mboxDir, SUBSCRIBED, OWRITE|OTRUNC); if(tfd >= 0 && seek(fd, 0, 0) >= 0 && write(fd, in, ein-in) == ein-in) ok = 1; if(tfd > 0) close(tfd); }else ok = 1; close(fd); mbUnlock(mb); return ok;} /* * stupidly complicated so that % doesn't read entire directory structure * yet * works * note: in most places, inbox is case-insensitive, * but here INBOX is checked for a case-sensitve match. */intlistBoxes(char *cmd, char *ref, char *pat){ int ok; ok = checkMatch(cmd, ref, pat, "INBOX", listMtime("mbox"), 0); return ok | listMatch(cmd, ref, pat, ref, pat);} /* * look for all messages which may match the pattern * punt when a * is reached */static intlistMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm){ Dir *dir, *dirs; char *mdir, *m, *mb, *wc; long mode; int c, i, nmb, nmdir, nd, ok, fd; mdir = nil; for(m = mm; c = *m; m++){ if(c == '%' || c == '*'){ if(mdir == nil){ fd = cdOpen(mboxDir, ".", OREAD); if(fd < 0) return 0; mbox = ""; nmdir = 0; }else{ *mdir = '\0'; fd = cdOpen(mboxDir, mbox, OREAD); *mdir = '/'; nmdir = mdir - mbox + 1; if(fd < 0) return 0; dir = dirfstat(fd); if(dir == nil){ close(fd); return 0; } mode = dir->mode; free(dir); if(!(mode & DMDIR)) break; } wc = m; for(; c = *m; m++) if(c == '/') break; nmb = nmdir + strlen(m) + MboxNameLen + 3; mb = emalloc(nmb); strncpy(mb, mbox, nmdir); ok = 0; while((nd = dirread(fd, &dirs)) > 0){ for(i = 0; i < nd; i++){ if(strcmp(mbox, "") == 0 && !okMbox(dirs[i].name)) continue; /* Safety: ignore message dirs */ if(strstr(dirs[i].name, "mails") != 0 || strcmp(dirs[i].name, "out") == 0 || strcmp(dirs[i].name, "obox") == 0 || strcmp(dirs[i].name, "ombox") == 0) continue; if(strcmp(dirs[i].name, "msgs") == 0) dirs[i].mode &= ~DMDIR; if(*wc == '*' && dirs[i].mode & DMDIR && mayMatch(mm, dirs[i].name, 1)){ snprint(mb+nmdir, nmb-nmdir, "%s", dirs[i].name); ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime); }else if(mayMatch(mm, dirs[i].name, 0)){ snprint(mb+nmdir, nmb-nmdir, "%s%s", dirs[i].name, m); if(*m == '\0') ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, dirs[i].mode & DMDIR); else if(dirs[i].mode & DMDIR) ok |= listMatch(cmd, ref, pat, mb, mb + nmdir + strlen( dirs[i].name)); } } free(dirs); } close(fd); free(mb); return ok; } if(c == '/'){ mdir = m; mm = m + 1; } } m = mbox; if(*mbox == '\0') m = "."; dir = cdDirstat(mboxDir, m); if(dir == nil) return 0; ok = checkMatch(cmd, ref, pat, mbox, dir->mtime, (dir->mode & DMDIR) == DMDIR); free(dir); return ok;} /* * too hard: recursively list all files rooted at mbox, * and list checkMatch figure it out */static intlistAll(char *cmd, char *ref, char *pat, char *mbox, long mtime){ Dir *dirs; char *mb; int i, nmb, nd, ok, fd; ok = checkMatch(cmd, ref, pat, mbox, mtime, 1); fd = cdOpen(mboxDir, mbox, OREAD); if(fd < 0) return ok; nmb = strlen(mbox) + MboxNameLen + 2; mb = emalloc(nmb); while((nd = dirread(fd, &dirs)) > 0){ for(i = 0; i < nd; i++){ snprint(mb, nmb, "%s/%s", mbox, dirs[i].name); /* safety: do not recurr */ if(0 && dirs[i].mode & DMDIR) ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime); else ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, 0); } free(dirs); } close(fd); free(mb); return ok;} static intmayMatch(char *pat, char *name, int star){ Rune r; int i, n; for(; *pat && *pat != '/'; pat += n){ r = *(uchar*)pat; if(r < Runeself) n = 1; else n = chartorune(&r, pat); if(r == '*' || r == '%'){ pat += n; if(r == '*' && star || *pat == '\0' || *pat == '/') return 1; while(*name){ if(mayMatch(pat, name, star)) return 1; name += chartorune(&r, name); } return 0; } for(i = 0; i < n; i++) if(name[i] != pat[i]) return 0; name += n; } if(*name == '\0') return 1; return 0;} /* * mbox is a mailbox name which might match pat. * verify the match * generates response */static intcheckMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir){ char *s, *flags; if(!matches(ref, pat, mbox) || !okMbox(mbox)) return 0; if(strcmp(mbox, ".") == 0) mbox = ""; if(isdir) flags = "(\\Noselect)"; else{ s = impName(mbox); if(s != nil && listMtime(s) < mtime) flags = "(\\Noinferiors \\Marked)"; else flags = "(\\Noinferiors)"; } s = strmutf7(mbox); if(s != nil) Bprint(&bout, "* %s %s \"/\" \"%s\"\r\n", cmd, flags, s); return 1;} static intmatches(char *ref, char *pat, char *name){ Rune r; int i, n; while(ref != pat) if(*name++ != *ref++) return 0; for(; *pat; pat += n){ r = *(uchar*)pat; if(r < Runeself) n = 1; else n = chartorune(&r, pat); if(r == '*'){ pat += n; if(*pat == '\0') return 1; while(*name){ if(matches(pat, pat, name)) return 1; name += chartorune(&r, name); } return 0; } if(r == '%'){ pat += n; while(*name && *name != '/'){ if(matches(pat, pat, name)) return 1; name += chartorune(&r, name); } pat -= n; continue; } for(i = 0; i < n; i++) if(name[i] != pat[i]) return 0; name += n; } if(*name == '\0') return 1; return 0;}
/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include "imap4d.h"
#define SUBSCRIBED "imap.subscribed"
static int matches(char *ref, char *pat, char *name);
static int mayMatch(char *pat, char *name, int star);
static int checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir);
static int listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime);
static int listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm);
static int mkSubscribed(void);
static long
listMtime(char *file)
{
Dir *d;
long mtime;
d = cdDirstat(mboxDir, file);
if(d == nil)
return 0;
mtime = d->mtime;
free(d);
return mtime;
}
/*
* check for subscribed mailboxes
* each line is either a comment starting with #
* or is a subscribed mailbox name
*/
int
lsubBoxes(char *cmd, char *ref, char *pat)
{
MbLock *mb;
Dir *d;
Biobuf bin;
char *s;
long mtime;
int fd, ok, isdir;
mb = mbLock();
if(mb == nil)
return 0;
fd = cdOpen(mboxDir, SUBSCRIBED, OREAD);
if(fd < 0)
fd = mkSubscribed();
if(fd < 0){
mbUnlock(mb);
return 0;
}
ok = 0;
Binit(&bin, fd, OREAD);
while(s = Brdline(&bin, '\n')){
s[Blinelen(&bin) - 1] = '\0';
if(s[0] == '#')
continue;
isdir = 1;
if(cistrcmp(s, "INBOX") == 0){
if(access("msgs", AEXIST) == 0)
mtime = listMtime("msgs");
else
mtime = listMtime("mbox");
isdir = 0;
}else{
d = cdDirstat(mboxDir, s);
if(d != nil){
mtime = d->mtime;
if(!(d->mode & DMDIR))
isdir = 0;
free(d);
}else
mtime = 0;
}
ok |= checkMatch(cmd, ref, pat, s, mtime, isdir);
}
Bterm(&bin);
close(fd);
mbUnlock(mb);
return ok;
}
static int
mkSubscribed(void)
{
int fd;
fd = cdCreate(mboxDir, SUBSCRIBED, ORDWR, 0664);
if(fd < 0)
return -1;
fprint(fd, "#imap4 subscription list\nINBOX\n");
seek(fd, 0, 0);
return fd;
}
/*
* either subscribe or unsubscribe to a mailbox
*/
int
subscribe(char *mbox, int how)
{
MbLock *mb;
char *s, *in, *ein;
int fd, tfd, ok, nmbox;
if(cistrcmp(mbox, "inbox") == 0)
mbox = "INBOX";
mb = mbLock();
if(mb == nil)
return 0;
fd = cdOpen(mboxDir, SUBSCRIBED, ORDWR);
if(fd < 0)
fd = mkSubscribed();
if(fd < 0){
mbUnlock(mb);
return 0;
}
in = readFile(fd);
if(in == nil){
mbUnlock(mb);
return 0;
}
nmbox = strlen(mbox);
s = strstr(in, mbox);
while(s != nil && (s != in && s[-1] != '\n' || s[nmbox] != '\n'))
s = strstr(s+1, mbox);
ok = 0;
if(how == 's' && s == nil){
if(fprint(fd, "%s\n", mbox) > 0)
ok = 1;
}else if(how == 'u' && s != nil){
ein = strchr(s, '\0');
memmove(s, &s[nmbox+1], ein - &s[nmbox+1]);
ein -= nmbox+1;
tfd = cdOpen(mboxDir, SUBSCRIBED, OWRITE|OTRUNC);
if(tfd >= 0 && seek(fd, 0, 0) >= 0 && write(fd, in, ein-in) == ein-in)
ok = 1;
if(tfd > 0)
close(tfd);
}else
ok = 1;
close(fd);
mbUnlock(mb);
return ok;
}
/*
* stupidly complicated so that % doesn't read entire directory structure
* yet * works
* note: in most places, inbox is case-insensitive,
* but here INBOX is checked for a case-sensitve match.
*/
int
listBoxes(char *cmd, char *ref, char *pat)
{
int ok;
ok = checkMatch(cmd, ref, pat, "INBOX", listMtime("mbox"), 0);
return ok | listMatch(cmd, ref, pat, ref, pat);
}
/*
* look for all messages which may match the pattern
* punt when a * is reached
*/
static int
listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm)
{
Dir *dir, *dirs;
char *mdir, *m, *mb, *wc;
long mode;
int c, i, nmb, nmdir, nd, ok, fd;
mdir = nil;
for(m = mm; c = *m; m++){
if(c == '%' || c == '*'){
if(mdir == nil){
fd = cdOpen(mboxDir, ".", OREAD);
if(fd < 0)
return 0;
mbox = "";
nmdir = 0;
}else{
*mdir = '\0';
fd = cdOpen(mboxDir, mbox, OREAD);
*mdir = '/';
nmdir = mdir - mbox + 1;
if(fd < 0)
return 0;
dir = dirfstat(fd);
if(dir == nil){
close(fd);
return 0;
}
mode = dir->mode;
free(dir);
if(!(mode & DMDIR))
break;
}
wc = m;
for(; c = *m; m++)
if(c == '/')
break;
nmb = nmdir + strlen(m) + MboxNameLen + 3;
mb = emalloc(nmb);
strncpy(mb, mbox, nmdir);
ok = 0;
while((nd = dirread(fd, &dirs)) > 0){
for(i = 0; i < nd; i++){
if(strcmp(mbox, "") == 0 &&
!okMbox(dirs[i].name))
continue;
/* Safety: ignore message dirs */
if(strstr(dirs[i].name, "mails") != 0 ||
strcmp(dirs[i].name, "out") == 0 ||
strcmp(dirs[i].name, "obox") == 0 ||
strcmp(dirs[i].name, "ombox") == 0)
continue;
if(strcmp(dirs[i].name, "msgs") == 0)
dirs[i].mode &= ~DMDIR;
if(*wc == '*' && dirs[i].mode & DMDIR &&
mayMatch(mm, dirs[i].name, 1)){
snprint(mb+nmdir, nmb-nmdir,
"%s", dirs[i].name);
ok |= listAll(cmd, ref, pat, mb,
dirs[i].mtime);
}else if(mayMatch(mm, dirs[i].name, 0)){
snprint(mb+nmdir, nmb-nmdir,
"%s%s", dirs[i].name, m);
if(*m == '\0')
ok |= checkMatch(cmd,
ref, pat, mb,
dirs[i].mtime,
dirs[i].mode &
DMDIR);
else if(dirs[i].mode & DMDIR)
ok |= listMatch(cmd,
ref, pat, mb, mb
+ nmdir + strlen(
dirs[i].name));
}
}
free(dirs);
}
close(fd);
free(mb);
return ok;
}
if(c == '/'){
mdir = m;
mm = m + 1;
}
}
m = mbox;
if(*mbox == '\0')
m = ".";
dir = cdDirstat(mboxDir, m);
if(dir == nil)
return 0;
ok = checkMatch(cmd, ref, pat, mbox, dir->mtime, (dir->mode & DMDIR) == DMDIR);
free(dir);
return ok;
}
/*
* too hard: recursively list all files rooted at mbox,
* and list checkMatch figure it out
*/
static int
listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime)
{
Dir *dirs;
char *mb;
int i, nmb, nd, ok, fd;
ok = checkMatch(cmd, ref, pat, mbox, mtime, 1);
fd = cdOpen(mboxDir, mbox, OREAD);
if(fd < 0)
return ok;
nmb = strlen(mbox) + MboxNameLen + 2;
mb = emalloc(nmb);
while((nd = dirread(fd, &dirs)) > 0){
for(i = 0; i < nd; i++){
snprint(mb, nmb, "%s/%s", mbox, dirs[i].name);
/* safety: do not recurr */
if(0 && dirs[i].mode & DMDIR)
ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime);
else
ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, 0);
}
free(dirs);
}
close(fd);
free(mb);
return ok;
}
static int
mayMatch(char *pat, char *name, int star)
{
Rune r;
int i, n;
for(; *pat && *pat != '/'; pat += n){
r = *(uchar*)pat;
if(r < Runeself)
n = 1;
else
n = chartorune(&r, pat);
if(r == '*' || r == '%'){
pat += n;
if(r == '*' && star || *pat == '\0' || *pat == '/')
return 1;
while(*name){
if(mayMatch(pat, name, star))
return 1;
name += chartorune(&r, name);
}
return 0;
}
for(i = 0; i < n; i++)
if(name[i] != pat[i])
return 0;
name += n;
}
if(*name == '\0')
return 1;
return 0;
}
/*
* mbox is a mailbox name which might match pat.
* verify the match
* generates response
*/
static int
checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir)
{
char *s, *flags;
if(!matches(ref, pat, mbox) || !okMbox(mbox))
return 0;
if(strcmp(mbox, ".") == 0)
mbox = "";
if(isdir)
flags = "(\\Noselect)";
else{
s = impName(mbox);
if(s != nil && listMtime(s) < mtime)
flags = "(\\Noinferiors \\Marked)";
else
flags = "(\\Noinferiors)";
}
s = strmutf7(mbox);
if(s != nil)
Bprint(&bout, "* %s %s \"/\" \"%s\"\r\n", cmd, flags, s);
return 1;
}
static int
matches(char *ref, char *pat, char *name)
{
Rune r;
int i, n;
while(ref != pat)
if(*name++ != *ref++)
return 0;
for(; *pat; pat += n){
r = *(uchar*)pat;
if(r < Runeself)
n = 1;
else
n = chartorune(&r, pat);
if(r == '*'){
pat += n;
if(*pat == '\0')
return 1;
while(*name){
if(matches(pat, pat, name))
return 1;
name += chartorune(&r, name);
}
return 0;
}
if(r == '%'){
pat += n;
while(*name && *name != '/'){
if(matches(pat, pat, name))
return 1;
name += chartorune(&r, name);
}
pat -= n;
continue;
}
for(i = 0; i < n; i++)
if(name[i] != pat[i])
return 0;
name += n;
}
if(*name == '\0')
return 1;
return 0;
}