/* * 2007+ Copyright (c) Evgeniy Polyakov * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include "netfs.h" /* * Global configuration list. * Each client can be asked to get one of them. * * Allows to provide remote server address (ipv4/v6/whatever), port * and so on via kernel connector. */ static struct cb_id pohmelfs_cn_id = {.idx = POHMELFS_CN_IDX, .val = POHMELFS_CN_VAL}; static LIST_HEAD(pohmelfs_config_list); static DEFINE_MUTEX(pohmelfs_config_lock); static inline int pohmelfs_config_eql(struct pohmelfs_ctl *sc, struct pohmelfs_ctl *ctl) { if (sc->idx == ctl->idx && sc->type == ctl->type && sc->proto == ctl->proto && sc->addrlen == ctl->addrlen && !memcmp(&sc->addr, &ctl->addr, ctl->addrlen)) return 1; return 0; } static struct pohmelfs_config_group *pohmelfs_find_config_group(unsigned int idx) { struct pohmelfs_config_group *g, *group = NULL; list_for_each_entry(g, &pohmelfs_config_list, group_entry) { if (g->idx == idx) { group = g; break; } } return group; } static struct pohmelfs_config_group *pohmelfs_find_create_config_group(unsigned int idx) { struct pohmelfs_config_group *g; g = pohmelfs_find_config_group(idx); if (g) return g; g = kzalloc(sizeof(struct pohmelfs_config_group), GFP_KERNEL); if (!g) return NULL; INIT_LIST_HEAD(&g->config_list); g->idx = idx; g->num_entry = 0; list_add_tail(&g->group_entry, &pohmelfs_config_list); return g; } static inline void pohmelfs_insert_config_entry(struct pohmelfs_sb *psb, struct pohmelfs_config *dst) { struct pohmelfs_config *tmp; INIT_LIST_HEAD(&dst->config_entry); list_for_each_entry(tmp, &psb->state_list, config_entry) { if (dst->state.ctl.prio > tmp->state.ctl.prio) list_add_tail(&dst->config_entry, &tmp->config_entry); } if (list_empty(&dst->config_entry)) list_add_tail(&dst->config_entry, &psb->state_list); } static int pohmelfs_move_config_entry(struct pohmelfs_sb *psb, struct pohmelfs_config *dst, struct pohmelfs_config *new) { if ((dst->state.ctl.prio == new->state.ctl.prio) && (dst->state.ctl.perm == new->state.ctl.perm)) return 0; dprintk("%s: dst: prio: %d, perm: %x, new: prio: %d, perm: %d.\n", __func__, dst->state.ctl.prio, dst->state.ctl.perm, new->state.ctl.prio, new->state.ctl.perm); dst->state.ctl.prio = new->state.ctl.prio; dst->state.ctl.perm = new->state.ctl.perm; list_del_init(&dst->config_entry); pohmelfs_insert_config_entry(psb, dst); return 0; } /* * pohmelfs_copy_config() is used to copy new state configs from the * config group (controlled by the netlink messages) into the superblock. * This happens either at startup time where no transactions can access * the list of the configs (and thus list of the network states), or at * run-time, where it is protected by the psb->state_lock. */ int pohmelfs_copy_config(struct pohmelfs_sb *psb) { struct pohmelfs_config_group *g; struct pohmelfs_config *c, *dst; int err = -ENODEV; mutex_lock(&pohmelfs_config_lock); g = pohmelfs_find_config_group(psb->idx); if (!g) goto out_unlock; /* * Run over all entries in given config group and try to create and * initialize those, which do not exist in superblock list. * Skip all existing entries. */ list_for_each_entry(c, &g->config_list, config_entry) { err = 0; list_for_each_entry(dst, &psb->state_list, config_entry) { if (pohmelfs_config_eql(&dst->state.ctl, &c->state.ctl)) { err = pohmelfs_move_config_entry(psb, dst, c); if (!err) err = -EEXIST; break; } } if (err) continue; dst = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL); if (!dst) { err = -ENOMEM; break; } memcpy(&dst->state.ctl, &c->state.ctl, sizeof(struct pohmelfs_ctl)); pohmelfs_insert_config_entry(psb, dst); err = pohmelfs_state_init_one(psb, dst); if (err) { list_del(&dst->config_entry); kfree(dst); } err = 0; } out_unlock: mutex_unlock(&pohmelfs_config_lock); return err; } int pohmelfs_copy_crypto(struct pohmelfs_sb *psb) { struct pohmelfs_config_group *g; int err = -ENOENT; mutex_lock(&pohmelfs_config_lock); g = pohmelfs_find_config_group(psb->idx); if (!g) goto err_out_exit; if (g->hash_string) { err = -ENOMEM; psb->hash_string = kstrdup(g->hash_string, GFP_KERNEL); if (!psb->hash_string) goto err_out_exit; psb->hash_strlen = g->hash_strlen; } if (g->cipher_string) { psb->cipher_string = kstrdup(g->cipher_string, GFP_KERNEL); if (!psb->cipher_string) goto err_out_free_hash_string; psb->cipher_strlen = g->cipher_strlen; } if (g->hash_keysize) { psb->hash_key = kmemdup(g->hash_key, g->hash_keysize, GFP_KERNEL); if (!psb->hash_key) goto err_out_free_cipher_string; psb->hash_keysize = g->hash_keysize; } if (g->cipher_keysize) { psb->cipher_key = kmemdup(g->cipher_key, g->cipher_keysize, GFP_KERNEL); if (!psb->cipher_key) goto err_out_free_hash; psb->cipher_keysize = g->cipher_keysize; } mutex_unlock(&pohmelfs_config_lock); return 0; err_out_free_hash: kfree(psb->hash_key); err_out_free_cipher_string: kfree(psb->cipher_string); err_out_free_hash_string: kfree(psb->hash_string); err_out_exit: mutex_unlock(&pohmelfs_config_lock); return err; } static int pohmelfs_send_reply(int err, int msg_num, int action, struct cn_msg *msg, struct pohmelfs_ctl *ctl) { struct pohmelfs_cn_ack *ack; ack = kzalloc(sizeof(struct pohmelfs_cn_ack), GFP_KERNEL); if (!ack) return -ENOMEM; memcpy(&ack->msg, msg, sizeof(struct cn_msg)); if (action == POHMELFS_CTLINFO_ACK) memcpy(&ack->ctl, ctl, sizeof(struct pohmelfs_ctl)); ack->msg.len = sizeof(struct pohmelfs_cn_ack) - sizeof(struct cn_msg); ack->msg.ack = msg->ack + 1; ack->error = err; ack->msg_num = msg_num; cn_netlink_send(&ack->msg, 0, GFP_KERNEL); kfree(ack); return 0; } static int pohmelfs_cn_disp(struct cn_msg *msg) { struct pohmelfs_config_group *g; struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data; struct pohmelfs_config *c, *tmp; int err = 0, i = 1; if (msg->len != sizeof(struct pohmelfs_ctl)) return -EBADMSG; mutex_lock(&pohmelfs_config_lock); g = pohmelfs_find_config_group(ctl->idx); if (!g) { pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL); goto out_unlock; } list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { struct pohmelfs_ctl *sc = &c->state.ctl; if (pohmelfs_send_reply(err, g->num_entry - i, POHMELFS_CTLINFO_ACK, msg, sc)) { err = -ENOMEM; goto out_unlock; } i += 1; } out_unlock: mutex_unlock(&pohmelfs_config_lock); return err; } static int pohmelfs_cn_dump(struct cn_msg *msg) { struct pohmelfs_config_group *g; struct pohmelfs_config *c, *tmp; int err = 0, i = 1; int total_msg = 0; if (msg->len != sizeof(struct pohmelfs_ctl)) return -EBADMSG; mutex_lock(&pohmelfs_config_lock); list_for_each_entry(g, &pohmelfs_config_list, group_entry) total_msg += g->num_entry; if (total_msg == 0) { if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL)) err = -ENOMEM; goto out_unlock; } list_for_each_entry(g, &pohmelfs_config_list, group_entry) { list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { struct pohmelfs_ctl *sc = &c->state.ctl; if (pohmelfs_send_reply(err, total_msg - i, POHMELFS_CTLINFO_ACK, msg, sc)) { err = -ENOMEM; goto out_unlock; } i += 1; } } out_unlock: mutex_unlock(&pohmelfs_config_lock); return err; } static int pohmelfs_cn_flush(struct cn_msg *msg) { struct pohmelfs_config_group *g; struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data; struct pohmelfs_config *c, *tmp; int err = 0; if (msg->len != sizeof(struct pohmelfs_ctl)) return -EBADMSG; mutex_lock(&pohmelfs_config_lock); if (ctl->idx != POHMELFS_NULL_IDX) { g = pohmelfs_find_config_group(ctl->idx); if (!g) goto out_unlock; list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { list_del(&c->config_entry); g->num_entry--; kfree(c); } } else { list_for_each_entry(g, &pohmelfs_config_list, group_entry) { list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { list_del(&c->config_entry); g->num_entry--; kfree(c); } } } out_unlock: mutex_unlock(&pohmelfs_config_lock); pohmelfs_cn_dump(msg); return err; } static int pohmelfs_modify_config(struct pohmelfs_ctl *old, struct pohmelfs_ctl *new) { old->perm = new->perm; old->prio = new->prio; return 0; } static int pohmelfs_cn_ctl(struct cn_msg *msg, int action) { struct pohmelfs_config_group *g; struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data; struct pohmelfs_config *c, *tmp; int err = 0; if (msg->len != sizeof(struct pohmelfs_ctl)) return -EBADMSG; mutex_lock(&pohmelfs_config_lock); g = pohmelfs_find_create_config_group(ctl->idx); if (!g) { err = -ENOMEM; goto out_unlock; } list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { struct pohmelfs_ctl *sc = &c->state.ctl; if (pohmelfs_config_eql(sc, ctl)) { if (action == POHMELFS_FLAGS_ADD) { err = -EEXIST; goto out_unlock; } else if (action == POHMELFS_FLAGS_DEL) { list_del(&c->config_entry); g->num_entry--; kfree(c); goto out_unlock; } else if (action == POHMELFS_FLAGS_MODIFY) { err = pohmelfs_modify_config(sc, ctl); goto out_unlock; } else { err = -EEXIST; goto out_unlock; } } } if (action == POHMELFS_FLAGS_DEL) { err = -EBADMSG; goto out_unlock; } c = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL); if (!c) { err = -ENOMEM; goto out_unlock; } memcpy(&c->state.ctl, ctl, sizeof(struct pohmelfs_ctl)); g->num_entry++; list_add_tail(&c->config_entry, &g->config_list); out_unlock: mutex_unlock(&pohmelfs_config_lock); if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL)) err = -ENOMEM; return err; } static int pohmelfs_crypto_hash_init(struct pohmelfs_config_group *g, struct pohmelfs_crypto *c) { char *algo = (char *)c->data; u8 *key = (u8 *)(algo + c->strlen); if (g->hash_string) return -EEXIST; g->hash_string = kstrdup(algo, GFP_KERNEL); if (!g->hash_string) return -ENOMEM; g->hash_strlen = c->strlen; g->hash_keysize = c->keysize; g->hash_key = kmemdup(key, c->keysize, GFP_KERNEL); if (!g->hash_key) { kfree(g->hash_string); return -ENOMEM; } return 0; } static int pohmelfs_crypto_cipher_init(struct pohmelfs_config_group *g, struct pohmelfs_crypto *c) { char *algo = (char *)c->data; u8 *key = (u8 *)(algo + c->strlen); if (g->cipher_string) return -EEXIST; g->cipher_string = kstrdup(algo, GFP_KERNEL); if (!g->cipher_string) return -ENOMEM; g->cipher_strlen = c->strlen; g->cipher_keysize = c->keysize; g->cipher_key = kmemdup(key, c->keysize, GFP_KERNEL); if (!g->cipher_key) { kfree(g->cipher_string); return -ENOMEM; } return 0; } static int pohmelfs_cn_crypto(struct cn_msg *msg) { struct pohmelfs_crypto *crypto = (struct pohmelfs_crypto *)msg->data; struct pohmelfs_config_group *g; int err = 0; dprintk("%s: idx: %u, strlen: %u, type: %u, keysize: %u, algo: %s.\n", __func__, crypto->idx, crypto->strlen, crypto->type, crypto->keysize, (char *)crypto->data); mutex_lock(&pohmelfs_config_lock); g = pohmelfs_find_create_config_group(crypto->idx); if (!g) { err = -ENOMEM; goto out_unlock; } switch (crypto->type) { case POHMELFS_CRYPTO_HASH: err = pohmelfs_crypto_hash_init(g, crypto); break; case POHMELFS_CRYPTO_CIPHER: err = pohmelfs_crypto_cipher_init(g, crypto); break; default: err = -ENOTSUPP; break; } out_unlock: mutex_unlock(&pohmelfs_config_lock); if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL)) err = -ENOMEM; return err; } static void pohmelfs_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) { int err; if (!cap_raised(current_cap(), CAP_SYS_ADMIN)) return; switch (msg->flags) { case POHMELFS_FLAGS_ADD: case POHMELFS_FLAGS_DEL: case POHMELFS_FLAGS_MODIFY: err = pohmelfs_cn_ctl(msg, msg->flags); break; case POHMELFS_FLAGS_FLUSH: err = pohmelfs_cn_flush(msg); break; case POHMELFS_FLAGS_SHOW: err = pohmelfs_cn_disp(msg); break; case POHMELFS_FLAGS_DUMP: err = pohmelfs_cn_dump(msg); break; case POHMELFS_FLAGS_CRYPTO: err = pohmelfs_cn_crypto(msg); break; default: err = -ENOSYS; break; } } int pohmelfs_config_check(struct pohmelfs_config *config, int idx) { struct pohmelfs_ctl *ctl = &config->state.ctl; struct pohmelfs_config *tmp; int err = -ENOENT; struct pohmelfs_ctl *sc; struct pohmelfs_config_group *g; mutex_lock(&pohmelfs_config_lock); g = pohmelfs_find_config_group(ctl->idx); if (g) { list_for_each_entry(tmp, &g->config_list, config_entry) { sc = &tmp->state.ctl; if (pohmelfs_config_eql(sc, ctl)) { err = 0; break; } } } mutex_unlock(&pohmelfs_config_lock); return err; } int __init pohmelfs_config_init(void) { /* XXX remove (void *) cast when vanilla connector got synced */ return cn_add_callback(&pohmelfs_cn_id, "pohmelfs", (void *)pohmelfs_cn_callback); } void pohmelfs_config_exit(void) { struct pohmelfs_config *c, *tmp; struct pohmelfs_config_group *g, *gtmp; cn_del_callback(&pohmelfs_cn_id); mutex_lock(&pohmelfs_config_lock); list_for_each_entry_safe(g, gtmp, &pohmelfs_config_list, group_entry) { list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { list_del(&c->config_entry); kfree(c); } list_del(&g->group_entry); kfree(g->hash_string); kfree(g->cipher_string); kfree(g); } mutex_unlock(&pohmelfs_config_lock); }