#include "lib.h" #include "str.h" #include "index/index-mail.h" #include "index/index-storage.h" #include "quota-private.h" #include #include #include #include #include #include #include #include #include #include #include #include #include enum rquotad_states { RQUOTAD_UNKNOWN, RQUOTAD_ERR, RQUOTAD_OFF, RQUOTAD_ON }; struct rquotad_quota_root { struct quota_root root; enum rquotad_states state; const char **resources; uid_t uid; uint64_t kbyte_usage; uint64_t inode_usage; uint64_t kbyte_limit; uint64_t inode_limit; }; static void debugmsg(const char *msg, ...) { va_list args; static int debug = -1; if (debug < 0) { if (getenv("DEBUG") != NULL) debug = 1; else debug = 0; } if (!debug) return; va_start(args, msg); i_vinfo(msg, args); va_end(args); } extern struct quota_backend quota_backend_rquotad; static void rquotad_quota_root_update(struct rquotad_quota_root *root); static struct quota_root * rquotad_quota_init(struct quota_setup *setup __attr_unused__, const char *name) { struct rquotad_quota_root *root; debugmsg("creating quotaroot %s", name); root = i_new(struct rquotad_quota_root, 1); root->root.name = NULL; root->root.v = quota_backend_rquotad.v; root->state = RQUOTAD_UNKNOWN; root->uid = geteuid(); return &root->root; } static void rquotad_quota_deinit(struct quota_root *_root) { struct rquotad_quota_root *root = (struct rquotad_quota_root *)_root; i_free(root->root.name); if (root->resources != NULL) i_free(root->resources); i_free(root); } static bool rquotad_quota_add_storage(struct quota_root *_root, struct mail_storage *storage) { const char *dir; bool is_file; struct statfs statbuf; dir = mail_storage_get_mailbox_path(storage, "", &is_file); debugmsg("rquotad quota add storage dir = %s (is_file = %d)", dir, is_file); if (statfs(dir, &statbuf) < 0) return FALSE; if (statbuf.f_flags & MNT_LOCAL || strchr(statbuf.f_mntfromname, ':') == NULL) return FALSE; if (_root->name == NULL) { _root->name = i_strndup(statbuf.f_mntonname, MNAMELEN); } else if (strncmp(_root->name, statbuf.f_mntonname, MNAMELEN) != 0) { return FALSE; } debugmsg("rquotad quota mountpath = %s", _root->name); return TRUE; } static void rquotad_quota_remove_storage(struct quota_root *root __attr_unused__, struct mail_storage *storage __attr_unused__) { } static const char *const * rquotad_quota_root_get_resources(struct quota_root *_root) { struct rquotad_quota_root *root = (struct rquotad_quota_root *)_root; int i = 0; if (root->resources != NULL) { return root->resources; } if (root->state == RQUOTAD_UNKNOWN) { rquotad_quota_root_update(root); } if (root->state == RQUOTAD_ERR) { return NULL; } if (root->state == RQUOTAD_OFF) { root->resources = i_new(const char *, 1); root->resources[0] = NULL; return root->resources; } /* Out of date is alright, we don't really care about up-to-date * numbers, just whether quotas are active */ root->resources = i_new(const char *, 3); if (root->kbyte_limit != 0) { root->resources[i++] = QUOTA_NAME_STORAGE; } if (root->inode_limit != 0) { root->resources[i++] = QUOTA_NAME_MESSAGES; } root->resources[i] = NULL; return root->resources; } static int rquotad_quota_get_resource(struct quota_root *_root, const char *name, uint64_t *value_r, uint64_t *limit_r) { struct rquotad_quota_root *root = (struct rquotad_quota_root *)_root; if (root->state == RQUOTAD_UNKNOWN || root->state == RQUOTAD_ON) { rquotad_quota_root_update(root); } if (root->state == RQUOTAD_ERR) { return -1; } *value_r = 0; *limit_r = 0; if (root->state == RQUOTAD_OFF) { return 0; } if (strcmp(name, QUOTA_NAME_STORAGE) == 0) { *value_r = root->kbyte_usage; *limit_r = root->kbyte_limit; return 1; } if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) { *value_r = root->inode_usage; *limit_r = root->inode_limit; return 1; } return 0; } static int rquotad_quota_set_resource(struct quota_root *root, const char *name __attr_unused__, uint64_t value __attr_unused__) { quota_set_error(root->setup->quota, MAIL_STORAGE_ERR_NO_PERMISSION); return -1; } static struct quota_root_transaction_context * rquotad_quota_transaction_begin(struct quota_root *root, struct quota_transaction_context *ctx) { struct quota_root_transaction_context *root_ctx; root_ctx = i_new(struct quota_root_transaction_context, 1); root_ctx->root = root; root_ctx->ctx = ctx; root_ctx->disabled = TRUE; return root_ctx; } static int rquotad_quota_transaction_commit(struct quota_root_transaction_context *ctx) { i_free(ctx); return 0; } struct quota_backend quota_backend_rquotad = { "rquotad", { rquotad_quota_init, rquotad_quota_deinit, rquotad_quota_add_storage, rquotad_quota_remove_storage, rquotad_quota_root_get_resources, rquotad_quota_get_resource, rquotad_quota_set_resource, rquotad_quota_transaction_begin, rquotad_quota_transaction_commit, quota_default_transaction_rollback, quota_default_try_alloc, quota_default_try_alloc_bytes, quota_default_test_alloc_bytes, quota_default_alloc, quota_default_free } }; static int rquota_get(char *host, getquota_args *qargs, getquota_rslt *qres) { struct sockaddr_in rhost; struct hostent *hent; struct timeval tout; enum clnt_stat cstat; CLIENT *clnt = NULL; int sock = RPC_ANYSOCK; hent = gethostbyname(host); if (!hent) return -1; if (hent->h_length > (int)sizeof(rhost.sin_addr)) return -1; tout.tv_sec = 6; tout.tv_usec = 0; *(long *)&rhost.sin_addr = *(long *)hent->h_addr; rhost.sin_family = AF_INET; rhost.sin_port = 0; clnt = clntudp_create(&rhost, RQUOTAPROG, RQUOTAVERS, tout, &sock); if (!clnt) return rpc_createerr.cf_stat; clnt->cl_auth = authunix_create_default(); tout.tv_sec = 25; tout.tv_usec = 0; cstat = clnt_call(clnt, RQUOTAPROC_GETQUOTA, xdr_getquota_args, qargs, xdr_getquota_rslt, qres, tout); clnt_destroy(clnt); return cstat; } static void rquotad_quota_root_update(struct rquotad_quota_root *root) { struct statfs statfsbuf; struct getquota_args qargs; struct getquota_rslt qres; char *remotehost, *remotepath; char *c; if (root->state == RQUOTAD_OFF) { return; } if (statfs(root->root.name, &statfsbuf) < 0) { quota_set_error(root->root.setup->quota, strerror(errno)); root->state = RQUOTAD_ERR; return; } if (statfsbuf.f_flags & MNT_LOCAL) { root->state = RQUOTAD_OFF; return; } /* * must be some form of "hostname:/path" */ c = strchr(statfsbuf.f_mntfromname, ':'); if (c == NULL) { quota_set_error(root->root.setup->quota, "cannot find hostname"); root->state = RQUOTAD_ERR; return; } *c = '\0'; remotehost = strdup(statfsbuf.f_mntfromname); remotepath = strdup(c + 1); *c = ':'; if (remotepath[0] != '/') { quota_set_error(root->root.setup->quota, "weird filesystem"); root->state = RQUOTAD_ERR; return; } qargs.gqa_pathp = remotepath; qargs.gqa_uid = root->uid; if (rquota_get(remotehost, &qargs, &qres) != 0) { return; } switch (qres.status) { case Q_OK: root->kbyte_limit = (qres.getquota_rslt_u.gqr_rquota.rq_bhardlimit * qres.getquota_rslt_u.gqr_rquota.rq_bsize) / 1024; root->kbyte_usage = (qres.getquota_rslt_u.gqr_rquota.rq_curblocks * qres.getquota_rslt_u.gqr_rquota.rq_bsize) / 1024; /* inodes */ root->inode_limit = qres.getquota_rslt_u.gqr_rquota.rq_fhardlimit; root->inode_usage = qres.getquota_rslt_u.gqr_rquota.rq_curfiles; root->state = RQUOTAD_ON; return; case Q_NOQUOTA: root->state = RQUOTAD_OFF; return; case Q_EPERM: quota_set_error(root->root.setup->quota, "quota permission error"); root->state = RQUOTAD_ERR; return; } quota_set_error(root->root.setup->quota, "bad rpc result"); root->state = RQUOTAD_ERR; }