src/server/daemon/vfs.c

Thu, 09 May 2013 19:41:11 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 09 May 2013 19:41:11 +0200
changeset 62
c47e081b6c0f
parent 59
ab25c0a231d0
child 63
66442f81f823
permissions
-rw-r--r--

added keyfile based authentication

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

#include "../util/pool.h"
#include "../ucx/map.h"
#include "vfs.h"

static UcxMap *vfs_map;

static VFS_IO sys_file_io = {
    sys_file_read,
    sys_file_write,
    sys_file_close
};

static VFS_DIRIO sys_dir_io = {
    sys_dir_read,
    sys_dir_close
};

int vfs_init() {
    vfs_map = ucx_map_new(16);
    if(!vfs_map) {
        return -1;
    }
    return 0;
}

void vfs_add(char *name, VFS *vfs) {
    if(!vfs_map) {
        vfs_init();
    }
    ucx_map_cstr_put(vfs_map, name, vfs);
}

VFSContext* vfs_request_context(Session *sn, Request *rq) {
    VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext));
    ctx->sn = sn;
    ctx->rq = rq;
    ctx->vfs = rq->vfs;
    ctx->user = acllist_getuser(sn, rq, rq->acllist);
    ctx->acllist = rq->acllist;
    ctx->aclreqaccess = rq->aclreqaccess;
    ctx->pool = sn->pool;
    ctx->vfs_errno = 0;
    return ctx;
}

SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags) {
    Session *sn;
    Request *rq;
    pool_handle_t *pool;
    uint32_t access_mask;
    
    if(ctx) {
        access_mask = ctx->aclreqaccess;
        access_mask |= acl_oflag2mask(oflags);
        if(!ctx->pool) {
            // TODO: log warning
            // broken VFSContext
        }
        if(ctx->vfs) {
            // ctx->aclreqaccess should be the complete access mask
            uint32_t m = ctx->aclreqaccess; // save original access mask
            ctx->aclreqaccess = access_mask; // set mask for vfs->open call
            SYS_FILE file = ctx->vfs->open(ctx, path, oflags);
            ctx->aclreqaccess = m; // restore original access mask
            return file;
        } else {
            pool = ctx->pool;
        }
    } else {
        sn = NULL;
        rq = NULL;
        pool = NULL;
        access_mask = acl_oflag2mask(oflags);
    }
    
    // check ACLs
    uid_t uid; // uid and gid will be initialized by sys_acl_check
    gid_t gid;
    if(sys_acl_check(ctx, access_mask, &uid, &gid)) {
        return NULL;
    }
    
    // open file
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    int fd = open(path, oflags, mode);
    if(fd == -1) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    
    VFSFile *file = pool ?
            pool_malloc(pool, sizeof(VFSFile)) : malloc(sizeof(VFSFile));
    if(!file) {
        close(fd);
        return NULL;
    }
    file->ctx = ctx;
    file->data = NULL;
    file->fd = fd;
    file->io = &sys_file_io;
    return file;
}

SYS_FILE vfs_openRO(VFSContext *ctx, char *path) {
    return vfs_open(ctx, path, O_RDONLY);
}

SYS_FILE vfs_openWO(VFSContext *ctx, char *path) {
    return vfs_open(ctx, path, O_WRONLY | O_CREAT);
}

SYS_FILE vfs_openRW(VFSContext *ctx, char *path) {
    return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT);
}

