Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include "imap4d.h"

static  int     copyData(int ffd, int tfd, MbLock *ml);
static  MbLock  mLock =
{
        .fd = -1
};

static char curDir[MboxNameLen];

void
resetCurDir(void)
{
        curDir[0] = '\0';
}

int
myChdir(char *dir)
{
        if(strcmp(dir, curDir) == 0)
                return 0;
        if(dir[0] != '/' || strlen(dir) > MboxNameLen)
                return -1;
        strcpy(curDir, dir);
        if(chdir(dir) < 0){
                werrstr("mychdir failed: %r");
                return -1;
        }
        return 0;
}

int
cdCreate(char *dir, char *file, int mode, ulong perm)
{
        if(myChdir(dir) < 0)
                return -1;
        return create(file, mode, perm);
}

Dir*
cdDirstat(char *dir, char *file)
{
        if(myChdir(dir) < 0)
                return nil;
        return dirstat(file);
}

int
cdExists(char *dir, char *file)
{
        Dir *d;

        d = cdDirstat(dir, file);
        if(d == nil)
                return 0;
        free(d);
        return 1;
}

int
cdDirwstat(char *dir, char *file, Dir *d)
{
        if(myChdir(dir) < 0)
                return -1;
        return dirwstat(file, d);
}

int
cdOpen(char *dir, char *file, int mode)
{
        if(myChdir(dir) < 0)
                return -1;
        return open(file, mode);
}

int
cdRemove(char *dir, char *file)
{
        if(myChdir(dir) < 0)
                return -1;
        return remove(file);
}

/*
 * open the one true mail lock file
 */
MbLock*
mbLock(void)
{
        int i;

        if(mLock.fd >= 0)
                bye("mail lock deadlock");
        for(i = 0; i < 5; i++){
                mLock.fd = openLocked(mboxDir, "L.mbox", OREAD);
                if(mLock.fd >= 0)
                        return &mLock;
                sleep(1000);
        }
        return nil;
}

void
mbUnlock(MbLock *ml)
{
        if(ml != &mLock)
                bye("bad mail unlock");
        if(ml->fd < 0)
                bye("mail unlock when not locked");
        close(ml->fd);
        ml->fd = -1;
}

void
mbLockRefresh(MbLock *ml)
{
        char buf[1];

        seek(ml->fd, 0, 0);
        read(ml->fd, buf, 1);
}

int
mbLocked(void)
{
        return mLock.fd >= 0;
}

char*
impName(char *name)
{
        char *s;
        int n;

        if(cistrcmp(name, "inbox") == 0)
                if(access("msgs", AEXIST) == 0)
                        name = "msgs";
                else
                        name = "mbox";
        n = strlen(name) + STRLEN(".imp") + 1;
        s = binalloc(&parseBin, n, 0);
        if(s == nil)
                return nil;
        snprint(s, n, "%s.imp", name);
        return s;
}

/*
 * massage the mailbox name into something valid
 * eliminates all .', and ..',s, redundatant and trailing /'s.
 */
char *
mboxName(char *s)
{
        char *ss;

        ss = mutf7str(s);
        if(ss == nil)
                return nil;
        cleanname(ss);
        return ss;
}

char *
strmutf7(char *s)
{
        char *m;
        int n;

        n = strlen(s) * MUtf7Max + 1;
        m = binalloc(&parseBin, n, 0);
        if(m == nil)
                return nil;
        if(encmutf7(m, n, s) < 0)
                return nil;
        return m;
}

char *
mutf7str(char *s)
{
        char *m;
        int n;

        /*
         * n = strlen(s) * UTFmax / (2.67) + 1
         * UTFMax / 2.67 == 3 / (8/3) == 9 / 8
         */
        n = strlen(s);
        n = (n * 9 + 7) / 8 + 1;
        m = binalloc(&parseBin, n, 0);
        if(m == nil)
                return nil;
        if(decmutf7(m, n, s) < 0)
                return nil;
        return m;
}

void
splitr(char *s, int c, char **left, char **right)
{
        char *d;
        int n;

        n = strlen(s);
        d = binalloc(&parseBin, n + 1, 0);
        if(d == nil)
                parseErr("out of memory");
        strcpy(d, s);
        s = strrchr(d, c);
        if(s != nil){
                *left = d;
                *s++ = '\0';
                *right = s;
        }else{
                *right = d;
                *left = d + n;
        }
}

