# HG changeset patch # User Olaf Wintermann # Date 1368271706 -7200 # Node ID 66442f81f8239cb86e8af87d723224aa802786c8 # Parent c47e081b6c0ff7b9fbc41b46e084ba88410c3b69 supports file system ACLs on Solaris diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/acl.c --- a/src/server/daemon/acl.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/acl.c Sat May 11 13:28:26 2013 +0200 @@ -29,6 +29,7 @@ #include #include +#include "../util/util.h" #include "../util/pool.h" #include "../safs/auth.h" #include "acl.h" @@ -122,6 +123,10 @@ } void acl_set_error_status(Session *sn, Request *rq, ACLList *acl, User *user) { + if(sn == NULL || rq == NULL) { + return; + } + if(!user) { char *value = NULL; if(acl->authprompt) { @@ -159,7 +164,7 @@ User *user = acllist_getuser(sn, rq, list); // evalutate all ACLs - ACLList *acl = acl_evallist(list, user, access_mask); + ACLList *acl = acl_evallist(list, user, access_mask, NULL); if(acl) { acl_set_error_status(sn, rq, acl, user); // TODO: don't free the user here @@ -177,16 +182,29 @@ return REQ_PROCEED; } -ACLList* acl_evallist(ACLListHandle *list, User *user, int access_mask) { +ACLList* acl_evallist( + ACLListHandle *list, + User *user, + int access_mask, + ACLList **externacl) +{ if(!list) { return NULL; } + if(externacl) { + *externacl = NULL; + } // evaluate each acl until one denies access ACLListElm *elm = list->listhead; while(elm) { ACLList *acl = elm->acl; - if(!acl->check(acl, user, access_mask)) { + if(acl->isextern) { + // set externacl to the first external acl + if(externacl && *externacl == NULL) { + *externacl = acl; + } + } else if(!acl->check(acl, user, access_mask)) { // the acl denies access return acl; } @@ -266,3 +284,269 @@ return allow; // allow is 0, if no ace set it to 1 } + + +/* filesystem acl functions */ + +#if defined (__SVR4) && defined (__sun) + +#include + +int solaris_acl_check( + char *path, + struct stat *s, + uint32_t mask, + uid_t uid, + gid_t gid); +int solaris_acl_affects_user( + ace_t *ace, + uid_t uid, + gid_t gid, + uid_t owner, + gid_t owninggroup); + +int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) { + sstr_t p; + if(path[0] != '/') { + size_t n = 128; + char *cwd = malloc(n); + while(!getcwd(cwd, n)) { + if(errno == ERANGE) { + n *= 2; + cwd = realloc(cwd, n); + } else { + free(cwd); + return 0; + } + } + sstr_t wd = sstr(cwd); + sstr_t pp = sstr(path); + p.length = wd.length + pp.length + 1; + p.ptr = malloc(p.length + 1); + p = sstrncat(3, p, wd, sstrn("/", 1), pp); + p.ptr[p.length] = '\0'; + } else { + p = sstrdup(sstr(path)); + } + if(p.ptr[p.length-1] == '/') { + p.ptr[p.length-1] = 0; + p.length--; + } + + // get uid/gid + struct passwd pw; + if(user) { + char *pwbuf = malloc(DEF_PWBUF); + if(pwbuf == NULL) { + free(p.ptr); + return 0; + } + if(!util_getpwnam(user->name, &pw, pwbuf, DEF_PWBUF)) { + free(pwbuf); + free(p.ptr); + return 0; + } + free(pwbuf); + acl->user_uid = pw.pw_uid; + acl->user_gid = pw.pw_gid; + } else { + acl->user_uid = -1; + acl->user_gid = -1; + } + + // translate access_mask + uint32_t mask = 0; + if((access_mask & ACL_READ_DATA) == ACL_READ_DATA) { + mask |= ACE_READ_DATA; + } + if((access_mask & ACL_WRITE_DATA) == ACL_WRITE_DATA) { + mask |= ACE_WRITE_DATA; + } + if((access_mask & ACL_ADD_FILE) == ACL_ADD_FILE) { + mask |= ACE_ADD_FILE; + } + if((access_mask & ACL_READ_XATTR) == ACL_READ_XATTR) { + mask |= ACE_READ_NAMED_ATTRS; + } + if((access_mask & ACL_WRITE_XATTR) == ACL_WRITE_XATTR) { + mask |= ACE_WRITE_NAMED_ATTRS; + } + if((access_mask & ACL_EXECUTE) == ACL_EXECUTE) { + mask |= ACE_EXECUTE; + } + if((access_mask & ACL_DELETE) == ACL_DELETE) { + mask |= ACE_DELETE_CHILD; + } + if((access_mask & ACL_READ_ATTRIBUTES) == ACL_READ_ATTRIBUTES) { + mask |= ACE_READ_ATTRIBUTES; + } + if((access_mask & ACL_WRITE_ATTRIBUTES) == ACL_WRITE_ATTRIBUTES) { + mask |= ACE_WRITE_ATTRIBUTES; + } + if((access_mask & ACL_LIST) == ACL_LIST) { + mask |= ACE_LIST_DIRECTORY; + } + if((access_mask & ACL_READ_ACL) == ACL_READ_ACL) { + mask |= ACE_READ_ACL; + } + if((access_mask & ACL_WRITE_ACL) == ACL_WRITE_ACL) { + mask |= ACE_WRITE_ACL; + } + if((access_mask & ACL_WRITE_OWNER) == ACL_WRITE_OWNER) { + mask |= ACE_WRITE_OWNER; + } + if((access_mask & ACL_SYNCHRONIZE) == ACL_SYNCHRONIZE) { + mask |= ACE_SYNCHRONIZE; + } + + /* + * If the vfs wants to create new files, path does not name an existing + * file. In this case, we check if the user has the ACE_ADD_FILE + * permission for the parent directory + */ + struct stat s; + if(stat(p.ptr, &s)) { + if(errno != ENOENT) { + perror("fs_acl_check: stat"); + free(p.ptr); + return 0; + } else { + mask = ACE_ADD_FILE; + p = util_path_remove_last(p); + if(stat(p.ptr, &s)) { + free(p.ptr); + return 0; + } + } + } + + /* + * perform a acl check for the path and each parent directory + * we don't check the file system root + * + * after the first check, we check only search permission for the + * directories + */ + if(!solaris_acl_check(p.ptr, &s, mask, pw.pw_uid, pw.pw_gid)) { + free(p.ptr); + return 0; + } + + p = util_path_remove_last(p); + mask = ACE_LIST_DIRECTORY; + while(p.length > 1) { + if(stat(p.ptr, &s)) { + free(p.ptr); + return 0; + } + if(!solaris_acl_check(p.ptr, &s, mask, pw.pw_uid, pw.pw_gid)) { + free(p.ptr); + return 0; + } + + // cut the last file name from the path + p = util_path_remove_last(p); + } + + + return 1; +} + +int solaris_acl_check( + char *path, + struct stat *s, + uint32_t mask, + uid_t uid, + gid_t gid) +{ + //printf("solaris_acl_check %s\n", path); + + int nace = acl(path, ACE_GETACLCNT, 0, NULL); + if(nace == -1) { + perror("acl: ACE_GETACLCNT"); + // TODO: log error + return 0; + } + ace_t *aces = calloc(nace, sizeof(ace_t)); + if(acl(path, ACE_GETACL, nace, aces) == 1) { + perror("acl: ACE_GETACL"); + // TODO: log error + free(aces); + return 0; + } + + int allow = 0; + uint32_t allowed_access = 0; + for(int i=0;ist_uid, s->st_gid)) { + if(ace.a_type == ACE_ACCESS_ALLOWED_ACE_TYPE) { + // add all new access rights + allowed_access |= (mask & ace.a_access_mask); + // check if we have all requested rights + if((allowed_access & mask) == mask) { + allow = 1; + break; + } + } else if(ace.a_type == ACE_ACCESS_DENIED_ACE_TYPE) { + // ACL_TYPE_DENIED + + if((ace.a_access_mask & mask) != 0) { + // access denied + break; + } + } + } + } + + free(aces); + + //printf("return %d\n", allow); + return allow; +} + +int solaris_acl_affects_user( + ace_t *ace, + uid_t uid, + gid_t gid, + uid_t owner, + gid_t owninggroup) +{ + /* + * mostly the same as wsacl_affects_user + */ + + int check_access = 0; + + if((ace->a_flags & ACE_OWNER) == ACE_OWNER) { + if(uid == owner) { + check_access = 1; + } + } else if((ace->a_flags & ACE_GROUP) == ACE_GROUP) { + if(gid == owninggroup) { + check_access = 1; + } + } else if((ace->a_flags & ACE_EVERYONE) == ACE_EVERYONE) { + check_access = 1; + } else if(ace->a_who != -1 && uid != 0) { + // this ace is defined for a named user or group + if((ace->a_flags & ACE_IDENTIFIER_GROUP) == ACE_IDENTIFIER_GROUP) { + // TODO: check all groups + if(ace->a_who == gid) { + // the user is in the group + check_access = 1; + } + } else { + if(ace->a_who == uid) { + check_access = 1; + } + } + } + + return check_access; +} + + + +#endif + diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/acl.h --- a/src/server/daemon/acl.h Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/acl.h Sat May 11 13:28:26 2013 +0200 @@ -35,11 +35,21 @@ extern "C" { #endif +typedef struct SysACL { + ACLList *acl; + uid_t user_uid; + gid_t user_gid; +} SysACL; + // private int wsacl_affects_user(WSAce *ace, User *user); int wsacl_check(WSAcl *acl, User *user, int access_mask); +// file system acl functions + +int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask); + #ifdef __cplusplus } #endif diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/config.c --- a/src/server/daemon/config.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/config.c Sat May 11 13:28:26 2013 +0200 @@ -786,6 +786,7 @@ UCX_FOREACH(UcxList*, aclfile->namedACLs, elm) { ACLConfig *ac = elm->data; ACLList *acl = acl_config_convert(cfg, ac); + printf("put acl: %s\n", ac->id.ptr); ucx_map_sstr_put(acldata->namedACLs, ac->id, acl); } free_acl_file(aclfile); @@ -808,6 +809,11 @@ acllist->ace = NULL; acllist->ece = NULL; + if(acl->type.ptr && !sstrcmp(acl->type, sstr("fs"))) { + printf("set acl to extern\n"); + acllist->acl.isextern = 1; + } + size_t s = ucx_list_size(acl->entries); WSAce **aces = calloc(s, sizeof(WSAce*)); WSAce **eces = calloc(s, sizeof(WSAce*)); diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/keyfile_auth.c --- a/src/server/daemon/keyfile_auth.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/keyfile_auth.c Sat May 11 13:28:26 2013 +0200 @@ -84,6 +84,8 @@ KeyfileUser *user = malloc(sizeof(KeyfileUser)); user->user.name = sstrdup(name).ptr; + user->user.uid = -1; + user->user.gid = -1; user->user.verify_password = keyfile_user_verify_password; user->user.check_group = keyfile_user_check_group; user->user.free = keyfile_user_free; @@ -151,8 +153,11 @@ SHA1((const unsigned char*)saltpw, saltpwlen, pwhash); if(!memcmp(user->hash, pwhash, 20)) { + free(pwhash); return 1; } + free(pwhash); + return 0; } diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/ldap_auth.c --- a/src/server/daemon/ldap_auth.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/ldap_auth.c Sat May 11 13:28:26 2013 +0200 @@ -91,12 +91,16 @@ LDAPMessage *msg = ldap_first_entry(ld, result); if (msg) { - LDAPUser *user = malloc(sizeof (LDAPUser)); + LDAPUser *user = malloc(sizeof(LDAPUser)); if (user != NULL) { user->user.verify_password = ldap_user_verify_password; user->user.check_group = ldap_user_check_group; user->user.free = ldap_user_free; user->user.name = username; // must not be freed + + // TODO: get uid/gid from ldap + user->user.uid = -1; + user->user.gid = -1; user->ldap = ld; user->userdn = ldap_get_dn(ld, msg); diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/vfs.c --- a/src/server/daemon/vfs.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/vfs.c Sat May 11 13:28:26 2013 +0200 @@ -33,6 +33,7 @@ #include "../util/pool.h" #include "../ucx/map.h" +#include "acl.h" #include "vfs.h" static UcxMap *vfs_map; @@ -107,12 +108,18 @@ } // 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)) { + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { return NULL; } + if(sysacl.acl) { + if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) { + acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); + return NULL; + } + } + // open file mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int fd = open(path, oflags, mode); @@ -124,6 +131,16 @@ return NULL; } + // if a file system acl is active, we set the owner for newly created files + if(((oflags & O_CREAT) == O_CREAT) && sysacl.user_uid != -1) { + if(fchown(fd, sysacl.user_uid, sysacl.user_gid)) { + perror("vfs_open: fchown"); + close(fd); + return NULL; + } + } + + VFSFile *file = pool ? pool_malloc(pool, sizeof(VFSFile)) : malloc(sizeof(VFSFile)); if(!file) { @@ -176,12 +193,18 @@ } // 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)) { + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { return -1; } + if(sysacl.acl) { + if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) { + acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); + return -1; + } + } + // stat if(stat(path, buf)) { if(ctx) { @@ -256,12 +279,18 @@ } // 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)) { + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { return NULL; } + if(sysacl.acl) { + if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) { + acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); + return NULL; + } + } + // open file int sys_fd = open(path, O_RDONLY); if(sys_fd == -1) { @@ -315,7 +344,7 @@ 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); + return sys_path_op(ctx, path, sys_mkdir, ACL_ADD_FILE); } } @@ -323,7 +352,7 @@ 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); + return sys_path_op(ctx, path, sys_unlink, ACL_DELETE); } } @@ -332,38 +361,43 @@ int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) { Session *sn; Request *rq; - uint32_t access_mask; + + uint32_t access_mask = ctx->aclreqaccess; + access_mask |= access; + if(!ctx->pool) { + // TODO: log warning + // broken VFSContext + return -1; + } + // 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 function call + int ret = op(ctx, path); + ctx->aclreqaccess = m; // restore original access mask + return ret; +} + +int sys_path_op(VFSContext *ctx, char *path, sys_op_f op, uint32_t access) { 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; + access |= ctx->aclreqaccess; } // 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)) { + SysACL sysacl; + if(sys_acl_check(ctx, access, &sysacl)) { return -1; } + if(sysacl.acl) { + if(!fs_acl_check(&sysacl, ctx->user, path, access)) { + acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); + return -1; + } + } + // do path operation - if(op(ctx, path)) { + if(op(ctx, path, &sysacl)) { // error if(ctx) { ctx->vfs_errno = errno; @@ -375,32 +409,25 @@ 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; +int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *sysacl) { if(!ctx) { + if(sysacl) { + sysacl->acl = NULL; + } 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; + ACLList *acl = acl_evallist( + acllist, + ctx->user, + access_mask, + &sysacl->acl); + + if(acl) { + acl_set_error_status(ctx->sn, ctx->rq, acl, ctx->user); + return 1; } } @@ -452,12 +479,20 @@ closedir(dir->data); } -int sys_mkdir(VFSContext *ctx, char *path) { +int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl) { mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - return mkdir(path, mode); + int ret = mkdir(path, mode); + if(ret == 0) { + if(sysacl->user_uid != -1) { + if(chown(path, sysacl->user_uid, sysacl->user_gid)) { + // TODO: error + } + } + } + return ret; } -int sys_unlink(VFSContext *ctx, char *path) { +int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl) { return unlink(path); } diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/vfs.h --- a/src/server/daemon/vfs.h Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/vfs.h Sat May 11 13:28:26 2013 +0200 @@ -30,26 +30,28 @@ #define VFS_H #include "../public/vfs.h" +#include "acl.h" #ifdef __cplusplus extern "C" { #endif - -// private + int vfs_init(); typedef int(*vfs_op_f)(VFSContext *, char *); +typedef int(*sys_op_f)(VFSContext *, char *, SysACL *); int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access); +int sys_path_op(VFSContext *ctx, char *path, sys_op_f op, uint32_t access); -int sys_acl_check(VFSContext *ctx, uint32_t acm, uid_t *uid, gid_t *gid); +int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *externacl); void sys_set_error_status(VFSContext *ctx); ssize_t sys_file_read(SYS_FILE fd, void *buf, size_t nbyte); ssize_t sys_file_write(SYS_FILE fd, const void *buf, size_t nbyte); void sys_file_close(SYS_FILE fd); int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat); void sys_dir_close(VFS_DIR dir); -int sys_mkdir(VFSContext *ctx, char *path); -int sys_unlink(VFSContext *ctx, char *path); +int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl); +int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl); #ifdef __cplusplus } diff -r c47e081b6c0f -r 66442f81f823 src/server/daemon/webserver.c --- a/src/server/daemon/webserver.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/daemon/webserver.c Sat May 11 13:28:26 2013 +0200 @@ -93,11 +93,12 @@ // set global vars conf_global_vars_s *vars = conf_getglobals(); + setpwent(); if(cfg->user.ptr) { char *pwbuf = malloc(DEF_PWBUF); vars->Vuserpw = malloc(sizeof(struct passwd)); // open user database - setpwent(); + //setpwent(); if(!util_getpwnam(cfg->user.ptr, vars->Vuserpw, pwbuf, DEF_PWBUF)) { log_ereport( LOG_MISCONFIG, @@ -107,7 +108,7 @@ vars->Vuserpw = NULL; } free(pwbuf); - endpwent(); + //endpwent(); // TODO: close or not? } // change uid diff -r c47e081b6c0f -r 66442f81f823 src/server/public/acl.h --- a/src/server/public/acl.h Thu May 09 19:41:11 2013 +0200 +++ b/src/server/public/acl.h Sat May 11 13:28:26 2013 +0200 @@ -181,10 +181,16 @@ * * evalutes all ACLs in acllist * + * externacl is set if an acl is extern, otherwise it is set to NULL + * * returns NULL if access is allowed or a pointer to the ACLList which * denied access */ -ACLList* acl_evallist(ACLListHandle *acllist, User *user, int access_mask); +ACLList* acl_evallist( + ACLListHandle *acllist, + User *user, + int access_mask, + ACLList **externacl); #ifdef __cplusplus } diff -r c47e081b6c0f -r 66442f81f823 src/server/public/auth.h --- a/src/server/public/auth.h Thu May 09 19:41:11 2013 +0200 +++ b/src/server/public/auth.h Sat May 11 13:28:26 2013 +0200 @@ -29,6 +29,8 @@ #ifndef WS_AUTH_H #define WS_AUTH_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -74,6 +76,8 @@ struct user { char *name; + uid_t uid; + gid_t gid; /* int verify_password(User *user, char *password) */ user_verify_passwd_f verify_password; /* int check_group(User *user, char *group) */ diff -r c47e081b6c0f -r 66442f81f823 src/server/public/nsapi.h --- a/src/server/public/nsapi.h Thu May 09 19:41:11 2013 +0200 +++ b/src/server/public/nsapi.h Sat May 11 13:28:26 2013 +0200 @@ -1370,7 +1370,7 @@ NSAPI_PUBLIC int system_fwrite(SYS_FILE fd, const void *buf, int nbyte); NSAPI_PUBLIC int system_fclose(SYS_FILE fd); -NSAPI_PUBLIC int util_errno2status(int errno_value); +NSAPI_PUBLIC int util_errno2status(int errno_value); // new #define util_errno2status util_errno2status diff -r c47e081b6c0f -r 66442f81f823 src/server/util/util.c --- a/src/server/util/util.c Thu May 09 19:41:11 2013 +0200 +++ b/src/server/util/util.c Sat May 11 13:28:26 2013 +0200 @@ -301,3 +301,20 @@ return newstr; } + +sstr_t util_path_remove_last(sstr_t path) { + int i; + for(i=path.length-1;i>=0;i--) { + char c = path.ptr[i]; + if(c == '/') { + path.ptr[i] = 0; + path.length = i; + break; + } + } + if(i < 0) { + path.ptr = NULL; + path.length = 0; + } + return path; +} diff -r c47e081b6c0f -r 66442f81f823 src/server/util/util.h --- a/src/server/util/util.h Thu May 09 19:41:11 2013 +0200 +++ b/src/server/util/util.h Sat May 11 13:28:26 2013 +0200 @@ -228,6 +228,8 @@ /* path utils */ NSAPI_PUBLIC sstr_t util_path_append(pool_handle_t *pool, char *path, char *child); +NSAPI_PUBLIC +sstr_t util_path_remove_last(sstr_t path); /* --- End common function prototypes --- */