int vfs_stat(VFSContext *ctx, char *path, struct stat *buf) {
    Session *sn;
    Request *rq;
    uint32_t access_mask;
    
    if(ctx) {
        access_mask = ctx->aclreqaccess;
        access_mask |= ACL_READ_ATTRIBUTES;
        if(!ctx->pool) {
            // TODO: log warning
            // broken VFSContext
        }
        if(ctx->vfs) {
            // ctx->aclreqaccess should be the complete access mask
            uint32_t m = ctx->aclreqaccess; // save original access mask
            ctx->aclreqaccess = access_mask; // set mask for vfs->fstat call
            int ret = ctx->vfs->stat(ctx, path, buf);
            ctx->aclreqaccess = m; // restore original access mask
            return ret;
        }
    } else {
        sn = NULL;
        rq = NULL;
        access_mask = ACL_READ_ATTRIBUTES;
    }
    
    // check ACLs
    uid_t uid; // uid and gid will be initialized by sys_acl_check
    gid_t gid;
    if(sys_acl_check(ctx, access_mask, &uid, &gid)) {
        return -1;
    }
    
    // stat
    if(stat(path, buf)) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return -1;
    }
    
    return 0;
}

int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { 
    if(ctx) {
        if(!ctx->pool) {
            // TODO: log warning
            // broken VFSContext
        }
        if(ctx->vfs) {
            return ctx->vfs->fstat(ctx, fd, buf);
        }
    }
    
    // stat
    if(fstat(fd->fd, buf)) {
        if(ctx) {
            ctx->vfs_errno = errno;
        }
        return -1;
    }
    
    return 0;
}

void vfs_close(SYS_FILE fd) {
    fd->io->close(fd);
    if(fd->ctx) {
        pool_free(fd->ctx->pool, fd);
    } else {
        free(fd);
    }
}

VFS_DIR vfs_opendir(VFSContext *ctx, char *path) {
    Session *sn;
    Request *rq;
    pool_handle_t *pool;
    uint32_t access_mask;
    
    if(ctx) {
        access_mask = ctx->aclreqaccess;
        access_mask |= ACL_LIST;
        if(!ctx->pool) {
            // TODO: log warning
            // broken VFSContext
        }
        if(ctx->vfs) {
            // ctx->aclreqaccess should be the complete access mask
            uint32_t m = ctx->aclreqaccess; // save original access mask
            ctx->aclreqaccess = access_mask; // set mask for vfs->opendir call
            VFS_DIR dir = ctx->vfs->opendir(ctx, path);
            ctx->aclreqaccess = m; // restore original access mask
            return dir;
        } else {
            pool = ctx->pool;
        }
    } else {
        sn = NULL;
        rq = NULL;
        pool = NULL;
        access_mask = ACL_LIST;
    }
    
    // check ACLs
    uid_t uid; // uid and gid will be initialized by sys_acl_check
    gid_t gid;
    if(sys_acl_check(ctx, access_mask, &uid, &gid)) {
        return NULL;
    }
    
    // open file
    int sys_fd = open(path, O_RDONLY);
    if(sys_fd == -1) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    
    DIR *sys_dir = fdopendir(sys_fd);
    if(!sys_dir) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    
    VFSDir *dir = pool ?
            pool_malloc(pool, sizeof(VFSDir)) : malloc(sizeof(VFSDir));
    if(!dir) {
        closedir(sys_dir);
        return NULL;
    }
    dir->ctx = ctx;
    dir->data = sys_dir;
    dir->fd = sys_fd;
    dir->io = &sys_dir_io;
    return dir;
}

int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry) {
    return dir->io->readdir(dir, entry, 0);
}

int vfs_readdir_stat(VFS_DIR dir, VFS_ENTRY *entry) {
    return dir->io->readdir(dir, entry, 1);
}

void vfs_closedir(VFS_DIR dir) {
    dir->io->close(dir);
    if(dir->ctx) {
        pool_free(dir->ctx->pool, dir);
    } else {
        free(dir);
    }
}

int vfs_mkdir(VFSContext *ctx, char *path) {
    if(ctx && ctx->vfs) {
        return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE);
    } else {
        return vfs_path_op(ctx, path, sys_mkdir, ACL_ADD_FILE);
    }
}

int vfs_unlink(VFSContext *ctx, char *path) {
    if(ctx && ctx->vfs) {
        return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE);
    } else {
        return vfs_path_op(ctx, path, sys_unlink, ACL_DELETE);
    }
}