/*
 * create the mailbox and all intermediate components
 * a trailing / implies the new mailbox is a directory;
 * otherwise, it's a file.
 *
 * return with the file open for write, or directory open for read.
 */
int
createBox(char *mbox, int dir)
{
        char *m;
        int fd;

        fd = -1;
        for(m = mbox; *m; m++){
                if(*m == '/'){
                        *m = '\0';
                        if(access(mbox, AEXIST) < 0){
                                if(fd >= 0)
                                        close(fd);
                                fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
                                if(fd < 0)
                                        return -1;
                        }
                        *m = '/';
                }
        }
        if(dir)
                fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
        else
                fd = cdCreate(mboxDir, mbox, OWRITE, 0664);
        return fd;
}

/*
 * move one mail folder to another
 * destination mailbox doesn't exist.
 * the source folder may be a directory or a mailbox,
 * and may be in the same directory as the destination,
 * or a completely different directory.
 */
int
moveBox(char *from, char *to)
{
        Dir *d;
        char *fd, *fe, *td, *te, *fimp;

        splitr(from, '/', &fd, &fe);
        splitr(to, '/', &td, &te);

        /*
         * in the same directory: try rename
         */
        d = cdDirstat(mboxDir, from);
        if(d == nil)
                return 0;
        if(strcmp(fd, td) == 0){
                nulldir(d);
                d->name = te;
                if(cdDirwstat(mboxDir, from, d) >= 0){
                        fimp = impName(from);
                        d->name = impName(te);
                        cdDirwstat(mboxDir, fimp, d);
                        free(d);
                        return 1;
                }
        }

        /*
         * directory copy is too hard for now
         */
        if(d->mode & DMDIR)
                return 0;
        free(d);

        return copyBox(from, to, 1);
}

/*
 * copy the contents of one mailbox to another
 * either truncates or removes the source box if it succeeds.
 */
int
copyBox(char *from, char *to, int doremove)
{
        MbLock *ml;
        char *fimp, *timp;
        int ffd, tfd, ok;

        if(cistrcmp(from, "inbox") == 0)
                if(access("msgs", AEXIST) == 0)
                        from = "msgs";
                else
                        from = "mbox";

        ml = mbLock();
        if(ml == nil)
                return 0;
        ffd = openLocked(mboxDir, from, OREAD);
        if(ffd < 0){
                mbUnlock(ml);
                return 0;
        }
        tfd = createBox(to, 0);
        if(tfd < 0){
                mbUnlock(ml);
                close(ffd);
                return 0;
        }

        ok = copyData(ffd, tfd, ml);
        close(ffd);
        close(tfd);
        if(!ok){
                mbUnlock(ml);
                return 0;
        }

        fimp = impName(from);
        timp = impName(to);
        if(fimp != nil && timp != nil){
                ffd = cdOpen(mboxDir, fimp, OREAD);
                if(ffd >= 0){
                        tfd = cdCreate(mboxDir, timp, OWRITE, 0664);
                        if(tfd >= 0){
                                copyData(ffd, tfd, ml);
                                close(tfd);
                        }
                        close(ffd);
                }
        }
        cdRemove(mboxDir, fimp);
        if(doremove)
                cdRemove(mboxDir, from);
        else
                close(cdOpen(mboxDir, from, OWRITE|OTRUNC));
        mbUnlock(ml);
        return 1;
}

/*
 * copies while holding the mail lock,
 * then tries to copy permissions and group ownership
 */
static int
copyData(int ffd, int tfd, MbLock *ml)
{
        Dir *fd, td;
        char buf[BufSize];
        int n;

        for(;;){
                n = read(ffd, buf, BufSize);
                if(n <= 0){
                        if(n < 0)
                                return 0;
                        break;
                }
                if(write(tfd, buf, n) != n)
                        return 0;
                mbLockRefresh(ml);
        }
        fd = dirfstat(ffd);
        if(fd != nil){
                nulldir(&td);
                td.mode = fd->mode;
                if(dirfwstat(tfd, &td) >= 0){
                        nulldir(&td);
                        td.gid = fd->gid;
                        dirfwstat(tfd, &td);
                }
        }
        return 1;
}