diff options
-rw-r--r-- | lib9p/srv.c | 140 |
1 files changed, 129 insertions, 11 deletions
diff --git a/lib9p/srv.c b/lib9p/srv.c index fb2bd07..2feed56 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -165,17 +165,6 @@ static inline enum srv_filetype srv_qid_filetype(struct lib9p_qid qid) { return SRV_FILETYPE_FILE; } -static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_srv_stat *stat, uint8_t action) { - assert(ctx); - assert(stat); - assert(action); - - /* TODO actually check user and group instead of just assuming "other". */ - uint8_t mode = (uint8_t)(stat->mode & 07); - - return mode & action; -} - [[gnu::unused]] static struct lib9p_srv_userid *srv_userid_new(struct lib9p_s name #if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L @@ -230,6 +219,46 @@ static struct lib9p_srv_userid *srv_userid_incref(struct lib9p_srv_userid *useri return userid; } +static inline bool srv_user_is_same(struct lib9p_srv_userid *a, struct lib9p_srv_userid *b) { + assert(a); + assert(b); + return lib9p_str_eq(a->name, b->name) +#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L + && (a->num == b->num) +#endif + ; +} + +static inline bool srv_user_is_member(struct lib9p_srv_userid *user, struct lib9p_srv_userid *group) { + assert(user); + assert(group); + /* TODO: Add a way to check group membership. */ + return false; +} + +static inline bool srv_user_is_leader(struct lib9p_srv_userid *user, struct lib9p_srv_userid *group) { + assert(user); + assert(group); + /* TODO: Add a way to check group leadership. */ + return false; +} + +static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_srv_stat *stat, uint8_t requested) { + assert(ctx); + assert(stat); + assert(requested); + + uint8_t allowed; + if (srv_user_is_same(ctx->user, &stat->owner_uid)) + allowed = (stat->mode >> 6) & 07; + else if (srv_user_is_member(ctx->user, &stat->owner_gid)) + allowed = (stat->mode >> 3) & 07; + else + allowed = (stat->mode >> 0) & 07; + + return (requested & ~allowed) == 0; +} + /** * Ensures that `file` is saved into the pathmap, and increments the * gc_refcount by 1 (for presumptive insertion into the fidmap). @@ -1472,10 +1501,99 @@ static void handle_Twstat(struct srv_req *ctx, struct lib9p_msg_Twstat *req) { srv_handler_common(ctx, wstat, req); + struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid); + if (!fidinfo) { + lib9p_errorf(&ctx->basectx, + LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid); + return; + } + struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path); + assert(pathinfo); + + ctx->user = srv_userid_incref(fidinfo->user); + struct lib9p_srv_stat cur_stat = LO_CALL(pathinfo->file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + return; + lib9p_srv_stat_assert(cur_stat); + +#define is_str_donttouch(VAL) ((VAL).len == 0) +#define is_int_donttouch(VAL) ((VAL) == ~(typeof(VAL))0) + + if (!is_str_donttouch(req->stat.name) && !lib9p_str_eq(req->stat.name, cur_stat.name)) { + if (pathinfo->parent_dir == fidinfo->path) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EBUSY, "change name: cannot rename root"); + goto twstat_return; + } + struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir); + assert(parent); + struct lib9p_srv_stat parent_stat = LO_CALL(parent->file, stat, ctx); + if (lib9p_ctx_has_error(&ctx->basectx)) + goto twstat_return; + if (!srv_check_perm(ctx, &parent_stat, 0b010)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change name: you do not have write permission on the parent directory"); + goto twstat_return; + } + } + if (!is_int_donttouch(req->stat.length) && req->stat.length != cur_stat.size) { + if (!srv_check_perm(ctx, &cur_stat, 0b010)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change length: you do not have write permission"); + goto twstat_return; + } + } + if (!is_int_donttouch(req->stat.mode) && req->stat.mode != cur_stat.mode) { + if (!(srv_user_is_same(ctx->user, &cur_stat.owner_uid) || srv_user_is_leader(ctx->user, &cur_stat.owner_gid))) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change mode: you are neither the owner nor group-leader"); + goto twstat_return; + } + } + if (!is_int_donttouch(req->stat.mtime) && req->stat.mode != cur_stat.mtime_sec) { + if (!(srv_user_is_same(ctx->user, &cur_stat.owner_uid) || srv_user_is_leader(ctx->user, &cur_stat.owner_gid))) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change mtime: you are neither the owner nor group-leader"); + goto twstat_return; + } + } + if ((!is_str_donttouch(req->stat.owner_gname) && !lib9p_str_eq(req->stat.owner_gname, cur_stat.owner_gid.name)) +#if CONFIG_9P_ENABLE_9P2000_u + || (ctx->basectx.version == LIB9P_VER_9P2000_u && + !is_int_donttouch(req->stat.owner_gnum) && req->stat.owner_gnum != cur_stat.owner_gid.num) +#endif + ) { +#if CONFIG_9P_ENABLE_9P2000_u + if (ctx->basectx.version == LIB9P_VER_9P2000_u) { + /* TODO: check that gid==n_gid */ + } +#endif + struct lib9p_srv_userid new_group = srv_userid_new_stack(req->stat.owner_gname, req->stat.owner_gnum); + if (srv_user_is_same(ctx->user, &cur_stat.owner_uid)) { + if (!srv_user_is_member(ctx->user, &new_group)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change group: you are not a member of the new group"); + goto twstat_return; + } + } else if (srv_user_is_leader(ctx->user, &cur_stat.owner_gid)) { + if (!srv_user_is_leader(ctx->user, &new_group)) { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change group: you are not a leader of the new group"); + goto twstat_return; + } + } else { + lib9p_error(&ctx->basectx, + LIB9P_ERRNO_L_EACCES, "change group: you are neither the owner nor group-leader"); + goto twstat_return; + } + } + lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EOPNOTSUPP, "wstat not (yet?) implemented"); + twstat_return: srv_respond(ctx, wstat, &resp); + ctx->user = srv_userid_decref(ctx->user); } #endif |