// private
int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) {
    Session *sn;
    Request *rq;
    uint32_t access_mask;
    
    if(ctx) {
        access_mask = ctx->aclreqaccess;
        access_mask |= access;
        if(!ctx->pool) {
            // TODO: log warning
            // broken VFSContext
        }
        if(ctx->vfs) {
            // ctx->aclreqaccess should be the complete access mask
            uint32_t m = ctx->aclreqaccess; // save original access mask
            ctx->aclreqaccess = access_mask; // set mask for vfs->fstat call
            int ret = op(ctx, path);
            ctx->aclreqaccess = m; // restore original access mask
            return ret;
        }
    } else {
        sn = NULL;
        rq = NULL;
        access_mask = access;
    }
    
    // check ACLs
    uid_t uid; // uid and gid will be initialized by sys_acl_check
    gid_t gid;
    if(sys_acl_check(ctx, access_mask, &uid, &gid)) {
        return -1;
    }
    
    // do path operation
    if(op(ctx, path)) {
        // error
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return -1;
    }
    
    return 0;
}

int sys_acl_check(VFSContext *ctx, uint32_t acm, uid_t *uid, gid_t *gid) {
    /*
     * we don't allow remote root access, so a uid of 0 means that
     * no file system acl check is needed
     */
    *uid = 0;
    *gid = 0;
    if(!ctx) {
        return 0;
    }
    
    ACLListHandle *acllist = ctx->acllist;
    if(acllist) {
        ACLListElm *elm = acllist->listhead;
        while(elm) {
            ACLList *acl = elm->acl;
            if(acl->isextern) {
                // TODO
            } else if(!acl->check(acl, ctx->user, acm)) {
                // access denied
                if(ctx->sn && ctx->rq) {
                    acl_set_error_status(ctx->sn, ctx->rq, acl, ctx->user);
                }
                return 1;        
            } 
            elm = elm->next;
        }
    }
    
    return 0;
}

void sys_set_error_status(VFSContext *ctx) {
    if(ctx->sn && ctx->rq) {
        int status = util_errno2status(ctx->vfs_errno);
        protocol_status(ctx->sn, ctx->rq, status, NULL);
    }
}

ssize_t sys_file_read(SYS_FILE fd, void *buf, size_t nbyte) {
    return read(fd->fd, buf, nbyte);
}

ssize_t sys_file_write(SYS_FILE fd, const void *buf, size_t nbyte) {
    return write(fd->fd, buf, nbyte);
}

void sys_file_close(SYS_FILE fd) {
    close(fd->fd);
}

int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat) {
    struct dirent *e = readdir(dir->data);
    if(e) {
        char *name = e->d_name;
        if(!strcmp(name, ".") || !strcmp(name, "..")) {
            return sys_dir_read(dir, entry, getstat);
        } else {
            entry->name = name;
            if(getstat) {
                // TODO: check ACLs again for new path
                if(fstatat(dir->fd, e->d_name, &entry->stat, 0)) {
                    entry->stat_errno = errno;
                }
                entry->stat_extra = NULL;
            }
            return 1;
        }
    } else {
        return 0;
    }
}

void sys_dir_close(VFS_DIR dir) {
    closedir(dir->data);
}

int sys_mkdir(VFSContext *ctx, char *path) {
    mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
    return mkdir(path, mode);
}

int sys_unlink(VFSContext *ctx, char *path) {
    return unlink(path);
}

/* public file api */

NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) {
    return fd->io->read(fd, buf, nbyte);
}

NSAPI_PUBLIC int system_fwrite(SYS_FILE fd, const void *buf, int nbyte) {
    return fd->io->write(fd, buf, nbyte);
}

NSAPI_PUBLIC int system_fclose(SYS_FILE fd) {
    vfs_close(fd);
    return 0;
}

mercurial