|  | /* | 
|  | *  fs/cifs/dns_resolve.c | 
|  | * | 
|  | *   Copyright (c) 2007 Igor Mammedov | 
|  | *   Author(s): Igor Mammedov (niallain@gmail.com) | 
|  | *              Steve French (sfrench@us.ibm.com) | 
|  | * | 
|  | *   Contains the CIFS DFS upcall routines used for hostname to | 
|  | *   IP address translation. | 
|  | * | 
|  | *   This library is free software; you can redistribute it and/or modify | 
|  | *   it under the terms of the GNU Lesser General Public License as published | 
|  | *   by the Free Software Foundation; either version 2.1 of the License, or | 
|  | *   (at your option) any later version. | 
|  | * | 
|  | *   This library 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 Lesser General Public License for more details. | 
|  | * | 
|  | *   You should have received a copy of the GNU Lesser General Public License | 
|  | *   along with this library; if not, write to the Free Software | 
|  | *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 
|  | */ | 
|  |  | 
|  | #include <linux/slab.h> | 
|  | #include <linux/keyctl.h> | 
|  | #include <linux/key-type.h> | 
|  | #include <keys/user-type.h> | 
|  | #include "dns_resolve.h" | 
|  | #include "cifsglob.h" | 
|  | #include "cifsproto.h" | 
|  | #include "cifs_debug.h" | 
|  |  | 
|  | static const struct cred *dns_resolver_cache; | 
|  |  | 
|  | /* Checks if supplied name is IP address | 
|  | * returns: | 
|  | * 		1 - name is IP | 
|  | * 		0 - name is not IP | 
|  | */ | 
|  | static int | 
|  | is_ip(char *name) | 
|  | { | 
|  | struct sockaddr_storage ss; | 
|  |  | 
|  | return cifs_convert_address((struct sockaddr *)&ss, name); | 
|  | } | 
|  |  | 
|  | static int | 
|  | dns_resolver_instantiate(struct key *key, const void *data, | 
|  | size_t datalen) | 
|  | { | 
|  | int rc = 0; | 
|  | char *ip; | 
|  |  | 
|  | ip = kmalloc(datalen + 1, GFP_KERNEL); | 
|  | if (!ip) | 
|  | return -ENOMEM; | 
|  |  | 
|  | memcpy(ip, data, datalen); | 
|  | ip[datalen] = '\0'; | 
|  |  | 
|  | /* make sure this looks like an address */ | 
|  | if (!is_ip(ip)) { | 
|  | kfree(ip); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | key->type_data.x[0] = datalen; | 
|  | key->payload.data = ip; | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void | 
|  | dns_resolver_destroy(struct key *key) | 
|  | { | 
|  | kfree(key->payload.data); | 
|  | } | 
|  |  | 
|  | struct key_type key_type_dns_resolver = { | 
|  | .name        = "dns_resolver", | 
|  | .def_datalen = sizeof(struct in_addr), | 
|  | .describe    = user_describe, | 
|  | .instantiate = dns_resolver_instantiate, | 
|  | .destroy     = dns_resolver_destroy, | 
|  | .match       = user_match, | 
|  | }; | 
|  |  | 
|  | /* Resolves server name to ip address. | 
|  | * input: | 
|  | * 	unc - server UNC | 
|  | * output: | 
|  | * 	*ip_addr - pointer to server ip, caller responcible for freeing it. | 
|  | * return 0 on success | 
|  | */ | 
|  | int | 
|  | dns_resolve_server_name_to_ip(const char *unc, char **ip_addr) | 
|  | { | 
|  | const struct cred *saved_cred; | 
|  | int rc = -EAGAIN; | 
|  | struct key *rkey = ERR_PTR(-EAGAIN); | 
|  | char *name; | 
|  | char *data = NULL; | 
|  | int len; | 
|  |  | 
|  | if (!ip_addr || !unc) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* search for server name delimiter */ | 
|  | len = strlen(unc); | 
|  | if (len < 3) { | 
|  | cFYI(1, "%s: unc is too short: %s", __func__, unc); | 
|  | return -EINVAL; | 
|  | } | 
|  | len -= 2; | 
|  | name = memchr(unc+2, '\\', len); | 
|  | if (!name) { | 
|  | cFYI(1, "%s: probably server name is whole unc: %s", | 
|  | __func__, unc); | 
|  | } else { | 
|  | len = (name - unc) - 2/* leading // */; | 
|  | } | 
|  |  | 
|  | name = kmalloc(len+1, GFP_KERNEL); | 
|  | if (!name) { | 
|  | rc = -ENOMEM; | 
|  | return rc; | 
|  | } | 
|  | memcpy(name, unc+2, len); | 
|  | name[len] = 0; | 
|  |  | 
|  | if (is_ip(name)) { | 
|  | cFYI(1, "%s: it is IP, skipping dns upcall: %s", | 
|  | __func__, name); | 
|  | data = name; | 
|  | goto skip_upcall; | 
|  | } | 
|  |  | 
|  | saved_cred = override_creds(dns_resolver_cache); | 
|  | rkey = request_key(&key_type_dns_resolver, name, ""); | 
|  | revert_creds(saved_cred); | 
|  | if (!IS_ERR(rkey)) { | 
|  | if (!(rkey->perm & KEY_USR_VIEW)) { | 
|  | down_read(&rkey->sem); | 
|  | rkey->perm |= KEY_USR_VIEW; | 
|  | up_read(&rkey->sem); | 
|  | } | 
|  | len = rkey->type_data.x[0]; | 
|  | data = rkey->payload.data; | 
|  | } else { | 
|  | cERROR(1, "%s: unable to resolve: %s", __func__, name); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | skip_upcall: | 
|  | if (data) { | 
|  | *ip_addr = kmalloc(len + 1, GFP_KERNEL); | 
|  | if (*ip_addr) { | 
|  | memcpy(*ip_addr, data, len + 1); | 
|  | if (!IS_ERR(rkey)) | 
|  | cFYI(1, "%s: resolved: %s to %s", __func__, | 
|  | name, | 
|  | *ip_addr | 
|  | ); | 
|  | rc = 0; | 
|  | } else { | 
|  | rc = -ENOMEM; | 
|  | } | 
|  | if (!IS_ERR(rkey)) | 
|  | key_put(rkey); | 
|  | } | 
|  |  | 
|  | out: | 
|  | kfree(name); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | int __init cifs_init_dns_resolver(void) | 
|  | { | 
|  | struct cred *cred; | 
|  | struct key *keyring; | 
|  | int ret; | 
|  |  | 
|  | printk(KERN_NOTICE "Registering the %s key type\n", | 
|  | key_type_dns_resolver.name); | 
|  |  | 
|  | /* create an override credential set with a special thread keyring in | 
|  | * which DNS requests are cached | 
|  | * | 
|  | * this is used to prevent malicious redirections from being installed | 
|  | * with add_key(). | 
|  | */ | 
|  | cred = prepare_kernel_cred(NULL); | 
|  | if (!cred) | 
|  | return -ENOMEM; | 
|  |  | 
|  | keyring = key_alloc(&key_type_keyring, ".dns_resolver", 0, 0, cred, | 
|  | (KEY_POS_ALL & ~KEY_POS_SETATTR) | | 
|  | KEY_USR_VIEW | KEY_USR_READ, | 
|  | KEY_ALLOC_NOT_IN_QUOTA); | 
|  | if (IS_ERR(keyring)) { | 
|  | ret = PTR_ERR(keyring); | 
|  | goto failed_put_cred; | 
|  | } | 
|  |  | 
|  | ret = key_instantiate_and_link(keyring, NULL, 0, NULL, NULL); | 
|  | if (ret < 0) | 
|  | goto failed_put_key; | 
|  |  | 
|  | ret = register_key_type(&key_type_dns_resolver); | 
|  | if (ret < 0) | 
|  | goto failed_put_key; | 
|  |  | 
|  | /* instruct request_key() to use this special keyring as a cache for | 
|  | * the results it looks up */ | 
|  | cred->thread_keyring = keyring; | 
|  | cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING; | 
|  | dns_resolver_cache = cred; | 
|  | return 0; | 
|  |  | 
|  | failed_put_key: | 
|  | key_put(keyring); | 
|  | failed_put_cred: | 
|  | put_cred(cred); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void cifs_exit_dns_resolver(void) | 
|  | { | 
|  | key_revoke(dns_resolver_cache->thread_keyring); | 
|  | unregister_key_type(&key_type_dns_resolver); | 
|  | put_cred(dns_resolver_cache); | 
|  | printk(KERN_NOTICE "Unregistered %s key type\n", | 
|  | key_type_dns_resolver.name); | 
|  | } |